mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-05 17:31:12 +00:00
Merge branch 'main' into owa
This commit is contained in:
commit
161b4a16f0
102
.eslintrc.js
102
.eslintrc.js
|
@ -9,6 +9,7 @@ module.exports = {
|
||||||
'plugin:import/recommended',
|
'plugin:import/recommended',
|
||||||
'plugin:promise/recommended',
|
'plugin:promise/recommended',
|
||||||
'plugin:jsdoc/recommended',
|
'plugin:jsdoc/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
],
|
],
|
||||||
|
|
||||||
env: {
|
env: {
|
||||||
|
@ -54,28 +55,14 @@ module.exports = {
|
||||||
'\\.(css|scss|json)$',
|
'\\.(css|scss|json)$',
|
||||||
],
|
],
|
||||||
'import/resolver': {
|
'import/resolver': {
|
||||||
node: {
|
typescript: {},
|
||||||
paths: ['app/javascript'],
|
|
||||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
'brace-style': 'warn',
|
|
||||||
'comma-dangle': ['error', 'always-multiline'],
|
|
||||||
'comma-spacing': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
before: false,
|
|
||||||
after: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'comma-style': ['warn', 'last'],
|
|
||||||
'consistent-return': 'error',
|
'consistent-return': 'error',
|
||||||
'dot-notation': 'error',
|
'dot-notation': 'error',
|
||||||
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
|
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
|
||||||
indent: ['warn', 2],
|
|
||||||
'jsx-quotes': ['error', 'prefer-single'],
|
'jsx-quotes': ['error', 'prefer-single'],
|
||||||
'no-case-declarations': 'off',
|
'no-case-declarations': 'off',
|
||||||
'no-catch-shadow': 'error',
|
'no-catch-shadow': 'error',
|
||||||
|
@ -95,7 +82,6 @@ module.exports = {
|
||||||
{ property: 'substr', message: 'Use .slice instead of .substr.' },
|
{ property: 'substr', message: 'Use .slice instead of .substr.' },
|
||||||
],
|
],
|
||||||
'no-self-assign': 'off',
|
'no-self-assign': 'off',
|
||||||
'no-trailing-spaces': 'warn',
|
|
||||||
'no-unused-expressions': 'error',
|
'no-unused-expressions': 'error',
|
||||||
'no-unused-vars': 'off',
|
'no-unused-vars': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
@ -107,30 +93,14 @@ module.exports = {
|
||||||
ignoreRestSiblings: true,
|
ignoreRestSiblings: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'object-curly-spacing': ['error', 'always'],
|
|
||||||
'padded-blocks': [
|
|
||||||
'error',
|
|
||||||
{
|
|
||||||
classes: 'always',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
quotes: ['error', 'single'],
|
|
||||||
semi: 'error',
|
|
||||||
'valid-typeof': 'error',
|
'valid-typeof': 'error',
|
||||||
|
|
||||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
|
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
|
||||||
'react/jsx-boolean-value': 'error',
|
'react/jsx-boolean-value': 'error',
|
||||||
'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
|
|
||||||
'react/jsx-curly-spacing': 'error',
|
|
||||||
'react/display-name': 'off',
|
'react/display-name': 'off',
|
||||||
'react/jsx-equals-spacing': 'error',
|
'react/jsx-equals-spacing': 'error',
|
||||||
'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
|
|
||||||
'react/jsx-indent': ['error', 2],
|
|
||||||
'react/jsx-no-bind': 'error',
|
'react/jsx-no-bind': 'error',
|
||||||
'react/jsx-no-target-blank': 'off',
|
'react/jsx-no-target-blank': 'off',
|
||||||
'react/jsx-tag-spacing': 'error',
|
|
||||||
'react/jsx-wrap-multilines': 'error',
|
|
||||||
'react/no-deprecated': 'off',
|
|
||||||
'react/no-unknown-property': 'off',
|
'react/no-unknown-property': 'off',
|
||||||
'react/self-closing-comp': 'error',
|
'react/self-closing-comp': 'error',
|
||||||
|
|
||||||
|
@ -194,11 +164,14 @@ module.exports = {
|
||||||
{
|
{
|
||||||
js: 'never',
|
js: 'never',
|
||||||
jsx: 'never',
|
jsx: 'never',
|
||||||
|
mjs: 'never',
|
||||||
ts: 'never',
|
ts: 'never',
|
||||||
tsx: 'never',
|
tsx: 'never',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'import/first': 'error',
|
||||||
'import/newline-after-import': 'error',
|
'import/newline-after-import': 'error',
|
||||||
|
'import/no-anonymous-default-export': 'error',
|
||||||
'import/no-extraneous-dependencies': [
|
'import/no-extraneous-dependencies': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
@ -213,6 +186,9 @@ module.exports = {
|
||||||
'import/no-amd': 'error',
|
'import/no-amd': 'error',
|
||||||
'import/no-commonjs': 'error',
|
'import/no-commonjs': 'error',
|
||||||
'import/no-import-module-exports': 'error',
|
'import/no-import-module-exports': 'error',
|
||||||
|
'import/no-relative-packages': 'error',
|
||||||
|
'import/no-self-import': 'error',
|
||||||
|
'import/no-useless-path-segments': 'error',
|
||||||
'import/no-webpack-loader-syntax': 'error',
|
'import/no-webpack-loader-syntax': 'error',
|
||||||
|
|
||||||
'promise/always-return': 'off',
|
'promise/always-return': 'off',
|
||||||
|
@ -284,6 +260,7 @@ module.exports = {
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
'plugin:jsx-a11y/recommended',
|
'plugin:jsx-a11y/recommended',
|
||||||
|
@ -291,10 +268,69 @@ module.exports = {
|
||||||
'plugin:import/typescript',
|
'plugin:import/typescript',
|
||||||
'plugin:promise/recommended',
|
'plugin:promise/recommended',
|
||||||
'plugin:jsdoc/recommended',
|
'plugin:jsdoc/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
project: './tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||||
|
|
||||||
|
'import/order': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
alphabetize: { order: 'asc' },
|
||||||
|
'newlines-between': 'always',
|
||||||
|
groups: [
|
||||||
|
'builtin',
|
||||||
|
'external',
|
||||||
|
'internal',
|
||||||
|
'parent',
|
||||||
|
['index', 'sibling'],
|
||||||
|
'object',
|
||||||
|
],
|
||||||
|
pathGroups: [
|
||||||
|
// React core packages
|
||||||
|
{
|
||||||
|
pattern: '{react,react-dom,prop-types}',
|
||||||
|
group: 'builtin',
|
||||||
|
position: 'after',
|
||||||
|
},
|
||||||
|
// I18n
|
||||||
|
{
|
||||||
|
pattern: 'react-intl',
|
||||||
|
group: 'builtin',
|
||||||
|
position: 'after',
|
||||||
|
},
|
||||||
|
// Common React utilities
|
||||||
|
{
|
||||||
|
pattern: '{classnames,react-helmet}',
|
||||||
|
group: 'external',
|
||||||
|
position: 'before',
|
||||||
|
},
|
||||||
|
// Immutable / Redux / data store
|
||||||
|
{
|
||||||
|
pattern: '{immutable,react-redux,react-immutable-proptypes,react-immutable-pure-component,reselect}',
|
||||||
|
group: 'external',
|
||||||
|
position: 'before',
|
||||||
|
},
|
||||||
|
// Internal packages
|
||||||
|
{
|
||||||
|
pattern: '{mastodon/**}',
|
||||||
|
group: 'internal',
|
||||||
|
position: 'after',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pathGroupsExcludedImportTypes: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
|
||||||
|
'@typescript-eslint/consistent-type-exports': 'error',
|
||||||
|
'@typescript-eslint/consistent-type-imports': 'error',
|
||||||
|
|
||||||
'jsdoc/require-jsdoc': 'off',
|
'jsdoc/require-jsdoc': 'off',
|
||||||
|
|
||||||
|
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
|
@ -17,10 +17,6 @@ updates:
|
||||||
- dependency-name: '@rails/ujs'
|
- dependency-name: '@rails/ujs'
|
||||||
versions:
|
versions:
|
||||||
- '>= 7'
|
- '>= 7'
|
||||||
# TODO: This was ignored in https://github.com/mastodon/mastodon/pull/19120
|
|
||||||
- dependency-name: 'uuid'
|
|
||||||
versions:
|
|
||||||
- '>= 9'
|
|
||||||
# TODO: This requires code changes for migration
|
# TODO: This requires code changes for migration
|
||||||
- dependency-name: 'tesseract.js'
|
- dependency-name: 'tesseract.js'
|
||||||
versions:
|
versions:
|
||||||
|
|
|
@ -70,8 +70,6 @@ app/javascript/styles/mastodon/reset.scss
|
||||||
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
|
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
|
||||||
*.js
|
*.js
|
||||||
*.jsx
|
*.jsx
|
||||||
*.ts
|
|
||||||
*.tsx
|
|
||||||
|
|
||||||
# Ignore HTML till cleaned and included in CI
|
# Ignore HTML till cleaned and included in CI
|
||||||
*.html
|
*.html
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
singleQuote: true
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ Metrics/MethodLength:
|
||||||
- 'lib/mastodon/*_cli.rb'
|
- 'lib/mastodon/*_cli.rb'
|
||||||
|
|
||||||
# Reason:
|
# Reason:
|
||||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
|
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
CountAsOne: [array, heredoc]
|
CountAsOne: [array, heredoc]
|
||||||
|
|
||||||
|
|
|
@ -94,11 +94,6 @@ Lint/AmbiguousBlockAssociation:
|
||||||
- 'spec/services/unsuspend_account_service_spec.rb'
|
- 'spec/services/unsuspend_account_service_spec.rb'
|
||||||
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
Lint/AmbiguousOperatorPrecedence:
|
|
||||||
Exclude:
|
|
||||||
- 'config/initializers/rack_attack.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||||
Lint/EmptyBlock:
|
Lint/EmptyBlock:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -643,24 +638,6 @@ RSpec/RepeatedExampleGroupBody:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/statuses_controller_spec.rb'
|
- 'spec/controllers/statuses_controller_spec.rb'
|
||||||
|
|
||||||
RSpec/RepeatedExampleGroupDescription:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/admin/reports/actions_controller_spec.rb'
|
|
||||||
- 'spec/policies/report_note_policy_spec.rb'
|
|
||||||
|
|
||||||
RSpec/ScatteredSetup:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb'
|
|
||||||
- 'spec/controllers/activitypub/outboxes_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/disputes/appeals_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/registrations_controller_spec.rb'
|
|
||||||
- 'spec/services/activitypub/process_account_service_spec.rb'
|
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
RSpec/SharedContext:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/services/unsuspend_account_service_spec.rb'
|
|
||||||
|
|
||||||
RSpec/StubbedMock:
|
RSpec/StubbedMock:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/base_controller_spec.rb'
|
- 'spec/controllers/api/base_controller_spec.rb'
|
||||||
|
|
|
@ -166,7 +166,7 @@ GEM
|
||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.39.0)
|
capybara (3.39.1)
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
|
@ -329,7 +329,7 @@ GEM
|
||||||
httplog (1.6.2)
|
httplog (1.6.2)
|
||||||
rack (>= 2.0)
|
rack (>= 2.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.12.0)
|
i18n (1.13.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (1.0.12)
|
i18n-tasks (1.0.12)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
|
@ -416,7 +416,7 @@ GEM
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2023.0218.1)
|
mime-types-data (3.2023.0218.1)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.8.1)
|
mini_portile2 (2.8.2)
|
||||||
minitest (5.18.0)
|
minitest (5.18.0)
|
||||||
msgpack (1.7.0)
|
msgpack (1.7.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
|
@ -696,7 +696,7 @@ GEM
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
thor (1.2.1)
|
thor (1.2.2)
|
||||||
tilt (2.1.0)
|
tilt (2.1.0)
|
||||||
timeout (0.3.2)
|
timeout (0.3.2)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
|
|
|
@ -58,7 +58,7 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_canonical_email_blocks_from_test
|
def set_canonical_email_blocks_from_test
|
||||||
@canonical_email_blocks = CanonicalEmailBlock.matching_email(params[:email])
|
@canonical_email_blocks = CanonicalEmailBlock.matching_email(params.require(:email))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_canonical_email_block
|
def set_canonical_email_block
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import type { LayoutType } from '../is_mobile';
|
import type { LayoutType } from '../is_mobile';
|
||||||
|
|
||||||
export const focusApp = createAction('APP_FOCUS');
|
export const focusApp = createAction('APP_FOCUS');
|
||||||
export const unfocusApp = createAction('APP_UNFOCUS');
|
export const unfocusApp = createAction('APP_UNFOCUS');
|
||||||
|
|
||||||
type ChangeLayoutPayload = {
|
interface ChangeLayoutPayload {
|
||||||
layout: LayoutType;
|
layout: LayoutType;
|
||||||
};
|
}
|
||||||
export const changeLayout =
|
export const changeLayout =
|
||||||
createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE');
|
createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE');
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { importFetchedStatuses } from './importer';
|
import { importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
|
import { me } from '../initial_state';
|
||||||
|
|
||||||
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
||||||
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
||||||
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
||||||
|
|
||||||
import { me } from '../initial_state';
|
|
||||||
|
|
||||||
export function fetchPinnedStatuses() {
|
export function fetchPinnedStatuses() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchPinnedStatusesRequest());
|
dispatch(fetchPinnedStatusesRequest());
|
||||||
|
|
|
@ -98,9 +98,9 @@ export const decode83 = (str: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const intToRGB = (int: number) => ({
|
export const intToRGB = (int: number) => ({
|
||||||
r: Math.max(0, (int >> 16)),
|
r: Math.max(0, int >> 16),
|
||||||
g: Math.max(0, (int >> 8) & 255),
|
g: Math.max(0, (int >> 8) & 255),
|
||||||
b: Math.max(0, (int & 255)),
|
b: Math.max(0, int & 255),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAverageFromBlurhash = (blurhash: string) => {
|
export const getAverageFromBlurhash = (blurhash: string) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export function compareId (id1: string, id2: string) {
|
export function compareId(id1: string, id2: string) {
|
||||||
if (id1 === id2) {
|
if (id1 === id2) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { fromJS } from 'immutable';
|
import { fromJS } from 'immutable';
|
||||||
import DisplayName from '../display_name';
|
import { DisplayName } from '../display_name';
|
||||||
|
|
||||||
describe('<DisplayName />', () => {
|
describe('<DisplayName />', () => {
|
||||||
it('renders display name + account name', () => {
|
it('renders display name + account name', () => {
|
||||||
|
|
|
@ -2,18 +2,18 @@ import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Avatar } from './avatar';
|
import { Avatar } from './avatar';
|
||||||
import DisplayName from './display_name';
|
import { DisplayName } from './display_name';
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from './icon_button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { me } from '../initial_state';
|
import { me } from '../initial_state';
|
||||||
import { RelativeTimestamp } from './relative_timestamp';
|
import { RelativeTimestamp } from './relative_timestamp';
|
||||||
import Skeleton from 'mastodon/components/skeleton';
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { counterRenderer } from 'mastodon/components/common_counter';
|
import { counterRenderer } from 'mastodon/components/common_counter';
|
||||||
import ShortNumber from 'mastodon/components/short_number';
|
import ShortNumber from 'mastodon/components/short_number';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||||
|
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
|
@ -77,20 +77,7 @@ class Account extends ImmutablePureComponent {
|
||||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
|
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return (
|
return <EmptyAccount size={size} minimal={minimal} />;
|
||||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
|
||||||
<div className='account__wrapper'>
|
|
||||||
<div className='account__display-name'>
|
|
||||||
<div className='account__avatar-wrapper'><Skeleton width={size} height={size} /></div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<DisplayName />
|
|
||||||
<Skeleton width='7ch' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import ShortNumber from './short_number';
|
|
||||||
import { TransitionMotion, spring } from 'react-motion';
|
import { TransitionMotion, spring } from 'react-motion';
|
||||||
|
|
||||||
import { reduceMotion } from '../initial_state';
|
import { reduceMotion } from '../initial_state';
|
||||||
|
|
||||||
|
import ShortNumber from './short_number';
|
||||||
|
|
||||||
const obfuscatedCount = (count: number) => {
|
const obfuscatedCount = (count: number) => {
|
||||||
if (count < 0) {
|
if (count < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -13,16 +16,13 @@ const obfuscatedCount = (count: number) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
value: number;
|
value: number;
|
||||||
obfuscate?: boolean;
|
obfuscate?: boolean;
|
||||||
}
|
}
|
||||||
export const AnimatedNumber: React.FC<Props> = ({
|
export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
|
||||||
value,
|
|
||||||
obfuscate,
|
|
||||||
})=> {
|
|
||||||
const [previousValue, setPreviousValue] = useState(value);
|
const [previousValue, setPreviousValue] = useState(value);
|
||||||
const [direction, setDirection] = useState<1|-1>(1);
|
const [direction, setDirection] = useState<1 | -1>(1);
|
||||||
|
|
||||||
if (previousValue !== value) {
|
if (previousValue !== value) {
|
||||||
setPreviousValue(value);
|
setPreviousValue(value);
|
||||||
|
@ -30,24 +30,49 @@ export const AnimatedNumber: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
|
const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
|
||||||
const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]);
|
const willLeave = useCallback(
|
||||||
|
() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }),
|
||||||
|
[direction]
|
||||||
|
);
|
||||||
|
|
||||||
if (reduceMotion) {
|
if (reduceMotion) {
|
||||||
return obfuscate ? <>{obfuscatedCount(value)}</> : <ShortNumber value={value} />;
|
return obfuscate ? (
|
||||||
|
<>{obfuscatedCount(value)}</>
|
||||||
|
) : (
|
||||||
|
<ShortNumber value={value} />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = [{
|
const styles = [
|
||||||
key: `${value}`,
|
{
|
||||||
data: value,
|
key: `${value}`,
|
||||||
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
|
data: value,
|
||||||
}];
|
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
|
<TransitionMotion
|
||||||
{items => (
|
styles={styles}
|
||||||
|
willEnter={willEnter}
|
||||||
|
willLeave={willLeave}
|
||||||
|
>
|
||||||
|
{(items) => (
|
||||||
<span className='animated-number'>
|
<span className='animated-number'>
|
||||||
{items.map(({ key, data, style }) => (
|
{items.map(({ key, data, style }) => (
|
||||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
|
<span
|
||||||
|
key={key}
|
||||||
|
style={{
|
||||||
|
position: direction * style.y > 0 ? 'absolute' : 'static',
|
||||||
|
transform: `translateY(${style.y * 100}%)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{obfuscate ? (
|
||||||
|
obfuscatedCount(data as number)
|
||||||
|
) : (
|
||||||
|
<ShortNumber value={data as number} />
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
))}
|
))}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -154,7 +154,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
|
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
|
||||||
this.setState({ suggestionsHidden: false });
|
this.setState({ suggestionsHidden: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
|
if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) {
|
||||||
this.setState({ suggestionsHidden: false });
|
this.setState({ suggestionsHidden: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { autoPlayGif } from '../initial_state';
|
|
||||||
import { useHovering } from '../../hooks/useHovering';
|
import { useHovering } from '../../hooks/useHovering';
|
||||||
import type { Account } from '../../types/resources';
|
import type { Account } from '../../types/resources';
|
||||||
|
import { autoPlayGif } from '../initial_state';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
account: Account;
|
account: Account;
|
||||||
size: number;
|
size: number;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const Avatar: React.FC<Props> = ({
|
export const Avatar: React.FC<Props> = ({
|
||||||
account,
|
account,
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Account } from '../../types/resources';
|
|
||||||
import { useHovering } from '../../hooks/useHovering';
|
import { useHovering } from '../../hooks/useHovering';
|
||||||
|
import type { Account } from '../../types/resources';
|
||||||
import { autoPlayGif } from '../initial_state';
|
import { autoPlayGif } from '../initial_state';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
account: Account;
|
account: Account;
|
||||||
friend: Account;
|
friend: Account;
|
||||||
size?: number;
|
size?: number;
|
||||||
baseSize?: number;
|
baseSize?: number;
|
||||||
overlaySize?: number;
|
overlaySize?: number;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const AvatarOverlay: React.FC<Props> = ({
|
export const AvatarOverlay: React.FC<Props> = ({
|
||||||
account,
|
account,
|
||||||
|
@ -18,13 +19,19 @@ export const AvatarOverlay: React.FC<Props> = ({
|
||||||
baseSize = 36,
|
baseSize = 36,
|
||||||
overlaySize = 24,
|
overlaySize = 24,
|
||||||
}) => {
|
}) => {
|
||||||
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif);
|
const { hovering, handleMouseEnter, handleMouseLeave } =
|
||||||
const accountSrc = hovering ? account?.get('avatar') : account?.get('avatar_static');
|
useHovering(autoPlayGif);
|
||||||
const friendSrc = hovering ? friend?.get('avatar') : friend?.get('avatar_static');
|
const accountSrc = hovering
|
||||||
|
? account?.get('avatar')
|
||||||
|
: account?.get('avatar_static');
|
||||||
|
const friendSrc = hovering
|
||||||
|
? friend?.get('avatar')
|
||||||
|
: friend?.get('avatar_static');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='account__avatar-overlay' style={{ width: size, height: size }}
|
className='account__avatar-overlay'
|
||||||
|
style={{ width: size, height: size }}
|
||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { decode } from 'blurhash';
|
|
||||||
import React, { useRef, useEffect } from 'react';
|
import React, { useRef, useEffect } from 'react';
|
||||||
|
|
||||||
type Props = {
|
import { decode } from 'blurhash';
|
||||||
|
|
||||||
|
interface Props extends React.HTMLAttributes<HTMLCanvasElement> {
|
||||||
hash: string;
|
hash: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
|
dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
|
||||||
children?: never;
|
children?: never;
|
||||||
[key: string]: any;
|
|
||||||
}
|
}
|
||||||
const Blurhash: React.FC<Props> = ({
|
const Blurhash: React.FC<Props> = ({
|
||||||
hash,
|
hash,
|
||||||
|
@ -21,6 +21,7 @@ const Blurhash: React.FC<Props> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const canvas = canvasRef.current!;
|
const canvas = canvasRef.current!;
|
||||||
|
|
||||||
// eslint-disable-next-line no-self-assign
|
// eslint-disable-next-line no-self-assign
|
||||||
canvas.width = canvas.width; // resets canvas
|
canvas.width = canvas.width; // resets canvas
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const Check: React.FC = () => (
|
export const Check: React.FC = () => (
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor'>
|
<svg
|
||||||
<path fillRule='evenodd' d='M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z' clipRule='evenodd' />
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
|
viewBox='0 0 20 20'
|
||||||
|
fill='currentColor'
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule='evenodd'
|
||||||
|
d='M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z'
|
||||||
|
clipRule='evenodd'
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { autoPlayGif } from 'mastodon/initial_state';
|
|
||||||
import Skeleton from 'mastodon/components/skeleton';
|
|
||||||
|
|
||||||
export default class DisplayName extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
others: ImmutablePropTypes.list,
|
|
||||||
localDomain: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseEnter = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-original');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleMouseLeave = ({ currentTarget }) => {
|
|
||||||
if (autoPlayGif) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
|
||||||
|
|
||||||
for (var i = 0; i < emojis.length; i++) {
|
|
||||||
let emoji = emojis[i];
|
|
||||||
emoji.src = emoji.getAttribute('data-static');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { others, localDomain } = this.props;
|
|
||||||
|
|
||||||
let displayName, suffix, account;
|
|
||||||
|
|
||||||
if (others && others.size > 1) {
|
|
||||||
displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]);
|
|
||||||
|
|
||||||
if (others.size - 2 > 0) {
|
|
||||||
suffix = `+${others.size - 2}`;
|
|
||||||
}
|
|
||||||
} else if ((others && others.size > 0) || this.props.account) {
|
|
||||||
if (others && others.size > 0) {
|
|
||||||
account = others.first();
|
|
||||||
} else {
|
|
||||||
account = this.props.account;
|
|
||||||
}
|
|
||||||
|
|
||||||
let acct = account.get('acct');
|
|
||||||
|
|
||||||
if (acct.indexOf('@') === -1 && localDomain) {
|
|
||||||
acct = `${acct}@${localDomain}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>;
|
|
||||||
suffix = <span className='display-name__account'>@{acct}</span>;
|
|
||||||
} else {
|
|
||||||
displayName = <bdi><strong className='display-name__html'><Skeleton width='10ch' /></strong></bdi>;
|
|
||||||
suffix = <span className='display-name__account'><Skeleton width='7ch' /></span>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className='display-name' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
|
||||||
{displayName} {suffix}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
121
app/javascript/mastodon/components/display_name.tsx
Normal file
121
app/javascript/mastodon/components/display_name.tsx
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import type { List } from 'immutable';
|
||||||
|
|
||||||
|
import type { Account } from '../../types/resources';
|
||||||
|
import { autoPlayGif } from '../initial_state';
|
||||||
|
|
||||||
|
import Skeleton from './skeleton';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
account?: Account;
|
||||||
|
others?: List<Account>;
|
||||||
|
localDomain?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisplayName extends React.PureComponent<Props> {
|
||||||
|
handleMouseEnter: React.ReactEventHandler<HTMLSpanElement> = ({
|
||||||
|
currentTarget,
|
||||||
|
}) => {
|
||||||
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis =
|
||||||
|
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
||||||
|
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
const originalSrc = emoji.getAttribute('data-original');
|
||||||
|
if (originalSrc != null) emoji.src = originalSrc;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMouseLeave: React.ReactEventHandler<HTMLSpanElement> = ({
|
||||||
|
currentTarget,
|
||||||
|
}) => {
|
||||||
|
if (autoPlayGif) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojis =
|
||||||
|
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
||||||
|
|
||||||
|
emojis.forEach((emoji) => {
|
||||||
|
const staticSrc = emoji.getAttribute('data-static');
|
||||||
|
if (staticSrc != null) emoji.src = staticSrc;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { others, localDomain } = this.props;
|
||||||
|
|
||||||
|
let displayName: React.ReactNode,
|
||||||
|
suffix: React.ReactNode,
|
||||||
|
account: Account | undefined;
|
||||||
|
|
||||||
|
if (others && others.size > 0) {
|
||||||
|
account = others.first();
|
||||||
|
} else if (this.props.account) {
|
||||||
|
account = this.props.account;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (others && others.size > 1) {
|
||||||
|
displayName = others
|
||||||
|
.take(2)
|
||||||
|
.map((a) => (
|
||||||
|
<bdi key={a.get('id')}>
|
||||||
|
<strong
|
||||||
|
className='display-name__html'
|
||||||
|
dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }}
|
||||||
|
/>
|
||||||
|
</bdi>
|
||||||
|
))
|
||||||
|
.reduce((prev, cur) => [prev, ', ', cur]);
|
||||||
|
|
||||||
|
if (others.size - 2 > 0) {
|
||||||
|
suffix = `+${others.size - 2}`;
|
||||||
|
}
|
||||||
|
} else if (account) {
|
||||||
|
let acct = account.get('acct');
|
||||||
|
|
||||||
|
if (acct.indexOf('@') === -1 && localDomain) {
|
||||||
|
acct = `${acct}@${localDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName = (
|
||||||
|
<bdi>
|
||||||
|
<strong
|
||||||
|
className='display-name__html'
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: account.get('display_name_html'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</bdi>
|
||||||
|
);
|
||||||
|
suffix = <span className='display-name__account'>@{acct}</span>;
|
||||||
|
} else {
|
||||||
|
displayName = (
|
||||||
|
<bdi>
|
||||||
|
<strong className='display-name__html'>
|
||||||
|
<Skeleton width='10ch' />
|
||||||
|
</strong>
|
||||||
|
</bdi>
|
||||||
|
);
|
||||||
|
suffix = (
|
||||||
|
<span className='display-name__account'>
|
||||||
|
<Skeleton width='7ch' />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className='display-name'
|
||||||
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
onMouseLeave={this.handleMouseLeave}
|
||||||
|
>
|
||||||
|
{displayName} {suffix}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import type { InjectedIntl } from 'react-intl';
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from './icon_button';
|
||||||
import { InjectedIntl, defineMessages, injectIntl } from 'react-intl';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unblockDomain: {
|
unblockDomain: {
|
||||||
|
@ -9,11 +12,11 @@ const messages = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
domain: string;
|
domain: string;
|
||||||
onUnblockDomain: (domain: string) => void;
|
onUnblockDomain: (domain: string) => void;
|
||||||
intl: InjectedIntl;
|
intl: InjectedIntl;
|
||||||
};
|
}
|
||||||
const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => {
|
const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => {
|
||||||
const handleDomainUnblock = useCallback(() => {
|
const handleDomainUnblock = useCallback(() => {
|
||||||
onUnblockDomain(domain);
|
onUnblockDomain(domain);
|
||||||
|
|
33
app/javascript/mastodon/components/empty_account.tsx
Normal file
33
app/javascript/mastodon/components/empty_account.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
|
import Skeleton from 'mastodon/components/skeleton';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
size?: number;
|
||||||
|
minimal?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmptyAccount: React.FC<Props> = ({
|
||||||
|
size = 46,
|
||||||
|
minimal = false,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'>
|
||||||
|
<Skeleton width={size} height={size} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<DisplayName />
|
||||||
|
<Skeleton width='7ch' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
src: string;
|
src: string;
|
||||||
key: string;
|
key: string;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
|
@ -17,19 +17,23 @@ export const GIFV: React.FC<Props> = ({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
onClick,
|
onClick,
|
||||||
})=> {
|
}) => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> = useCallback(() => {
|
const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> =
|
||||||
setLoading(false);
|
useCallback(() => {
|
||||||
}, [setLoading]);
|
setLoading(false);
|
||||||
|
}, [setLoading]);
|
||||||
|
|
||||||
const handleClick: React.MouseEventHandler = useCallback((e) => {
|
const handleClick: React.MouseEventHandler = useCallback(
|
||||||
if (onClick) {
|
(e) => {
|
||||||
e.stopPropagation();
|
if (onClick) {
|
||||||
onClick();
|
e.stopPropagation();
|
||||||
}
|
onClick();
|
||||||
}, [onClick]);
|
}
|
||||||
|
},
|
||||||
|
[onClick]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='gifv' style={{ position: 'relative' }}>
|
<div className='gifv' style={{ position: 'relative' }}>
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
interface Props extends React.HTMLAttributes<HTMLImageElement> {
|
||||||
id: string;
|
id: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
fixedWidth?: boolean;
|
fixedWidth?: boolean;
|
||||||
children?: never;
|
children?: never;
|
||||||
[key: string]: any;
|
|
||||||
}
|
}
|
||||||
export const Icon: React.FC<Props> = ({ id, className, fixedWidth, ...other }) =>
|
|
||||||
<i className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })} {...other} />;
|
export const Icon: React.FC<Props> = ({
|
||||||
|
id,
|
||||||
|
className,
|
||||||
|
fixedWidth,
|
||||||
|
...other
|
||||||
|
}) => (
|
||||||
|
<i
|
||||||
|
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
|
||||||
|
{...other}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Icon } from './icon';
|
|
||||||
import { AnimatedNumber } from './animated_number';
|
|
||||||
|
|
||||||
type Props = {
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { AnimatedNumber } from './animated_number';
|
||||||
|
import { Icon } from './icon';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
title: string;
|
title: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
@ -26,12 +28,11 @@ type Props = {
|
||||||
href?: string;
|
href?: string;
|
||||||
ariaHidden: boolean;
|
ariaHidden: boolean;
|
||||||
}
|
}
|
||||||
type States = {
|
interface States {
|
||||||
activate: boolean,
|
activate: boolean;
|
||||||
deactivate: boolean,
|
deactivate: boolean;
|
||||||
}
|
}
|
||||||
export class IconButton extends React.PureComponent<Props, States> {
|
export class IconButton extends React.PureComponent<Props, States> {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
size: 18,
|
size: 18,
|
||||||
active: false,
|
active: false,
|
||||||
|
@ -47,7 +48,7 @@ export class IconButton extends React.PureComponent<Props, States> {
|
||||||
deactivate: false,
|
deactivate: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps (nextProps: Props) {
|
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||||
if (!nextProps.animate) return;
|
if (!nextProps.animate) return;
|
||||||
|
|
||||||
if (this.props.active && !nextProps.active) {
|
if (this.props.active && !nextProps.active) {
|
||||||
|
@ -57,7 +58,7 @@ export class IconButton extends React.PureComponent<Props, States> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!this.props.disabled && this.props.onClick != null) {
|
if (!this.props.disabled && this.props.onClick != null) {
|
||||||
|
@ -83,7 +84,7 @@ export class IconButton extends React.PureComponent<Props, States> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const style = {
|
const style = {
|
||||||
fontSize: `${this.props.size}px`,
|
fontSize: `${this.props.size}px`,
|
||||||
width: `${this.props.size * 1.28571429}px`,
|
width: `${this.props.size * 1.28571429}px`,
|
||||||
|
@ -109,10 +110,7 @@ export class IconButton extends React.PureComponent<Props, States> {
|
||||||
ariaHidden,
|
ariaHidden,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const { activate, deactivate } = this.state;
|
||||||
activate,
|
|
||||||
deactivate,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const classes = classNames(className, 'icon-button', {
|
const classes = classNames(className, 'icon-button', {
|
||||||
active,
|
active,
|
||||||
|
@ -130,7 +128,12 @@ export class IconButton extends React.PureComponent<Props, States> {
|
||||||
|
|
||||||
let contents = (
|
let contents = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Icon id={icon} fixedWidth aria-hidden='true' /> {typeof counter !== 'undefined' && <span className='icon-button__counter'><AnimatedNumber value={counter} obfuscate={obfuscateCount} /></span>}
|
<Icon id={icon} fixedWidth aria-hidden='true' />{' '}
|
||||||
|
{typeof counter !== 'undefined' && (
|
||||||
|
<span className='icon-button__counter'>
|
||||||
|
<AnimatedNumber value={counter} obfuscate={obfuscateCount} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -162,5 +165,4 @@ export class IconButton extends React.PureComponent<Props, States> {
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
|
|
||||||
const formatNumber = (num: number): number | string => num > 40 ? '40+' : num;
|
const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num);
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
count: number;
|
count: number;
|
||||||
issueBadge: boolean;
|
issueBadge: boolean;
|
||||||
className: string;
|
className: string;
|
||||||
}
|
}
|
||||||
export const IconWithBadge: React.FC<Props> = ({ id, count, issueBadge, className }) => (
|
export const IconWithBadge: React.FC<Props> = ({
|
||||||
|
id,
|
||||||
|
count,
|
||||||
|
issueBadge,
|
||||||
|
className,
|
||||||
|
}) => (
|
||||||
<i className='icon-with-badge'>
|
<i className='icon-with-badge'>
|
||||||
<Icon id={id} fixedWidth className={className} />
|
<Icon id={id} fixedWidth className={className} />
|
||||||
{count > 0 && <i className='icon-with-badge__badge'>{formatNumber(count)}</i>}
|
{count > 0 && (
|
||||||
|
<i className='icon-with-badge__badge'>{formatNumber(count)}</i>
|
||||||
|
)}
|
||||||
{issueBadge && <i className='icon-with-badge__issue-badge' />}
|
{issueBadge && <i className='icon-with-badge__issue-badge' />}
|
||||||
</i>
|
</i>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import logo from 'mastodon/../images/logo.svg';
|
import logo from 'mastodon/../images/logo.svg';
|
||||||
|
|
||||||
export const WordmarkLogo = () => (
|
export const WordmarkLogo: React.FC = () => (
|
||||||
<svg viewBox='0 0 261 66' className='logo logo--wordmark' role='img'>
|
<svg viewBox='0 0 261 66' className='logo logo--wordmark' role='img'>
|
||||||
<title>Mastodon</title>
|
<title>Mastodon</title>
|
||||||
<use xlinkHref='#logo-symbol-wordmark' />
|
<use xlinkHref='#logo-symbol-wordmark' />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SymbolLogo = () => (
|
export const SymbolLogo: React.FC = () => (
|
||||||
<img src={logo} alt='Mastodon' className='logo logo--icon' />
|
<img src={logo} alt='Mastodon' className='logo logo--icon' />
|
||||||
);
|
);
|
||||||
|
|
||||||
export default WordmarkLogo;
|
|
|
@ -231,7 +231,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) {
|
if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) {
|
||||||
this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' });
|
this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' });
|
||||||
} else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
} else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
||||||
|
@ -256,7 +256,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = (index) => {
|
handleClick = (index) => {
|
||||||
this.props.onOpenMedia(this.props.media, index);
|
this.props.onOpenMedia(this.props.media, index, this.props.lang);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleRef = c => {
|
handleRef = c => {
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default class ModalRoot extends React.PureComponent {
|
||||||
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
|
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!!nextProps.children && !this.props.children) {
|
if (!!nextProps.children && !this.props.children) {
|
||||||
this.activeElement = document.activeElement;
|
this.activeElement = document.activeElement;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
export const NotSignedInIndicator: React.FC = () => (
|
export const NotSignedInIndicator: React.FC = () => (
|
||||||
<div className='scrollable scrollable--flex'>
|
<div className='scrollable scrollable--flex'>
|
||||||
<div className='empty-column-indicator'>
|
<div className='empty-column-indicator'>
|
||||||
<FormattedMessage id='not_signed_in_indicator.not_signed_in' defaultMessage='You need to sign in to access this resource.' />
|
<FormattedMessage
|
||||||
|
id='not_signed_in_indicator.not_signed_in'
|
||||||
|
defaultMessage='You need to login to access this resource.'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const RadioButton: React.FC<Props> = ({ name, value, checked, onChange, label }) => {
|
export const RadioButton: React.FC<Props> = ({
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
checked,
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<label className='radio-button'>
|
<label className='radio-button'>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -1,23 +1,55 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { injectIntl, defineMessages, InjectedIntl } from 'react-intl';
|
|
||||||
|
import type { InjectedIntl } from 'react-intl';
|
||||||
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
today: { id: 'relative_time.today', defaultMessage: 'today' },
|
today: { id: 'relative_time.today', defaultMessage: 'today' },
|
||||||
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
|
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
|
||||||
just_now_full: { id: 'relative_time.full.just_now', defaultMessage: 'just now' },
|
just_now_full: {
|
||||||
|
id: 'relative_time.full.just_now',
|
||||||
|
defaultMessage: 'just now',
|
||||||
|
},
|
||||||
seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' },
|
seconds: { id: 'relative_time.seconds', defaultMessage: '{number}s' },
|
||||||
seconds_full: { id: 'relative_time.full.seconds', defaultMessage: '{number, plural, one {# second} other {# seconds}} ago' },
|
seconds_full: {
|
||||||
|
id: 'relative_time.full.seconds',
|
||||||
|
defaultMessage: '{number, plural, one {# second} other {# seconds}} ago',
|
||||||
|
},
|
||||||
minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' },
|
minutes: { id: 'relative_time.minutes', defaultMessage: '{number}m' },
|
||||||
minutes_full: { id: 'relative_time.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}} ago' },
|
minutes_full: {
|
||||||
|
id: 'relative_time.full.minutes',
|
||||||
|
defaultMessage: '{number, plural, one {# minute} other {# minutes}} ago',
|
||||||
|
},
|
||||||
hours: { id: 'relative_time.hours', defaultMessage: '{number}h' },
|
hours: { id: 'relative_time.hours', defaultMessage: '{number}h' },
|
||||||
hours_full: { id: 'relative_time.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}} ago' },
|
hours_full: {
|
||||||
|
id: 'relative_time.full.hours',
|
||||||
|
defaultMessage: '{number, plural, one {# hour} other {# hours}} ago',
|
||||||
|
},
|
||||||
days: { id: 'relative_time.days', defaultMessage: '{number}d' },
|
days: { id: 'relative_time.days', defaultMessage: '{number}d' },
|
||||||
days_full: { id: 'relative_time.full.days', defaultMessage: '{number, plural, one {# day} other {# days}} ago' },
|
days_full: {
|
||||||
moments_remaining: { id: 'time_remaining.moments', defaultMessage: 'Moments remaining' },
|
id: 'relative_time.full.days',
|
||||||
seconds_remaining: { id: 'time_remaining.seconds', defaultMessage: '{number, plural, one {# second} other {# seconds}} left' },
|
defaultMessage: '{number, plural, one {# day} other {# days}} ago',
|
||||||
minutes_remaining: { id: 'time_remaining.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}} left' },
|
},
|
||||||
hours_remaining: { id: 'time_remaining.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}} left' },
|
moments_remaining: {
|
||||||
days_remaining: { id: 'time_remaining.days', defaultMessage: '{number, plural, one {# day} other {# days}} left' },
|
id: 'time_remaining.moments',
|
||||||
|
defaultMessage: 'Moments remaining',
|
||||||
|
},
|
||||||
|
seconds_remaining: {
|
||||||
|
id: 'time_remaining.seconds',
|
||||||
|
defaultMessage: '{number, plural, one {# second} other {# seconds}} left',
|
||||||
|
},
|
||||||
|
minutes_remaining: {
|
||||||
|
id: 'time_remaining.minutes',
|
||||||
|
defaultMessage: '{number, plural, one {# minute} other {# minutes}} left',
|
||||||
|
},
|
||||||
|
hours_remaining: {
|
||||||
|
id: 'time_remaining.hours',
|
||||||
|
defaultMessage: '{number, plural, one {# hour} other {# hours}} left',
|
||||||
|
},
|
||||||
|
days_remaining: {
|
||||||
|
id: 'time_remaining.days',
|
||||||
|
defaultMessage: '{number, plural, one {# day} other {# days}} left',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const dateFormatOptions = {
|
const dateFormatOptions = {
|
||||||
|
@ -36,8 +68,8 @@ const shortDateFormatOptions = {
|
||||||
|
|
||||||
const SECOND = 1000;
|
const SECOND = 1000;
|
||||||
const MINUTE = 1000 * 60;
|
const MINUTE = 1000 * 60;
|
||||||
const HOUR = 1000 * 60 * 60;
|
const HOUR = 1000 * 60 * 60;
|
||||||
const DAY = 1000 * 60 * 60 * 24;
|
const DAY = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
const MAX_DELAY = 2147483647;
|
const MAX_DELAY = 2147483647;
|
||||||
|
|
||||||
|
@ -57,20 +89,27 @@ const selectUnits = (delta: number) => {
|
||||||
|
|
||||||
const getUnitDelay = (units: string) => {
|
const getUnitDelay = (units: string) => {
|
||||||
switch (units) {
|
switch (units) {
|
||||||
case 'second':
|
case 'second':
|
||||||
return SECOND;
|
return SECOND;
|
||||||
case 'minute':
|
case 'minute':
|
||||||
return MINUTE;
|
return MINUTE;
|
||||||
case 'hour':
|
case 'hour':
|
||||||
return HOUR;
|
return HOUR;
|
||||||
case 'day':
|
case 'day':
|
||||||
return DAY;
|
return DAY;
|
||||||
default:
|
default:
|
||||||
return MAX_DELAY;
|
return MAX_DELAY;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const timeAgoString = (intl: InjectedIntl, date: Date, now: number, year: number, timeGiven: boolean, short?: boolean) => {
|
export const timeAgoString = (
|
||||||
|
intl: InjectedIntl,
|
||||||
|
date: Date,
|
||||||
|
now: number,
|
||||||
|
year: number,
|
||||||
|
timeGiven: boolean,
|
||||||
|
short?: boolean
|
||||||
|
) => {
|
||||||
const delta = now - date.getTime();
|
const delta = now - date.getTime();
|
||||||
|
|
||||||
let relativeTime;
|
let relativeTime;
|
||||||
|
@ -78,27 +117,49 @@ export const timeAgoString = (intl: InjectedIntl, date: Date, now: number, year:
|
||||||
if (delta < DAY && !timeGiven) {
|
if (delta < DAY && !timeGiven) {
|
||||||
relativeTime = intl.formatMessage(messages.today);
|
relativeTime = intl.formatMessage(messages.today);
|
||||||
} else if (delta < 10 * SECOND) {
|
} else if (delta < 10 * SECOND) {
|
||||||
relativeTime = intl.formatMessage(short ? messages.just_now : messages.just_now_full);
|
relativeTime = intl.formatMessage(
|
||||||
|
short ? messages.just_now : messages.just_now_full
|
||||||
|
);
|
||||||
} else if (delta < 7 * DAY) {
|
} else if (delta < 7 * DAY) {
|
||||||
if (delta < MINUTE) {
|
if (delta < MINUTE) {
|
||||||
relativeTime = intl.formatMessage(short ? messages.seconds : messages.seconds_full, { number: Math.floor(delta / SECOND) });
|
relativeTime = intl.formatMessage(
|
||||||
|
short ? messages.seconds : messages.seconds_full,
|
||||||
|
{ number: Math.floor(delta / SECOND) }
|
||||||
|
);
|
||||||
} else if (delta < HOUR) {
|
} else if (delta < HOUR) {
|
||||||
relativeTime = intl.formatMessage(short ? messages.minutes : messages.minutes_full, { number: Math.floor(delta / MINUTE) });
|
relativeTime = intl.formatMessage(
|
||||||
|
short ? messages.minutes : messages.minutes_full,
|
||||||
|
{ number: Math.floor(delta / MINUTE) }
|
||||||
|
);
|
||||||
} else if (delta < DAY) {
|
} else if (delta < DAY) {
|
||||||
relativeTime = intl.formatMessage(short ? messages.hours : messages.hours_full, { number: Math.floor(delta / HOUR) });
|
relativeTime = intl.formatMessage(
|
||||||
|
short ? messages.hours : messages.hours_full,
|
||||||
|
{ number: Math.floor(delta / HOUR) }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
relativeTime = intl.formatMessage(short ? messages.days : messages.days_full, { number: Math.floor(delta / DAY) });
|
relativeTime = intl.formatMessage(
|
||||||
|
short ? messages.days : messages.days_full,
|
||||||
|
{ number: Math.floor(delta / DAY) }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (date.getFullYear() === year) {
|
} else if (date.getFullYear() === year) {
|
||||||
relativeTime = intl.formatDate(date, shortDateFormatOptions);
|
relativeTime = intl.formatDate(date, shortDateFormatOptions);
|
||||||
} else {
|
} else {
|
||||||
relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' });
|
relativeTime = intl.formatDate(date, {
|
||||||
|
...shortDateFormatOptions,
|
||||||
|
year: 'numeric',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return relativeTime;
|
return relativeTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeRemainingString = (intl: InjectedIntl, date: Date, now: number, timeGiven = true) => {
|
const timeRemainingString = (
|
||||||
|
intl: InjectedIntl,
|
||||||
|
date: Date,
|
||||||
|
now: number,
|
||||||
|
timeGiven = true
|
||||||
|
) => {
|
||||||
const delta = date.getTime() - now;
|
const delta = date.getTime() - now;
|
||||||
|
|
||||||
let relativeTime;
|
let relativeTime;
|
||||||
|
@ -108,96 +169,112 @@ const timeRemainingString = (intl: InjectedIntl, date: Date, now: number, timeGi
|
||||||
} else if (delta < 10 * SECOND) {
|
} else if (delta < 10 * SECOND) {
|
||||||
relativeTime = intl.formatMessage(messages.moments_remaining);
|
relativeTime = intl.formatMessage(messages.moments_remaining);
|
||||||
} else if (delta < MINUTE) {
|
} else if (delta < MINUTE) {
|
||||||
relativeTime = intl.formatMessage(messages.seconds_remaining, { number: Math.floor(delta / SECOND) });
|
relativeTime = intl.formatMessage(messages.seconds_remaining, {
|
||||||
|
number: Math.floor(delta / SECOND),
|
||||||
|
});
|
||||||
} else if (delta < HOUR) {
|
} else if (delta < HOUR) {
|
||||||
relativeTime = intl.formatMessage(messages.minutes_remaining, { number: Math.floor(delta / MINUTE) });
|
relativeTime = intl.formatMessage(messages.minutes_remaining, {
|
||||||
|
number: Math.floor(delta / MINUTE),
|
||||||
|
});
|
||||||
} else if (delta < DAY) {
|
} else if (delta < DAY) {
|
||||||
relativeTime = intl.formatMessage(messages.hours_remaining, { number: Math.floor(delta / HOUR) });
|
relativeTime = intl.formatMessage(messages.hours_remaining, {
|
||||||
|
number: Math.floor(delta / HOUR),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
relativeTime = intl.formatMessage(messages.days_remaining, { number: Math.floor(delta / DAY) });
|
relativeTime = intl.formatMessage(messages.days_remaining, {
|
||||||
|
number: Math.floor(delta / DAY),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return relativeTime;
|
return relativeTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
intl: InjectedIntl;
|
intl: InjectedIntl;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
year: number;
|
year: number;
|
||||||
futureDate?: boolean;
|
futureDate?: boolean;
|
||||||
short?: boolean;
|
short?: boolean;
|
||||||
}
|
}
|
||||||
type States = {
|
interface States {
|
||||||
now: number;
|
now: number;
|
||||||
}
|
}
|
||||||
class RelativeTimestamp extends React.Component<Props, States> {
|
class RelativeTimestamp extends React.Component<Props, States> {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
now: this.props.intl.now(),
|
now: this.props.intl.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
year: (new Date()).getFullYear(),
|
year: new Date().getFullYear(),
|
||||||
short: true,
|
short: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
_timer: number | undefined;
|
_timer: number | undefined;
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps: Props, nextState: States) {
|
shouldComponentUpdate(nextProps: Props, nextState: States) {
|
||||||
// As of right now the locale doesn't change without a new page load,
|
// As of right now the locale doesn't change without a new page load,
|
||||||
// but we might as well check in case that ever changes.
|
// but we might as well check in case that ever changes.
|
||||||
return this.props.timestamp !== nextProps.timestamp ||
|
return (
|
||||||
|
this.props.timestamp !== nextProps.timestamp ||
|
||||||
this.props.intl.locale !== nextProps.intl.locale ||
|
this.props.intl.locale !== nextProps.intl.locale ||
|
||||||
this.state.now !== nextState.now;
|
this.state.now !== nextState.now
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps (nextProps: Props) {
|
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||||
if (this.props.timestamp !== nextProps.timestamp) {
|
if (this.props.timestamp !== nextProps.timestamp) {
|
||||||
this.setState({ now: this.props.intl.now() });
|
this.setState({ now: this.props.intl.now() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this._scheduleNextUpdate(this.props, this.state);
|
this._scheduleNextUpdate(this.props, this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillUpdate (nextProps: Props, nextState: States) {
|
UNSAFE_componentWillUpdate(nextProps: Props, nextState: States) {
|
||||||
this._scheduleNextUpdate(nextProps, nextState);
|
this._scheduleNextUpdate(nextProps, nextState);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
window.clearTimeout(this._timer);
|
window.clearTimeout(this._timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
_scheduleNextUpdate (props: Props, state: States) {
|
_scheduleNextUpdate(props: Props, state: States) {
|
||||||
window.clearTimeout(this._timer);
|
window.clearTimeout(this._timer);
|
||||||
|
|
||||||
const { timestamp } = props;
|
const { timestamp } = props;
|
||||||
const delta = (new Date(timestamp)).getTime() - state.now;
|
const delta = new Date(timestamp).getTime() - state.now;
|
||||||
const unitDelay = getUnitDelay(selectUnits(delta));
|
const unitDelay = getUnitDelay(selectUnits(delta));
|
||||||
const unitRemainder = Math.abs(delta % unitDelay);
|
const unitRemainder = Math.abs(delta % unitDelay);
|
||||||
const updateInterval = 1000 * 10;
|
const updateInterval = 1000 * 10;
|
||||||
const delay = delta < 0 ? Math.max(updateInterval, unitDelay - unitRemainder) : Math.max(updateInterval, unitRemainder);
|
const delay =
|
||||||
|
delta < 0
|
||||||
|
? Math.max(updateInterval, unitDelay - unitRemainder)
|
||||||
|
: Math.max(updateInterval, unitRemainder);
|
||||||
|
|
||||||
this._timer = window.setTimeout(() => {
|
this._timer = window.setTimeout(() => {
|
||||||
this.setState({ now: this.props.intl.now() });
|
this.setState({ now: this.props.intl.now() });
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { timestamp, intl, year, futureDate, short } = this.props;
|
const { timestamp, intl, year, futureDate, short } = this.props;
|
||||||
|
|
||||||
const timeGiven = timestamp.includes('T');
|
const timeGiven = timestamp.includes('T');
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
const relativeTime = futureDate ? timeRemainingString(intl, date, this.state.now, timeGiven) : timeAgoString(intl, date, this.state.now, year, timeGiven, short);
|
const relativeTime = futureDate
|
||||||
|
? timeRemainingString(intl, date, this.state.now, timeGiven)
|
||||||
|
: timeAgoString(intl, date, this.state.now, year, timeGiven, short);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<time dateTime={timestamp} title={intl.formatDate(date, dateFormatOptions)}>
|
<time
|
||||||
|
dateTime={timestamp}
|
||||||
|
title={intl.formatDate(date, dateFormatOptions)}
|
||||||
|
>
|
||||||
{relativeTime}
|
{relativeTime}
|
||||||
</time>
|
</time>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const RelativeTimestampWithIntl = injectIntl(RelativeTimestamp);
|
const RelativeTimestampWithIntl = injectIntl(RelativeTimestamp);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import ShortNumber from 'mastodon/components/short_number';
|
||||||
import Skeleton from 'mastodon/components/skeleton';
|
import Skeleton from 'mastodon/components/skeleton';
|
||||||
import Account from 'mastodon/containers/account_container';
|
import Account from 'mastodon/containers/account_container';
|
||||||
import { domain } from 'mastodon/initial_state';
|
import { domain } from 'mastodon/initial_state';
|
||||||
import { Image } from 'mastodon/components/image';
|
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -41,7 +41,7 @@ class ServerBanner extends React.PureComponent {
|
||||||
<FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
|
<FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
|
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
|
||||||
|
|
||||||
<div className='server-banner__description'>
|
<div className='server-banner__description'>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|
|
@ -1,15 +1,22 @@
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Blurhash } from './blurhash';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
import { Blurhash } from './blurhash';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
src: string;
|
src: string;
|
||||||
srcSet?: string;
|
srcSet?: string;
|
||||||
blurhash?: string;
|
blurhash?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Image: React.FC<Props> = ({ src, srcSet, blurhash, className }) => {
|
export const ServerHeroImage: React.FC<Props> = ({
|
||||||
|
src,
|
||||||
|
srcSet,
|
||||||
|
blurhash,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|
||||||
const handleLoad = useCallback(() => {
|
const handleLoad = useCallback(() => {
|
||||||
|
@ -17,7 +24,10 @@ export const Image: React.FC<Props> = ({ src, srcSet, blurhash, className }) =>
|
||||||
}, [setLoaded]);
|
}, [setLoaded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('image', { loaded }, className)} role='presentation'>
|
<div
|
||||||
|
className={classNames('image', { loaded }, className)}
|
||||||
|
role='presentation'
|
||||||
|
>
|
||||||
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
|
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
|
||||||
<img src={src} srcSet={srcSet} alt='' onLoad={handleLoad} />
|
<img src={src} srcSet={srcSet} alt='' onLoad={handleLoad} />
|
||||||
</div>
|
</div>
|
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
import { Avatar } from './avatar';
|
import { Avatar } from './avatar';
|
||||||
import { AvatarOverlay } from './avatar_overlay';
|
import { AvatarOverlay } from './avatar_overlay';
|
||||||
import { RelativeTimestamp } from './relative_timestamp';
|
import { RelativeTimestamp } from './relative_timestamp';
|
||||||
import DisplayName from './display_name';
|
import { DisplayName } from './display_name';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
import AttachmentList from './attachment_list';
|
import AttachmentList from './attachment_list';
|
||||||
|
@ -194,11 +194,12 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleOpenVideo = (options) => {
|
handleOpenVideo = (options) => {
|
||||||
const status = this._properStatus();
|
const status = this._properStatus();
|
||||||
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
|
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), status.get('language'), options);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenMedia = (media, index) => {
|
handleOpenMedia = (media, index) => {
|
||||||
this.props.onOpenMedia(this._properStatus().get('id'), media, index);
|
const status = this._properStatus();
|
||||||
|
this.props.onOpenMedia(status.get('id'), media, index, status.get('language'));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHotkeyOpenMedia = e => {
|
handleHotkeyOpenMedia = e => {
|
||||||
|
@ -208,10 +209,11 @@ class Status extends ImmutablePureComponent {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('media_attachments').size > 0) {
|
||||||
|
const lang = status.get('language');
|
||||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), { startTime: 0 });
|
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
|
||||||
} else {
|
} else {
|
||||||
onOpenMedia(status.get('id'), status.get('media_attachments'), 0);
|
onOpenMedia(status.get('id'), status.get('media_attachments'), 0, lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
link: string;
|
link: string;
|
||||||
};
|
}
|
||||||
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' className='verified-badge__mark' />
|
<Icon id='check' className='verified-badge__mark' />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { store } from '../store/configureStore';
|
import { store } from '../store';
|
||||||
import { hydrateStore } from '../actions/store';
|
import { hydrateStore } from '../actions/store';
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { Provider as ReduxProvider } from 'react-redux';
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
import { BrowserRouter, Route } from 'react-router-dom';
|
import { BrowserRouter, Route } from 'react-router-dom';
|
||||||
import { ScrollContext } from 'react-router-scroll-4';
|
import { ScrollContext } from 'react-router-scroll-4';
|
||||||
import { store } from 'mastodon/store/configureStore';
|
import { store } from 'mastodon/store';
|
||||||
import UI from 'mastodon/features/ui';
|
import UI from 'mastodon/features/ui';
|
||||||
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
|
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
|
||||||
import { hydrateStore } from 'mastodon/actions/store';
|
import { hydrateStore } from 'mastodon/actions/store';
|
||||||
|
|
|
@ -29,19 +29,20 @@ export default class MediaContainer extends PureComponent {
|
||||||
state = {
|
state = {
|
||||||
media: null,
|
media: null,
|
||||||
index: null,
|
index: null,
|
||||||
|
lang: null,
|
||||||
time: null,
|
time: null,
|
||||||
backgroundColor: null,
|
backgroundColor: null,
|
||||||
options: null,
|
options: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenMedia = (media, index) => {
|
handleOpenMedia = (media, index, lang) => {
|
||||||
document.body.classList.add('with-modals--active');
|
document.body.classList.add('with-modals--active');
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
||||||
|
|
||||||
this.setState({ media, index });
|
this.setState({ media, index, lang });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenVideo = (options) => {
|
handleOpenVideo = (lang, options) => {
|
||||||
const { components } = this.props;
|
const { components } = this.props;
|
||||||
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
||||||
const mediaList = fromJS(media);
|
const mediaList = fromJS(media);
|
||||||
|
@ -49,7 +50,7 @@ export default class MediaContainer extends PureComponent {
|
||||||
document.body.classList.add('with-modals--active');
|
document.body.classList.add('with-modals--active');
|
||||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
||||||
|
|
||||||
this.setState({ media: mediaList, options });
|
this.setState({ media: mediaList, lang, options });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCloseMedia = () => {
|
handleCloseMedia = () => {
|
||||||
|
@ -105,6 +106,7 @@ export default class MediaContainer extends PureComponent {
|
||||||
<MediaModal
|
<MediaModal
|
||||||
media={this.state.media}
|
media={this.state.media}
|
||||||
index={this.state.index || 0}
|
index={this.state.index || 0}
|
||||||
|
lang={this.state.lang}
|
||||||
currentTime={this.state.options?.startTime}
|
currentTime={this.state.options?.startTime}
|
||||||
autoPlay={this.state.options?.autoPlay}
|
autoPlay={this.state.options?.autoPlay}
|
||||||
volume={this.state.options?.defaultVolume}
|
volume={this.state.options?.defaultVolume}
|
||||||
|
|
|
@ -182,12 +182,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||||
dispatch(mentionCompose(account, router));
|
dispatch(mentionCompose(account, router));
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenMedia (statusId, media, index) {
|
onOpenMedia (statusId, media, index, lang) {
|
||||||
dispatch(openModal('MEDIA', { statusId, media, index }));
|
dispatch(openModal('MEDIA', { statusId, media, index, lang }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenVideo (statusId, media, options) {
|
onOpenVideo (statusId, media, lang, options) {
|
||||||
dispatch(openModal('VIDEO', { statusId, media, options }));
|
dispatch(openModal('VIDEO', { statusId, media, lang, options }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlock (status) {
|
onBlock (status) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Account from 'mastodon/containers/account_container';
|
||||||
import Skeleton from 'mastodon/components/skeleton';
|
import Skeleton from 'mastodon/components/skeleton';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Image } from 'mastodon/components/image';
|
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.about', defaultMessage: 'About' },
|
title: { id: 'column.about', defaultMessage: 'About' },
|
||||||
|
@ -114,7 +114,7 @@ class About extends React.PureComponent {
|
||||||
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
|
||||||
<div className='scrollable about'>
|
<div className='scrollable about'>
|
||||||
<div className='about__header'>
|
<div className='about__header'>
|
||||||
<Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
|
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />
|
||||||
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
|
<h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1>
|
||||||
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</a> }} /></p>
|
<p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</a> }} /></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,7 +22,7 @@ class InlineAlert extends React.PureComponent {
|
||||||
|
|
||||||
static TRANSITION_DELAY = 200;
|
static TRANSITION_DELAY = 200;
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!this.props.show && nextProps.show) {
|
if (!this.props.show && nextProps.show) {
|
||||||
this.setState({ mountMessage: true });
|
this.setState({ mountMessage: true });
|
||||||
} else if (this.props.show && !nextProps.show) {
|
} else if (this.props.show && !nextProps.show) {
|
||||||
|
@ -58,11 +58,11 @@ class AccountNote extends ImmutablePureComponent {
|
||||||
saved: false,
|
saved: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this._reset();
|
this._reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
const accountWillChange = !is(this.props.account, nextProps.account);
|
const accountWillChange = !is(this.props.account, nextProps.account);
|
||||||
const newState = {};
|
const newState = {};
|
||||||
|
|
||||||
|
|
|
@ -136,16 +136,17 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
handleOpenMedia = attachment => {
|
handleOpenMedia = attachment => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const statusId = attachment.getIn(['status', 'id']);
|
const statusId = attachment.getIn(['status', 'id']);
|
||||||
|
const lang = attachment.getIn(['status', 'language']);
|
||||||
|
|
||||||
if (attachment.get('type') === 'video') {
|
if (attachment.get('type') === 'video') {
|
||||||
dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } }));
|
dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } }));
|
||||||
} else if (attachment.get('type') === 'audio') {
|
} else if (attachment.get('type') === 'audio') {
|
||||||
dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } }));
|
dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } }));
|
||||||
} else {
|
} else {
|
||||||
const media = attachment.getIn(['status', 'media_attachments']);
|
const media = attachment.getIn(['status', 'media_attachments']);
|
||||||
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||||
|
|
||||||
dispatch(openModal('MEDIA', { media, index, statusId }));
|
dispatch(openModal('MEDIA', { media, index, statusId, lang }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { AvatarOverlay } from '../../../components/avatar_overlay';
|
import { AvatarOverlay } from '../../../components/avatar_overlay';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export default class MovedNote extends ImmutablePureComponent {
|
export default class MovedNote extends ImmutablePureComponent {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
||||||
import StatusList from '../../components/status_list';
|
import StatusList from '../../components/status_list';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
@ -14,7 +14,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines';
|
|
||||||
import LimitedAccountHint from './components/limited_account_hint';
|
import LimitedAccountHint from './components/limited_account_hint';
|
||||||
import { getAccountHidden } from 'mastodon/selectors';
|
import { getAccountHidden } from 'mastodon/selectors';
|
||||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||||
|
|
|
@ -136,7 +136,7 @@ class Audio extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
||||||
this.setState({ revealed: nextProps.visible });
|
this.setState({ revealed: nextProps.visible });
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchBlocks());
|
this.props.dispatch(fetchBlocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Bookmarks extends ImmutablePureComponent {
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchBookmarkedStatuses());
|
this.props.dispatch(fetchBookmarkedStatuses());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ class ModifierPickerMenu extends React.PureComponent {
|
||||||
this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
|
this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.active) {
|
if (nextProps.active) {
|
||||||
this.attachListeners();
|
this.attachListeners();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -212,7 +212,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
this.props.onChange(value);
|
this.props.onChange(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
const { intl: { formatMessage } } = this.props;
|
const { intl: { formatMessage } } = this.props;
|
||||||
|
|
||||||
this.options = [
|
this.options = [
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import { IconButton } from '../../../components/icon_button';
|
import { IconButton } from '../../../components/icon_button';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import AttachmentList from 'mastodon/components/attachment_list';
|
import AttachmentList from 'mastodon/components/attachment_list';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeGetAccount } from 'mastodon/selectors';
|
import { makeGetAccount } from 'mastodon/selectors';
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
import DisplayName from 'mastodon/components/display_name';
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Button from 'mastodon/components/button';
|
import Button from 'mastodon/components/button';
|
||||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchDomainBlocks());
|
this.props.dispatch(fetchDomainBlocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchFavouritedStatuses());
|
this.props.dispatch(fetchFavouritedStatuses());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,13 +31,13 @@ class Favourites extends ImmutablePureComponent {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
if (!this.props.accountIds) {
|
if (!this.props.accountIds) {
|
||||||
this.props.dispatch(fetchFavourites(this.props.params.statusId));
|
this.props.dispatch(fetchFavourites(this.props.params.statusId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||||
this.props.dispatch(fetchFavourites(nextProps.params.statusId));
|
this.props.dispatch(fetchFavourites(nextProps.params.statusId));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import { IconButton } from '../../../components/icon_button';
|
import { IconButton } from '../../../components/icon_button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
|
@ -39,7 +39,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchFollowRequests());
|
this.props.dispatch(fetchFollowRequests());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ class InteractionModal extends React.PureComponent {
|
||||||
<div className='interaction-modal__choices'>
|
<div className='interaction-modal__choices'>
|
||||||
<div className='interaction-modal__choices__choice'>
|
<div className='interaction-modal__choices__choice'>
|
||||||
<h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3>
|
<h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3>
|
||||||
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||||
{signupButton}
|
{signupButton}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { makeGetAccount } from '../../../selectors';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { makeGetAccount } from '../../../selectors';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import { IconButton } from '../../../components/icon_button';
|
import { IconButton } from '../../../components/icon_button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
|
import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
|
||||||
|
|
|
@ -76,7 +76,7 @@ class ListTimeline extends React.PureComponent {
|
||||||
this.disconnect = dispatch(connectListStream(id));
|
this.disconnect = dispatch(connectListStream(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const { id } = nextProps.params;
|
const { id } = nextProps.params;
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class Lists extends ImmutablePureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchLists());
|
this.props.dispatch(fetchLists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class Mutes extends ImmutablePureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchMutes());
|
this.props.dispatch(fetchMutes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
import DisplayName from 'mastodon/components/display_name';
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
|
@ -93,7 +93,7 @@ class Notifications extends React.PureComponent {
|
||||||
trackScroll: true,
|
trackScroll: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.props.dispatch(mountNotifications());
|
this.props.dispatch(mountNotifications());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
||||||
import { markAsPartial } from 'mastodon/actions/timelines';
|
import { markAsPartial } from 'mastodon/actions/timelines';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import Account from 'mastodon/containers/account_container';
|
import Account from 'mastodon/containers/account_container';
|
||||||
import EmptyAccount from 'mastodon/components/account';
|
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||||
import { FormattedMessage, FormattedHTMLMessage } from 'react-intl';
|
import { FormattedMessage, FormattedHTMLMessage } from 'react-intl';
|
||||||
import { makeGetAccount } from 'mastodon/selectors';
|
import { makeGetAccount } from 'mastodon/selectors';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
|
@ -31,6 +31,7 @@ class Follows extends React.PureComponent {
|
||||||
suggestions: ImmutablePropTypes.list,
|
suggestions: ImmutablePropTypes.list,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
@ -44,7 +45,7 @@ class Follows extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { onBack, isLoading, suggestions, account } = this.props;
|
const { onBack, isLoading, suggestions, account, multiColumn } = this.props;
|
||||||
|
|
||||||
let loadedContent;
|
let loadedContent;
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ class Follows extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton onClick={onBack} />
|
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} />
|
||||||
|
|
||||||
<div className='scrollable privacy-policy'>
|
<div className='scrollable privacy-policy'>
|
||||||
<div className='column-title'>
|
<div className='column-title'>
|
||||||
|
|
|
@ -40,6 +40,7 @@ class Onboarding extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -93,14 +94,14 @@ class Onboarding extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account } = this.props;
|
const { account, multiColumn } = this.props;
|
||||||
const { step, shareClicked } = this.state;
|
const { step, shareClicked } = this.state;
|
||||||
|
|
||||||
switch(step) {
|
switch(step) {
|
||||||
case 'follows':
|
case 'follows':
|
||||||
return <Follows onBack={this.handleBackClick} />;
|
return <Follows onBack={this.handleBackClick} multiColumn={multiColumn} />;
|
||||||
case 'share':
|
case 'share':
|
||||||
return <Share onBack={this.handleBackClick} />;
|
return <Share onBack={this.handleBackClick} multiColumn={multiColumn} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -114,7 +115,7 @@ class Onboarding extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='onboarding__steps'>
|
<div className='onboarding__steps'>
|
||||||
<Step onClick={this.handleProfileClick} href='/settings/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
|
<Step onClick={this.handleProfileClick} href='/settings/profile' completed={(!account.get('avatar').endsWith('missing.png')) || (account.get('display_name').length > 0 && account.get('note').length > 0)} icon='address-book-o' label={<FormattedMessage id='onboarding.steps.setup_profile.title' defaultMessage='Customize your profile' />} description={<FormattedMessage id='onboarding.steps.setup_profile.body' defaultMessage='Others are more likely to interact with you with a filled out profile.' />} />
|
||||||
<Step onClick={this.handleFollowClick} completed={(account.get('following_count') * 1) >= 7} icon='user-plus' label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Follow {count, plural, one {one person} other {# people}}' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage='You curate your own feed. Lets fill it with interesting people.' />} />
|
<Step onClick={this.handleFollowClick} completed={(account.get('following_count') * 1) >= 7} icon='user-plus' label={<FormattedMessage id='onboarding.steps.follow_people.title' defaultMessage='Follow {count, plural, one {one person} other {# people}}' values={{ count: 7 }} />} description={<FormattedMessage id='onboarding.steps.follow_people.body' defaultMessage="You curate your own feed. Let's fill it with interesting people." />} />
|
||||||
<Step onClick={this.handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' />} />
|
<Step onClick={this.handleComposeClick} completed={(account.get('statuses_count') * 1) >= 1} icon='pencil-square-o' label={<FormattedMessage id='onboarding.steps.publish_status.title' defaultMessage='Make your first post' />} description={<FormattedMessage id='onboarding.steps.publish_status.body' defaultMessage='Say hello to the world.' />} />
|
||||||
<Step onClick={this.handleShareClick} completed={shareClicked} icon='copy' label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
|
<Step onClick={this.handleShareClick} completed={shareClicked} icon='copy' label={<FormattedMessage id='onboarding.steps.share_profile.title' defaultMessage='Share your profile' />} description={<FormattedMessage id='onboarding.steps.share_profile.body' defaultMessage='Let your friends know how to find you on Mastodon!' />} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -140,17 +140,18 @@ class Share extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onBack: PropTypes.func,
|
onBack: PropTypes.func,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
intl: PropTypes.object,
|
intl: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { onBack, account, intl } = this.props;
|
const { onBack, account, multiColumn, intl } = this.props;
|
||||||
|
|
||||||
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
|
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton onClick={onBack} />
|
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} />
|
||||||
|
|
||||||
<div className='scrollable privacy-policy'>
|
<div className='scrollable privacy-policy'>
|
||||||
<div className='column-title'>
|
<div className='column-title'>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
import DisplayName from 'mastodon/components/display_name';
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
|
|
@ -29,7 +29,7 @@ class PinnedStatuses extends ImmutablePureComponent {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchPinnedStatuses());
|
this.props.dispatch(fetchPinnedStatuses());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,13 +31,13 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
if (!this.props.accountIds) {
|
if (!this.props.accountIds) {
|
||||||
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||||
this.props.dispatch(fetchReblogs(nextProps.params.statusId));
|
this.props.dispatch(fetchReblogs(nextProps.params.statusId));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusContent from 'mastodon/components/status_content';
|
import StatusContent from 'mastodon/components/status_content';
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
import DisplayName from 'mastodon/components/display_name';
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||||
import Option from './option';
|
import Option from './option';
|
||||||
import MediaAttachments from 'mastodon/components/media_attachments';
|
import MediaAttachments from 'mastodon/components/media_attachments';
|
||||||
|
|
|
@ -66,7 +66,7 @@ export default class Card extends React.PureComponent {
|
||||||
revealed: !this.props.sensitive,
|
revealed: !this.props.sensitive,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!Immutable.is(this.props.card, nextProps.card)) {
|
if (!Immutable.is(this.props.card, nextProps.card)) {
|
||||||
this.setState({ embedded: false, previewLoaded: false });
|
this.setState({ embedded: false, previewLoaded: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import StatusContent from '../../../components/status_content';
|
import StatusContent from '../../../components/status_content';
|
||||||
import MediaGallery from '../../../components/media_gallery';
|
import MediaGallery from '../../../components/media_gallery';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
|
@ -128,12 +128,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
dispatch(mentionCompose(account, router));
|
dispatch(mentionCompose(account, router));
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenMedia (media, index) {
|
onOpenMedia (media, index, lang) {
|
||||||
dispatch(openModal('MEDIA', { media, index }));
|
dispatch(openModal('MEDIA', { media, index, lang }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenVideo (media, options) {
|
onOpenVideo (media, lang, options) {
|
||||||
dispatch(openModal('VIDEO', { media, options }));
|
dispatch(openModal('VIDEO', { media, lang, options }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlock (status) {
|
onBlock (status) {
|
||||||
|
|
|
@ -207,7 +207,7 @@ class Status extends ImmutablePureComponent {
|
||||||
loadedStatusId: undefined,
|
loadedStatusId: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
this.props.dispatch(fetchStatus(this.props.params.statusId));
|
this.props.dispatch(fetchStatus(this.props.params.statusId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ class Status extends ImmutablePureComponent {
|
||||||
attachFullscreenListener(this.onFullScreenChange);
|
attachFullscreenListener(this.onFullScreenChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||||
this._scrolledIntoView = false;
|
this._scrolledIntoView = false;
|
||||||
this.props.dispatch(fetchStatus(nextProps.params.statusId));
|
this.props.dispatch(fetchStatus(nextProps.params.statusId));
|
||||||
|
@ -345,12 +345,12 @@ class Status extends ImmutablePureComponent {
|
||||||
this.props.dispatch(mentionCompose(account, router));
|
this.props.dispatch(mentionCompose(account, router));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenMedia = (media, index) => {
|
handleOpenMedia = (media, index, lang) => {
|
||||||
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
|
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenVideo = (media, options) => {
|
handleOpenVideo = (media, lang, options) => {
|
||||||
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
|
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options }));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHotkeyOpenMedia = e => {
|
handleHotkeyOpenMedia = e => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import Button from '../../../components/button';
|
||||||
import StatusContent from '../../../components/status_content';
|
import StatusContent from '../../../components/status_content';
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
import { RelativeTimestamp } from '../../../components/relative_timestamp';
|
import { RelativeTimestamp } from '../../../components/relative_timestamp';
|
||||||
import DisplayName from '../../../components/display_name';
|
import { DisplayName } from '../../../components/display_name';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import AttachmentList from 'mastodon/components/attachment_list';
|
import AttachmentList from 'mastodon/components/attachment_list';
|
||||||
|
|
|
@ -33,11 +33,11 @@ class Bundle extends React.PureComponent {
|
||||||
forceRender: false,
|
forceRender: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.load(this.props);
|
this.load(this.props);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.fetchComponent !== this.props.fetchComponent) {
|
if (nextProps.fetchComponent !== this.props.fetchComponent) {
|
||||||
this.load(nextProps);
|
this.load(nextProps);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
BookmarkedStatuses,
|
BookmarkedStatuses,
|
||||||
ListTimeline,
|
ListTimeline,
|
||||||
Directory,
|
Directory,
|
||||||
} from '../../ui/util/async-components';
|
} from '../util/async-components';
|
||||||
import ComposePanel from './compose_panel';
|
import ComposePanel from './compose_panel';
|
||||||
import NavigationPanel from './navigation_panel';
|
import NavigationPanel from './navigation_panel';
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
|
@ -76,7 +76,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
||||||
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
UNSAFE_componentWillUpdate(nextProps) {
|
||||||
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
|
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
|
||||||
this.node.removeEventListener('wheel', this.handleWheel);
|
this.node.removeEventListener('wheel', this.handleWheel);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose';
|
import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose';
|
||||||
import { getPointerPosition } from '../../video';
|
import Video, { getPointerPosition } from '../../video';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import Button from 'mastodon/components/button';
|
import Button from 'mastodon/components/button';
|
||||||
import Video from 'mastodon/features/video';
|
|
||||||
import Audio from 'mastodon/features/audio';
|
import Audio from 'mastodon/features/audio';
|
||||||
import Textarea from 'react-textarea-autosize';
|
import Textarea from 'react-textarea-autosize';
|
||||||
import UploadProgress from 'mastodon/features/compose/components/upload_progress';
|
import UploadProgress from 'mastodon/features/compose/components/upload_progress';
|
||||||
|
|
|
@ -51,13 +51,13 @@ class Header extends React.PureComponent {
|
||||||
|
|
||||||
if (registrationsOpen) {
|
if (registrationsOpen) {
|
||||||
signupButton = (
|
signupButton = (
|
||||||
<a href='/auth/sign_up' className='button button-tertiary'>
|
<a href='/auth/sign_up' className='button'>
|
||||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
signupButton = (
|
signupButton = (
|
||||||
<button className='button button-tertiary' onClick={openClosedRegistrationsModal}>
|
<button className='button' onClick={openClosedRegistrationsModal}>
|
||||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -65,8 +65,8 @@ class Header extends React.PureComponent {
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
|
||||||
{signupButton}
|
{signupButton}
|
||||||
|
<a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'mastodon/features/video';
|
import Video from 'mastodon/features/video';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
@ -21,15 +20,12 @@ const messages = defineMessages({
|
||||||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, { statusId }) => ({
|
|
||||||
language: state.getIn(['statuses', statusId, 'language']),
|
|
||||||
});
|
|
||||||
|
|
||||||
class MediaModal extends ImmutablePureComponent {
|
class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.list.isRequired,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
statusId: PropTypes.string,
|
statusId: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
@ -133,7 +129,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, language, statusId, intl, onClose } = this.props;
|
const { media, statusId, lang, intl, onClose } = this.props;
|
||||||
const { navigationHidden } = this.state;
|
const { navigationHidden } = this.state;
|
||||||
|
|
||||||
const index = this.getIndex();
|
const index = this.getIndex();
|
||||||
|
@ -153,7 +149,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
lang={language}
|
lang={lang}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
zoomButtonHidden={this.state.zoomButtonHidden}
|
zoomButtonHidden={this.state.zoomButtonHidden}
|
||||||
|
@ -176,7 +172,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
onCloseVideo={onClose}
|
onCloseVideo={onClose}
|
||||||
detailed
|
detailed
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
lang={language}
|
lang={lang}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -188,7 +184,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
height={height}
|
height={height}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
lang={language}
|
lang={lang}
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -256,4 +252,4 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal));
|
export default injectIntl(MediaModal);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Logo from 'mastodon/components/logo';
|
import { WordmarkLogo } from 'mastodon/components/logo';
|
||||||
import { timelinePreview, showTrends } from 'mastodon/initial_state';
|
import { timelinePreview, showTrends } from 'mastodon/initial_state';
|
||||||
import ColumnLink from './column_link';
|
import ColumnLink from './column_link';
|
||||||
import DisabledAccountBanner from './disabled_account_banner';
|
import DisabledAccountBanner from './disabled_account_banner';
|
||||||
|
@ -46,7 +46,7 @@ class NavigationPanel extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className='navigation-panel'>
|
<div className='navigation-panel'>
|
||||||
<div className='navigation-panel__logo'>
|
<div className='navigation-panel__logo'>
|
||||||
<Link to='/' className='column-link column-link--logo'><Logo /></Link>
|
<Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -16,13 +16,13 @@ const SignInBanner = () => {
|
||||||
|
|
||||||
if (registrationsOpen) {
|
if (registrationsOpen) {
|
||||||
signupButton = (
|
signupButton = (
|
||||||
<a href='/auth/sign_up' className='button button--block button-tertiary'>
|
<a href='/auth/sign_up' className='button button--block'>
|
||||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
signupButton = (
|
signupButton = (
|
||||||
<button className='button button--block button-tertiary' onClick={openClosedRegistrationsModal}>
|
<button className='button button--block' onClick={openClosedRegistrationsModal}>
|
||||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
@ -30,9 +30,9 @@ const SignInBanner = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='sign-in-banner'>
|
<div className='sign-in-banner'>
|
||||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||||
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
|
||||||
{signupButton}
|
{signupButton}
|
||||||
|
<a href='/auth/sign_in' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Motion from '../../ui/util/optional_motion';
|
import Motion from '../util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
|
|
@ -123,7 +123,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
mobile: PropTypes.bool,
|
mobile: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
UNSAFE_componentWillMount () {
|
||||||
if (this.props.mobile) {
|
if (this.props.mobile) {
|
||||||
document.body.classList.toggle('layout-single-column', true);
|
document.body.classList.toggle('layout-single-column', true);
|
||||||
document.body.classList.toggle('layout-multiple-columns', false);
|
document.body.classList.toggle('layout-multiple-columns', false);
|
||||||
|
|
|
@ -370,7 +370,7 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||||
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) {
|
||||||
this.setState({ revealed: nextProps.visible });
|
this.setState({ revealed: nextProps.visible });
|
||||||
}
|
}
|
||||||
|
@ -469,7 +469,7 @@ class Video extends React.PureComponent {
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
this.video.pause();
|
this.video.pause();
|
||||||
|
|
||||||
this.props.onOpenVideo({
|
this.props.onOpenVideo(this.props.lang, {
|
||||||
startTime: this.video.currentTime,
|
startTime: this.video.currentTime,
|
||||||
autoPlay: !this.state.paused,
|
autoPlay: !this.state.paused,
|
||||||
defaultVolume: this.state.volume,
|
defaultVolume: this.state.volume,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
|
|
||||||
import { forceSingleColumn } from './initial_state';
|
import { forceSingleColumn } from './initial_state';
|
||||||
|
|
||||||
const LAYOUT_BREAKPOINT = 630;
|
const LAYOUT_BREAKPOINT = 630;
|
||||||
|
@ -16,10 +17,6 @@ export const layoutFromWindow = (): LayoutType => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-expect-error
|
|
||||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && window.MSStream != null;
|
|
||||||
|
|
||||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||||
|
|
||||||
let userTouching = false;
|
let userTouching = false;
|
||||||
|
@ -33,5 +30,3 @@ const touchListener = () => {
|
||||||
window.addEventListener('touchstart', touchListener, listenerOptions);
|
window.addEventListener('touchstart', touchListener, listenerOptions);
|
||||||
|
|
||||||
export const isUserTouching = () => userTouching;
|
export const isUserTouching = () => userTouching;
|
||||||
|
|
||||||
export const isIOS = () => iOS;
|
|
||||||
|
|
|
@ -356,7 +356,7 @@
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
"defaultMessage": "You need to sign in to access this resource.",
|
"defaultMessage": "You need to login to access this resource.",
|
||||||
"id": "not_signed_in_indicator.not_signed_in"
|
"id": "not_signed_in_indicator.not_signed_in"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -2623,7 +2623,7 @@
|
||||||
"id": "interaction_modal.on_this_server"
|
"id": "interaction_modal.on_this_server"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Sign in",
|
"defaultMessage": "Login",
|
||||||
"id": "sign_in_banner.sign_in"
|
"id": "sign_in_banner.sign_in"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -3236,7 +3236,7 @@
|
||||||
"id": "onboarding.steps.follow_people.title"
|
"id": "onboarding.steps.follow_people.title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "You curate your own feed. Lets fill it with interesting people.",
|
"defaultMessage": "You curate your own feed. Let's fill it with interesting people.",
|
||||||
"id": "onboarding.steps.follow_people.body"
|
"id": "onboarding.steps.follow_people.body"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4175,7 +4175,7 @@
|
||||||
"id": "sign_in_banner.create_account"
|
"id": "sign_in_banner.create_account"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Sign in",
|
"defaultMessage": "Login",
|
||||||
"id": "sign_in_banner.sign_in"
|
"id": "sign_in_banner.sign_in"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -4374,11 +4374,11 @@
|
||||||
"id": "sign_in_banner.create_account"
|
"id": "sign_in_banner.create_account"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
"defaultMessage": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||||
"id": "sign_in_banner.text"
|
"id": "sign_in_banner.text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Sign in",
|
"defaultMessage": "Login",
|
||||||
"id": "sign_in_banner.sign_in"
|
"id": "sign_in_banner.sign_in"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -391,7 +391,7 @@
|
||||||
"navigation_bar.public_timeline": "Federated timeline",
|
"navigation_bar.public_timeline": "Federated timeline",
|
||||||
"navigation_bar.search": "Search",
|
"navigation_bar.search": "Search",
|
||||||
"navigation_bar.security": "Security",
|
"navigation_bar.security": "Security",
|
||||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
"not_signed_in_indicator.not_signed_in": "You need to login to access this resource.",
|
||||||
"notification.admin.report": "{name} reported {target}",
|
"notification.admin.report": "{name} reported {target}",
|
||||||
"notification.admin.sign_up": "{name} signed up",
|
"notification.admin.sign_up": "{name} signed up",
|
||||||
"notification.favourite": "{name} favourited your post",
|
"notification.favourite": "{name} favourited your post",
|
||||||
|
@ -573,8 +573,8 @@
|
||||||
"server_banner.learn_more": "Learn more",
|
"server_banner.learn_more": "Learn more",
|
||||||
"server_banner.server_stats": "Server stats:",
|
"server_banner.server_stats": "Server stats:",
|
||||||
"sign_in_banner.create_account": "Create account",
|
"sign_in_banner.create_account": "Create account",
|
||||||
"sign_in_banner.sign_in": "Sign in",
|
"sign_in_banner.sign_in": "Login",
|
||||||
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
"sign_in_banner.text": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||||
"status.admin_account": "Open moderation interface for @{name}",
|
"status.admin_account": "Open moderation interface for @{name}",
|
||||||
"status.admin_domain": "Open moderation interface for {domain}",
|
"status.admin_domain": "Open moderation interface for {domain}",
|
||||||
"status.admin_status": "Open this post in the moderation interface",
|
"status.admin_status": "Open this post in the moderation interface",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/*eslint no-nested-ternary: "off"*/
|
/*eslint no-nested-ternary: "off"*/
|
||||||
/*eslint quotes: "off"*/
|
/*eslint quotes: "off"*/
|
||||||
|
|
||||||
export default [{
|
const rules = [{
|
||||||
locale: "co",
|
locale: "co",
|
||||||
pluralRuleFunction: function (e, a) {
|
pluralRuleFunction: function (e, a) {
|
||||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||||
|
@ -106,3 +106,5 @@ export default [{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
export default rules;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/*eslint no-nested-ternary: "off"*/
|
/*eslint no-nested-ternary: "off"*/
|
||||||
/*eslint quotes: "off"*/
|
/*eslint quotes: "off"*/
|
||||||
|
|
||||||
export default [{
|
const rules = [{
|
||||||
locale: "oc",
|
locale: "oc",
|
||||||
pluralRuleFunction: function (e, a) {
|
pluralRuleFunction: function (e, a) {
|
||||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||||
|
@ -106,3 +106,5 @@ export default [{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
export default rules;
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
/*eslint no-nested-ternary: "off"*/
|
/*eslint no-nested-ternary: "off"*/
|
||||||
/*eslint quotes: "off"*/
|
/*eslint quotes: "off"*/
|
||||||
/*eslint comma-dangle: "off"*/
|
/*eslint comma-dangle: "off"*/
|
||||||
/*eslint semi: "off"*/
|
|
||||||
|
|
||||||
export default [
|
const rules = [
|
||||||
{
|
{
|
||||||
locale: "sa",
|
locale: "sa",
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -94,4 +93,6 @@ export default [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
|
export default rules;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { setupBrowserNotifications } from 'mastodon/actions/notifications';
|
import { setupBrowserNotifications } from 'mastodon/actions/notifications';
|
||||||
import Mastodon from 'mastodon/containers/mastodon';
|
import Mastodon from 'mastodon/containers/mastodon';
|
||||||
import { store } from 'mastodon/store/configureStore';
|
import { store } from 'mastodon/store';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
import ready from 'mastodon/ready';
|
import ready from 'mastodon/ready';
|
||||||
import * as perf from 'mastodon/performance';
|
import * as perf from 'mastodon/performance';
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { showAlertForError } from '../actions/alerts';
|
|
||||||
|
|
||||||
const defaultFailSuffix = 'FAIL';
|
|
||||||
|
|
||||||
export default function errorsMiddleware() {
|
|
||||||
return ({ dispatch }) => next => action => {
|
|
||||||
if (action.type && !action.skipAlert) {
|
|
||||||
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
|
|
||||||
|
|
||||||
if (action.type.match(isFail)) {
|
|
||||||
dispatch(showAlertForError(action.error, action.skipNotFound));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(action);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
|
||||||
|
|
||||||
const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED'];
|
|
||||||
|
|
||||||
export default function loadingBarMiddleware(config = {}) {
|
|
||||||
const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes;
|
|
||||||
|
|
||||||
return ({ dispatch }) => next => (action) => {
|
|
||||||
if (action.type && !action.skipLoading) {
|
|
||||||
const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes;
|
|
||||||
|
|
||||||
const isPending = new RegExp(`${PENDING}$`, 'g');
|
|
||||||
const isFulfilled = new RegExp(`${FULFILLED}$`, 'g');
|
|
||||||
const isRejected = new RegExp(`${REJECTED}$`, 'g');
|
|
||||||
|
|
||||||
if (action.type.match(isPending)) {
|
|
||||||
dispatch(showLoading());
|
|
||||||
} else if (action.type.match(isFulfilled) || action.type.match(isRejected)) {
|
|
||||||
dispatch(hideLoading());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return next(action);
|
|
||||||
};
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user