[build] upgrade eslint to 9.37.0 (#88)
Co-authored-by: tobi <tobi.smethurst@protonmail.com> Reviewed-on: https://codeberg.org/superseriousbusiness/masto-fe-standalone/pulls/88 Co-authored-by: Zoë Bijl <moiety@noreply.codeberg.org> Co-committed-by: Zoë Bijl <moiety@noreply.codeberg.org>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import renderer from 'react-test-renderer';
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import AutosuggestEmoji from '../autosuggest_emoji';
|
||||
import AutosuggestEmoji from "../autosuggest_emoji";
|
||||
|
||||
describe('<AutosuggestEmoji />', () => {
|
||||
it('renders native emoji', () => {
|
||||
describe("<AutosuggestEmoji />", () => {
|
||||
it("renders native emoji", () => {
|
||||
const emoji = {
|
||||
native: '💙',
|
||||
colons: ':foobar:',
|
||||
native: "💙",
|
||||
colons: ":foobar:",
|
||||
};
|
||||
const component = renderer.create(<AutosuggestEmoji emoji={emoji} />);
|
||||
const tree = component.toJSON();
|
||||
@@ -14,12 +14,12 @@ describe('<AutosuggestEmoji />', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders emoji with custom url', () => {
|
||||
it("renders emoji with custom url", () => {
|
||||
const emoji = {
|
||||
custom: true,
|
||||
imageUrl: 'http://example.com/emoji.png',
|
||||
native: 'foobar',
|
||||
colons: ':foobar:',
|
||||
imageUrl: "http://example.com/emoji.png",
|
||||
native: "foobar",
|
||||
colons: ":foobar:",
|
||||
};
|
||||
const component = renderer.create(<AutosuggestEmoji emoji={emoji} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { fromJS } from "immutable";
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import { Avatar } from '../avatar';
|
||||
import { Avatar } from "../avatar";
|
||||
|
||||
describe('<Avatar />', () => {
|
||||
describe("<Avatar />", () => {
|
||||
const account = fromJS({
|
||||
username: 'alice',
|
||||
acct: 'alice',
|
||||
display_name: 'Alice',
|
||||
avatar: '/animated/alice.gif',
|
||||
avatar_static: '/static/alice.jpg',
|
||||
username: "alice",
|
||||
acct: "alice",
|
||||
display_name: "Alice",
|
||||
avatar: "/animated/alice.gif",
|
||||
avatar_static: "/static/alice.jpg",
|
||||
});
|
||||
|
||||
const size = 100;
|
||||
|
||||
describe('Autoplay', () => {
|
||||
it('renders a animated avatar', () => {
|
||||
describe("Autoplay", () => {
|
||||
it("renders a animated avatar", () => {
|
||||
const component = renderer.create(<Avatar account={account} animate size={size} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
@@ -24,8 +24,8 @@ describe('<Avatar />', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Still', () => {
|
||||
it('renders a still avatar', () => {
|
||||
describe("Still", () => {
|
||||
it("renders a still avatar", () => {
|
||||
const component = renderer.create(<Avatar account={account} size={size} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { fromJS } from "immutable";
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import { AvatarOverlay } from '../avatar_overlay';
|
||||
import { AvatarOverlay } from "../avatar_overlay";
|
||||
|
||||
describe('<AvatarOverlay', () => {
|
||||
describe("<AvatarOverlay", () => {
|
||||
const account = fromJS({
|
||||
username: 'alice',
|
||||
acct: 'alice',
|
||||
display_name: 'Alice',
|
||||
avatar: '/animated/alice.gif',
|
||||
avatar_static: '/static/alice.jpg',
|
||||
username: "alice",
|
||||
acct: "alice",
|
||||
display_name: "Alice",
|
||||
avatar: "/animated/alice.gif",
|
||||
avatar_static: "/static/alice.jpg",
|
||||
});
|
||||
|
||||
const friend = fromJS({
|
||||
username: 'eve',
|
||||
acct: 'eve@blackhat.lair',
|
||||
display_name: 'Evelyn',
|
||||
avatar: '/animated/eve.gif',
|
||||
avatar_static: '/static/eve.jpg',
|
||||
username: "eve",
|
||||
acct: "eve@blackhat.lair",
|
||||
display_name: "Evelyn",
|
||||
avatar: "/animated/eve.gif",
|
||||
avatar_static: "/static/eve.jpg",
|
||||
});
|
||||
|
||||
it('renders a overlay avatar', () => {
|
||||
it("renders a overlay avatar", () => {
|
||||
const component = renderer.create(<AvatarOverlay account={account} friend={friend} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
import { render, fireEvent, screen } from '@testing-library/react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { render, fireEvent, screen } from "@testing-library/react";
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import Button from '../button';
|
||||
import Button from "../button";
|
||||
|
||||
describe('<Button />', () => {
|
||||
it('renders a button element', () => {
|
||||
describe("<Button />", () => {
|
||||
it("renders a button element", () => {
|
||||
const component = renderer.create(<Button />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the given text', () => {
|
||||
const text = 'foo';
|
||||
it("renders the given text", () => {
|
||||
const text = "foo";
|
||||
const component = renderer.create(<Button text={text} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles click events using the given handler', () => {
|
||||
it("handles click events using the given handler", () => {
|
||||
const handler = jest.fn();
|
||||
render(<Button onClick={handler}>button</Button>);
|
||||
fireEvent.click(screen.getByText('button'));
|
||||
fireEvent.click(screen.getByText("button"));
|
||||
|
||||
expect(handler.mock.calls.length).toEqual(1);
|
||||
expect(handler.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not handle click events if props.disabled given', () => {
|
||||
it("does not handle click events if props.disabled given", () => {
|
||||
const handler = jest.fn();
|
||||
render(<Button onClick={handler} disabled>button</Button>);
|
||||
fireEvent.click(screen.getByText('button'));
|
||||
fireEvent.click(screen.getByText("button"));
|
||||
|
||||
expect(handler.mock.calls.length).toEqual(0);
|
||||
expect(handler.mock.calls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('renders a disabled attribute if props.disabled given', () => {
|
||||
it("renders a disabled attribute if props.disabled given", () => {
|
||||
const component = renderer.create(<Button disabled />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the children', () => {
|
||||
it("renders the children", () => {
|
||||
const children = <p>children</p>;
|
||||
const component = renderer.create(<Button>{children}</Button>);
|
||||
const tree = component.toJSON();
|
||||
@@ -50,8 +50,8 @@ describe('<Button />', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the props.text instead of children', () => {
|
||||
const text = 'foo';
|
||||
it("renders the props.text instead of children", () => {
|
||||
const text = "foo";
|
||||
const children = <p>children</p>;
|
||||
const component = renderer.create(<Button text={text}>{children}</Button>);
|
||||
const tree = component.toJSON();
|
||||
@@ -59,14 +59,14 @@ describe('<Button />', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders class="button--block" if props.block given', () => {
|
||||
it("renders class=\"button--block\" if props.block given", () => {
|
||||
const component = renderer.create(<Button block />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('adds class "button-secondary" if props.secondary given', () => {
|
||||
it("adds class \"button-secondary\" if props.secondary given", () => {
|
||||
const component = renderer.create(<Button secondary />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { fromJS } from "immutable";
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
import renderer from "react-test-renderer";
|
||||
|
||||
import { DisplayName } from '../display_name';
|
||||
import { DisplayName } from "../display_name";
|
||||
|
||||
describe('<DisplayName />', () => {
|
||||
it('renders display name + account name', () => {
|
||||
describe("<DisplayName />", () => {
|
||||
it("renders display name + account name", () => {
|
||||
const account = fromJS({
|
||||
username: 'bar',
|
||||
acct: 'bar@baz',
|
||||
display_name_html: '<p>Foo</p>',
|
||||
username: "bar",
|
||||
acct: "bar@baz",
|
||||
display_name_html: "<p>Foo</p>",
|
||||
});
|
||||
const component = renderer.create(<DisplayName account={account} />);
|
||||
const tree = component.toJSON();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { fromJS } from 'immutable';
|
||||
import { fromJS } from "immutable";
|
||||
|
||||
import type { StatusLike } from '../hashtag_bar';
|
||||
import { computeHashtagBarForStatus } from '../hashtag_bar';
|
||||
import { type StatusLike } from "../hashtag_bar";
|
||||
import { computeHashtagBarForStatus } from "../hashtag_bar";
|
||||
|
||||
function createStatus(
|
||||
content: string,
|
||||
@@ -12,43 +12,43 @@ function createStatus(
|
||||
return fromJS({
|
||||
tags: hashtags.map((name) => ({ name })),
|
||||
contentHtml: content,
|
||||
media_attachments: hasMedia ? ['fakeMedia'] : [],
|
||||
media_attachments: hasMedia ? ["fakeMedia"] : [],
|
||||
spoiler_text: spoilerText,
|
||||
}) as unknown as StatusLike; // need to force the type here, as it is not properly defined
|
||||
}
|
||||
|
||||
describe('computeHashtagBarForStatus', () => {
|
||||
it('does nothing when there are no tags', () => {
|
||||
const status = createStatus('<p>Simple text</p>', []);
|
||||
describe("computeHashtagBarForStatus", () => {
|
||||
it("does nothing when there are no tags", () => {
|
||||
const status = createStatus("<p>Simple text</p>", []);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Simple text</p>"`,
|
||||
"\"<p>Simple text</p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('displays out of band hashtags in the bar', () => {
|
||||
it("displays out of band hashtags in the bar", () => {
|
||||
const status = createStatus(
|
||||
'<p>Simple text <a href="test">#hashtag</a></p>',
|
||||
['hashtag', 'test'],
|
||||
"<p>Simple text <a href=\"test\">#hashtag</a></p>",
|
||||
["hashtag", "test"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual(['test']);
|
||||
expect(hashtagsInBar).toEqual(["test"]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Simple text <a href="test">#hashtag</a></p>"`,
|
||||
"\"<p>Simple text <a href=\"test\">#hashtag</a></p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('does not truncate the contents when the last child is a text node', () => {
|
||||
it("does not truncate the contents when the last child is a text node", () => {
|
||||
const status = createStatus(
|
||||
'this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text',
|
||||
['test'],
|
||||
"this is a #<a class=\"zrl\" href=\"https://example.com/search?tag=test\">test</a>. Some more text",
|
||||
["test"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
@@ -56,29 +56,29 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text"`,
|
||||
"\"this is a #<a class=\"zrl\" href=\"https://example.com/search?tag=test\">test</a>. Some more text\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('extract tags from the last line', () => {
|
||||
it("extract tags from the last line", () => {
|
||||
const status = createStatus(
|
||||
'<p>Simple text</p><p><a href="test">#hashtag</a></p>',
|
||||
['hashtag'],
|
||||
"<p>Simple text</p><p><a href=\"test\">#hashtag</a></p>",
|
||||
["hashtag"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual(['hashtag']);
|
||||
expect(hashtagsInBar).toEqual(["hashtag"]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Simple text</p>"`,
|
||||
"\"<p>Simple text</p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('does not include tags from content', () => {
|
||||
it("does not include tags from content", () => {
|
||||
const status = createStatus(
|
||||
'<p>Simple text with a <a href="test">#hashtag</a></p><p><a href="test">#hashtag</a></p>',
|
||||
['hashtag'],
|
||||
"<p>Simple text with a <a href=\"test\">#hashtag</a></p><p><a href=\"test\">#hashtag</a></p>",
|
||||
["hashtag"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
@@ -86,14 +86,14 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Simple text with a <a href="test">#hashtag</a></p>"`,
|
||||
"\"<p>Simple text with a <a href=\"test\">#hashtag</a></p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('works with one line status and hashtags', () => {
|
||||
it("works with one line status and hashtags", () => {
|
||||
const status = createStatus(
|
||||
'<p><a href="test">#test</a>. And another <a href="test">#hashtag</a></p>',
|
||||
['hashtag', 'test'],
|
||||
"<p><a href=\"test\">#test</a>. And another <a href=\"test\">#hashtag</a></p>",
|
||||
["hashtag", "test"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
@@ -101,44 +101,44 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p><a href="test">#test</a>. And another <a href="test">#hashtag</a></p>"`,
|
||||
"\"<p><a href=\"test\">#test</a>. And another <a href=\"test\">#hashtag</a></p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('de-duplicate accentuated characters with case differences', () => {
|
||||
it("de-duplicate accentuated characters with case differences", () => {
|
||||
const status = createStatus(
|
||||
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
|
||||
['éaa'],
|
||||
"<p>Text</p><p><a href=\"test\">#éaa</a> <a href=\"test\">#Éaa</a></p>",
|
||||
["éaa"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual(['Éaa']);
|
||||
expect(hashtagsInBar).toEqual(["Éaa"]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Text</p>"`,
|
||||
"\"<p>Text</p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('handles server-side normalized tags with accentuated characters', () => {
|
||||
it("handles server-side normalized tags with accentuated characters", () => {
|
||||
const status = createStatus(
|
||||
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
|
||||
['eaa'], // The server may normalize the hashtags in the `tags` attribute
|
||||
"<p>Text</p><p><a href=\"test\">#éaa</a> <a href=\"test\">#Éaa</a></p>",
|
||||
["eaa"], // The server may normalize the hashtags in the `tags` attribute
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual(['Éaa']);
|
||||
expect(hashtagsInBar).toEqual(["Éaa"]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Text</p>"`,
|
||||
"\"<p>Text</p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('does not display in bar a hashtag in content with a case difference', () => {
|
||||
it("does not display in bar a hashtag in content with a case difference", () => {
|
||||
const status = createStatus(
|
||||
'<p>Text <a href="test">#Éaa</a></p><p><a href="test">#éaa</a></p>',
|
||||
['éaa'],
|
||||
"<p>Text <a href=\"test\">#Éaa</a></p><p><a href=\"test\">#éaa</a></p>",
|
||||
["éaa"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
@@ -146,14 +146,14 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>Text <a href="test">#Éaa</a></p>"`,
|
||||
"\"<p>Text <a href=\"test\">#Éaa</a></p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('does not modify a status with a line of hashtags only', () => {
|
||||
it("does not modify a status with a line of hashtags only", () => {
|
||||
const status = createStatus(
|
||||
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
|
||||
['test', 'hashtag'],
|
||||
"<p><a href=\"test\">#test</a> <a href=\"test\">#hashtag</a></p>",
|
||||
["test", "hashtag"],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
@@ -161,14 +161,14 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p><a href="test">#test</a> <a href="test">#hashtag</a></p>"`,
|
||||
"\"<p><a href=\"test\">#test</a> <a href=\"test\">#hashtag</a></p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('puts the hashtags in the bar if a status content has hashtags in the only line and has a media', () => {
|
||||
it("puts the hashtags in the bar if a status content has hashtags in the only line and has a media", () => {
|
||||
const status = createStatus(
|
||||
'<p>This is my content! <a href="test">#hashtag</a></p>',
|
||||
['hashtag'],
|
||||
"<p>This is my content! <a href=\"test\">#hashtag</a></p>",
|
||||
["hashtag"],
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -177,30 +177,30 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p>This is my content! <a href="test">#hashtag</a></p>"`,
|
||||
"\"<p>This is my content! <a href=\"test\">#hashtag</a></p>\"",
|
||||
);
|
||||
});
|
||||
|
||||
it('puts the hashtags in the bar if a status content is only hashtags and has a media', () => {
|
||||
it("puts the hashtags in the bar if a status content is only hashtags and has a media", () => {
|
||||
const status = createStatus(
|
||||
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
|
||||
['test', 'hashtag'],
|
||||
"<p><a href=\"test\">#test</a> <a href=\"test\">#hashtag</a></p>",
|
||||
["test", "hashtag"],
|
||||
true,
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual(['test', 'hashtag']);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(`""`);
|
||||
expect(hashtagsInBar).toEqual(["test", "hashtag"]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot("\"\"");
|
||||
});
|
||||
|
||||
it('does not use the hashtag bar if the status content is only hashtags, has a CW and a media', () => {
|
||||
it("does not use the hashtag bar if the status content is only hashtags, has a CW and a media", () => {
|
||||
const status = createStatus(
|
||||
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
|
||||
['test', 'hashtag'],
|
||||
"<p><a href=\"test\">#test</a> <a href=\"test\">#hashtag</a></p>",
|
||||
["test", "hashtag"],
|
||||
true,
|
||||
'My CW text',
|
||||
"My CW text",
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
@@ -208,7 +208,7 @@ describe('computeHashtagBarForStatus', () => {
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"<p><a href="test">#test</a> <a href="test">#hashtag</a></p>"`,
|
||||
"\"<p><a href=\"test\">#test</a> <a href=\"test\">#hashtag</a></p>\"",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||
import { EmptyAccount } from "mastodon/components/empty_account";
|
||||
import { ShortNumber } from "mastodon/components/short_number";
|
||||
import { VerifiedBadge } from "mastodon/components/verified_badge";
|
||||
|
||||
import { me } from '../initial_state';
|
||||
import { me } from "../initial_state";
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import Button from './button';
|
||||
import { FollowersCounter } from './counters';
|
||||
import { DisplayName } from './display_name';
|
||||
import { IconButton } from './icon_button';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
import { Avatar } from "./avatar";
|
||||
import Button from "./button";
|
||||
import { FollowersCounter } from "./counters";
|
||||
import { DisplayName } from "./display_name";
|
||||
import { IconButton } from "./icon_button";
|
||||
import { RelativeTimestamp } from "./relative_timestamp";
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
|
||||
unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
|
||||
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
|
||||
block: { id: 'account.block_short', defaultMessage: 'Block' },
|
||||
follow: { id: "account.follow", defaultMessage: "Follow" },
|
||||
unfollow: { id: "account.unfollow", defaultMessage: "Unfollow" },
|
||||
cancel_follow_request: { id: "account.cancel_follow_request", defaultMessage: "Withdraw follow request" },
|
||||
unblock: { id: "account.unblock_short", defaultMessage: "Unblock" },
|
||||
unmute: { id: "account.unmute_short", defaultMessage: "Unmute" },
|
||||
mute_notifications: { id: "account.mute_notifications_short", defaultMessage: "Mute notifications" },
|
||||
unmute_notifications: { id: "account.unmute_notifications_short", defaultMessage: "Unmute notifications" },
|
||||
mute: { id: "account.mute_short", defaultMessage: "Mute" },
|
||||
block: { id: "account.block_short", defaultMessage: "Block" },
|
||||
});
|
||||
|
||||
class Account extends ImmutablePureComponent {
|
||||
@@ -90,8 +90,8 @@ class Account extends ImmutablePureComponent {
|
||||
if (hidden) {
|
||||
return (
|
||||
<>
|
||||
{account.get('display_name')}
|
||||
{account.get('username')}
|
||||
{account.get("display_name")}
|
||||
{account.get("username")}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -100,11 +100,11 @@ class Account extends ImmutablePureComponent {
|
||||
|
||||
if (actionIcon && onActionClick) {
|
||||
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
|
||||
} else if (!actionIcon && account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
const requested = account.getIn(['relationship', 'requested']);
|
||||
const blocking = account.getIn(['relationship', 'blocking']);
|
||||
const muting = account.getIn(['relationship', 'muting']);
|
||||
} else if (!actionIcon && account.get("id") !== me && account.get("relationship", null) !== null) {
|
||||
const following = account.getIn(["relationship", "following"]);
|
||||
const requested = account.getIn(["relationship", "requested"]);
|
||||
const blocking = account.getIn(["relationship", "blocking"]);
|
||||
const muting = account.getIn(["relationship", "muting"]);
|
||||
|
||||
if (requested) {
|
||||
buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={this.handleFollow} />;
|
||||
@@ -113,7 +113,7 @@ class Account extends ImmutablePureComponent {
|
||||
} else if (muting) {
|
||||
let hidingNotificationsButton;
|
||||
|
||||
if (account.getIn(['relationship', 'muting_notifications'])) {
|
||||
if (account.getIn(["relationship", "muting_notifications"])) {
|
||||
hidingNotificationsButton = <Button text={intl.formatMessage(messages.unmute_notifications)} onClick={this.handleUnmuteNotifications} />;
|
||||
} else {
|
||||
hidingNotificationsButton = <Button text={intl.formatMessage(messages.mute_notifications)} onClick={this.handleMuteNotifications} />;
|
||||
@@ -125,33 +125,33 @@ class Account extends ImmutablePureComponent {
|
||||
{hidingNotificationsButton}
|
||||
</>
|
||||
);
|
||||
} else if (defaultAction === 'mute') {
|
||||
} else if (defaultAction === "mute") {
|
||||
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
||||
} else if (defaultAction === 'block') {
|
||||
} else if (defaultAction === "block") {
|
||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
||||
} else if (!account.get('moved') || following) {
|
||||
} else if (!account.get("moved") || following) {
|
||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
||||
}
|
||||
}
|
||||
|
||||
let muteTimeRemaining;
|
||||
|
||||
if (account.get('mute_expires_at')) {
|
||||
muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
|
||||
if (account.get("mute_expires_at")) {
|
||||
muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get("mute_expires_at")} futureDate /></>;
|
||||
}
|
||||
|
||||
let verification;
|
||||
|
||||
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
|
||||
const firstVerifiedField = account.get("fields").find(item => !!item.get("verified_at"));
|
||||
|
||||
if (firstVerifiedField) {
|
||||
verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
|
||||
verification = <VerifiedBadge link={firstVerifiedField.get("value")} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className={classNames("account", { "account--minimal": minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`}>
|
||||
<Link key={account.get("id")} className='account__display-name' title={account.get("acct")} to={`/@${account.get("acct")}`}>
|
||||
<div className='account__avatar-wrapper'>
|
||||
<Avatar account={account} size={size} />
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@ class Account extends ImmutablePureComponent {
|
||||
<DisplayName account={account} />
|
||||
{!minimal && (
|
||||
<div className='account__details'>
|
||||
<ShortNumber value={account.get('followers_count')} renderer={FollowersCounter} /> {verification} {muteTimeRemaining}
|
||||
<ShortNumber value={account.get("followers_count")} renderer={FollowersCounter} /> {verification} {muteTimeRemaining}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -173,10 +173,10 @@ class Account extends ImmutablePureComponent {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{withBio && (account.get('note').length > 0 ? (
|
||||
{withBio && (account.get("note").length > 0 ? (
|
||||
<div
|
||||
className='account__note translate'
|
||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||
dangerouslySetInnerHTML={{ __html: account.get("note_emojified") }}
|
||||
/>
|
||||
) : (
|
||||
<div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
import { FormattedNumber } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
import { Sparklines, SparklinesCurve } from "react-sparklines";
|
||||
|
||||
import api from 'mastodon/api';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import api from "mastodon/api";
|
||||
import { Skeleton } from "mastodon/components/skeleton";
|
||||
|
||||
const percIncrease = (a, b) => {
|
||||
let percent;
|
||||
@@ -48,7 +48,7 @@ export default class Counter extends PureComponent {
|
||||
componentDidMount () {
|
||||
const { measure, start_at, end_at, params } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
|
||||
api().post("/api/v1/admin/measures", { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
@@ -78,7 +78,7 @@ export default class Counter extends PureComponent {
|
||||
content = (
|
||||
<>
|
||||
<span className='sparkline__value__total'>{measure.human_value || <FormattedNumber value={measure.total} />}</span>
|
||||
{measure.previous_total && (<span className={classNames('sparkline__value__change', { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && '+'}<FormattedNumber value={percentChange} style='percent' /></span>)}
|
||||
{measure.previous_total && (<span className={classNames("sparkline__value__change", { positive: percentChange > 0, negative: percentChange < 0 })}>{percentChange > 0 && "+"}<FormattedNumber value={percentChange} style='percent' /></span>)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
import { FormattedNumber } from "react-intl";
|
||||
|
||||
import api from 'mastodon/api';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import { roundTo10 } from 'mastodon/utils/numbers';
|
||||
import api from "mastodon/api";
|
||||
import { Skeleton } from "mastodon/components/skeleton";
|
||||
import { roundTo10 } from "mastodon/utils/numbers";
|
||||
|
||||
export default class Dimension extends PureComponent {
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class Dimension extends PureComponent {
|
||||
componentDidMount () {
|
||||
const { start_at, end_at, dimension, limit, params } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
|
||||
api().post("/api/v1/admin/dimensions", { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
@@ -74,7 +74,7 @@ export default class Dimension extends PureComponent {
|
||||
</td>
|
||||
|
||||
<td className='dimension__item__value'>
|
||||
{typeof item.human_value !== 'undefined' ? item.human_value : <FormattedNumber value={item.value} />}
|
||||
{typeof item.human_value !== "undefined" ? item.human_value : <FormattedNumber value={item.value} />}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedNumber, FormattedMessage } from 'react-intl';
|
||||
import { FormattedNumber, FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import api from 'mastodon/api';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import api from "mastodon/api";
|
||||
import { Skeleton } from "mastodon/components/skeleton";
|
||||
|
||||
export default class ImpactReport extends PureComponent {
|
||||
|
||||
@@ -27,8 +27,8 @@ export default class ImpactReport extends PureComponent {
|
||||
include_subdomains: true,
|
||||
};
|
||||
|
||||
api().post('/api/v1/admin/measures', {
|
||||
keys: ['instance_accounts', 'instance_follows', 'instance_followers'],
|
||||
api().post("/api/v1/admin/measures", {
|
||||
keys: ["instance_accounts", "instance_follows", "instance_followers"],
|
||||
start_at: null,
|
||||
end_at: null,
|
||||
instance_accounts: params,
|
||||
@@ -63,7 +63,7 @@ export default class ImpactReport extends PureComponent {
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr className={classNames('dimension__item', { negative: !loading && data[1].total > 0 })}>
|
||||
<tr className={classNames("dimension__item", { negative: !loading && data[1].total > 0 })}>
|
||||
<td className='dimension__item__key'>
|
||||
<FormattedMessage id='admin.impact_report.instance_follows' defaultMessage='Followers their users would lose' />
|
||||
</td>
|
||||
@@ -73,7 +73,7 @@ export default class ImpactReport extends PureComponent {
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr className={classNames('dimension__item', { negative: !loading && data[2].total > 0 })}>
|
||||
<tr className={classNames("dimension__item", { negative: !loading && data[2].total > 0 })}>
|
||||
<td className='dimension__item__key'>
|
||||
<FormattedMessage id='admin.impact_report.instance_followers' defaultMessage='Followers our users would lose' />
|
||||
</td>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import { injectIntl, defineMessages } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import api from 'mastodon/api';
|
||||
import api from "mastodon/api";
|
||||
|
||||
const messages = defineMessages({
|
||||
legal: { id: 'report.categories.legal', defaultMessage: 'Legal' },
|
||||
other: { id: 'report.categories.other', defaultMessage: 'Other' },
|
||||
spam: { id: 'report.categories.spam', defaultMessage: 'Spam' },
|
||||
violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' },
|
||||
legal: { id: "report.categories.legal", defaultMessage: "Legal" },
|
||||
other: { id: "report.categories.other", defaultMessage: "Other" },
|
||||
spam: { id: "report.categories.spam", defaultMessage: "Spam" },
|
||||
violation: { id: "report.categories.violation", defaultMessage: "Content violates one or more server rules" },
|
||||
});
|
||||
|
||||
class Category extends PureComponent {
|
||||
@@ -37,11 +37,11 @@ class Category extends PureComponent {
|
||||
const { id, text, disabled, selected, children } = this.props;
|
||||
|
||||
return (
|
||||
<div tabIndex={0} role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
|
||||
<div tabIndex={0} role='button' className={classNames("report-reason-selector__category", { selected, disabled })} onClick={this.handleClick}>
|
||||
{selected && <input type='hidden' name='report[category]' value={id} />}
|
||||
|
||||
<div className='report-reason-selector__category__label'>
|
||||
<span className={classNames('poll__input', { active: selected, disabled })} />
|
||||
<span className={classNames("poll__input", { active: selected, disabled })} />
|
||||
{text}
|
||||
</div>
|
||||
|
||||
@@ -78,8 +78,8 @@ class Rule extends PureComponent {
|
||||
const { id, text, disabled, selected } = this.props;
|
||||
|
||||
return (
|
||||
<div tabIndex={0} role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
|
||||
<span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} />
|
||||
<div tabIndex={0} role='button' className={classNames("report-reason-selector__rule", { selected, disabled })} onClick={this.handleClick}>
|
||||
<span className={classNames("poll__input", { checkbox: true, active: selected, disabled })} />
|
||||
{selected && <input type='hidden' name='report[rule_ids][]' value={id} />}
|
||||
{text}
|
||||
</div>
|
||||
@@ -105,7 +105,7 @@ class ReportReasonSelector extends PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
api().get('/api/v1/instance').then(res => {
|
||||
api().get("/api/v1/instance").then(res => {
|
||||
this.setState({
|
||||
rules: res.data.rules,
|
||||
});
|
||||
@@ -150,10 +150,10 @@ class ReportReasonSelector extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className='report-reason-selector'>
|
||||
<Category id='other' text={intl.formatMessage(messages.other)} selected={category === 'other'} onSelect={this.handleSelect} disabled={disabled} />
|
||||
<Category id='legal' text={intl.formatMessage(messages.legal)} selected={category === 'legal'} onSelect={this.handleSelect} disabled={disabled} />
|
||||
<Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === 'spam'} onSelect={this.handleSelect} disabled={disabled} />
|
||||
<Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === 'violation'} onSelect={this.handleSelect} disabled={disabled}>
|
||||
<Category id='other' text={intl.formatMessage(messages.other)} selected={category === "other"} onSelect={this.handleSelect} disabled={disabled} />
|
||||
<Category id='legal' text={intl.formatMessage(messages.legal)} selected={category === "legal"} onSelect={this.handleSelect} disabled={disabled} />
|
||||
<Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === "spam"} onSelect={this.handleSelect} disabled={disabled} />
|
||||
<Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === "violation"} onSelect={this.handleSelect} disabled={disabled}>
|
||||
{rules.map(rule => <Rule key={rule.id} id={rule.id} text={rule.text} selected={rule_ids.includes(rule.id)} onToggle={this.handleToggle} disabled={disabled} />)}
|
||||
</Category>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage, FormattedNumber, FormattedDate } from 'react-intl';
|
||||
import { FormattedMessage, FormattedNumber, FormattedDate } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import api from 'mastodon/api';
|
||||
import { roundTo10 } from 'mastodon/utils/numbers';
|
||||
import api from "mastodon/api";
|
||||
import { roundTo10 } from "mastodon/utils/numbers";
|
||||
|
||||
const dateForCohort = cohort => {
|
||||
const timeZone = 'UTC';
|
||||
const timeZone = "UTC";
|
||||
switch(cohort.frequency) {
|
||||
case 'day':
|
||||
return <FormattedDate value={cohort.period} month='long' day='2-digit' timeZone={timeZone} />;
|
||||
default:
|
||||
return <FormattedDate value={cohort.period} month='long' year='numeric' timeZone={timeZone} />;
|
||||
case "day":
|
||||
return <FormattedDate value={cohort.period} month='long' day='2-digit' timeZone={timeZone} />;
|
||||
default:
|
||||
return <FormattedDate value={cohort.period} month='long' year='numeric' timeZone={timeZone} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class Retention extends PureComponent {
|
||||
componentDidMount () {
|
||||
const { start_at, end_at, frequency } = this.props;
|
||||
|
||||
api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
|
||||
api().post("/api/v1/admin/retention", { start_at, end_at, frequency }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
@@ -96,7 +96,7 @@ export default class Retention extends PureComponent {
|
||||
|
||||
return (
|
||||
<td key={retention.date}>
|
||||
<div className={classNames('retention__table__box', 'retention__table__average', `retention__table__box--${roundTo10(average * 100)}`)}>
|
||||
<div className={classNames("retention__table__box", "retention__table__average", `retention__table__box--${roundTo10(average * 100)}`)}>
|
||||
<FormattedNumber value={average} style='percent' />
|
||||
</div>
|
||||
</td>
|
||||
@@ -122,7 +122,7 @@ export default class Retention extends PureComponent {
|
||||
|
||||
{cohort.data.slice(1).map(retention => (
|
||||
<td key={retention.date}>
|
||||
<div className={classNames('retention__table__box', `retention__table__box--${roundTo10(retention.rate * 100)}`)}>
|
||||
<div className={classNames("retention__table__box", `retention__table__box--${roundTo10(retention.rate * 100)}`)}>
|
||||
<FormattedNumber value={retention.rate} style='percent' />
|
||||
</div>
|
||||
</td>
|
||||
@@ -136,11 +136,11 @@ export default class Retention extends PureComponent {
|
||||
|
||||
let title = null;
|
||||
switch(frequency) {
|
||||
case 'day':
|
||||
title = <FormattedMessage id='admin.dashboard.daily_retention' defaultMessage='User retention rate by day after sign-up' />;
|
||||
break;
|
||||
default:
|
||||
title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
|
||||
case "day":
|
||||
title = <FormattedMessage id='admin.dashboard.daily_retention' defaultMessage='User retention rate by day after sign-up' />;
|
||||
break;
|
||||
default:
|
||||
title = <FormattedMessage id='admin.dashboard.monthly_retention' defaultMessage='User retention rate by month after sign-up' />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import api from 'mastodon/api';
|
||||
import Hashtag from 'mastodon/components/hashtag';
|
||||
import api from "mastodon/api";
|
||||
import Hashtag from "mastodon/components/hashtag";
|
||||
|
||||
export default class Trends extends PureComponent {
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class Trends extends PureComponent {
|
||||
componentDidMount () {
|
||||
const { limit } = this.props;
|
||||
|
||||
api().get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
|
||||
api().get("/api/v1/admin/trends/tags", { params: { limit } }).then(res => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
data: res.data,
|
||||
@@ -57,7 +57,7 @@ export default class Trends extends PureComponent {
|
||||
people={hashtag.history[0].accounts * 1 + hashtag.history[1].accounts * 1}
|
||||
uses={hashtag.history[0].uses * 1 + hashtag.history[1].uses * 1}
|
||||
history={hashtag.history.reverse().map(day => day.uses)}
|
||||
className={classNames(hashtag.requires_review && 'trends__item--requires-review', !hashtag.trendable && !hashtag.requires_review && 'trends__item--disabled')}
|
||||
className={classNames(hashtag.requires_review && "trends__item--requires-review", !hashtag.trendable && !hashtag.requires_review && "trends__item--disabled")}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
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';
|
||||
import { ShortNumber } from "./short_number";
|
||||
|
||||
interface Props {
|
||||
value: number;
|
||||
value: number,
|
||||
}
|
||||
export const AnimatedNumber: React.FC<Props> = ({ value }) => {
|
||||
const [previousValue, setPreviousValue] = useState(value);
|
||||
@@ -48,7 +48,7 @@ export const AnimatedNumber: React.FC<Props> = ({ value }) => {
|
||||
<span
|
||||
key={key}
|
||||
style={{
|
||||
position: direction * style.y > 0 ? 'absolute' : 'static',
|
||||
position: direction * style.y > 0 ? "absolute" : "static",
|
||||
transform: `translateY(${style.y * 100}%)`,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
|
||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||
const filename = url => url.split("/").pop().split("#")[0].split("?")[0];
|
||||
|
||||
export default class AttachmentList extends ImmutablePureComponent {
|
||||
|
||||
@@ -22,7 +22,7 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
const { media, compact } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames('attachment-list', { compact })}>
|
||||
<div className={classNames("attachment-list", { compact })}>
|
||||
{!compact && (
|
||||
<div className='attachment-list__icon'>
|
||||
<Icon id='link' />
|
||||
@@ -31,13 +31,13 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
|
||||
<ul className='attachment-list__list'>
|
||||
{media.map(attachment => {
|
||||
const displayUrl = attachment.get('remote_url') || attachment.get('url');
|
||||
const displayUrl = attachment.get("remote_url") || attachment.get("url");
|
||||
|
||||
return (
|
||||
<li key={attachment.get('id')}>
|
||||
<li key={attachment.get("id")}>
|
||||
<a href={displayUrl} target='_blank' rel='noopener noreferrer'>
|
||||
{compact && <Icon id='link' />}
|
||||
{compact && ' ' }
|
||||
{compact && " " }
|
||||
{displayUrl ? filename(displayUrl) : <FormattedMessage id='attachments_list.unprocessed' defaultMessage='(unprocessed)' />}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
import { assetHost } from "mastodon/utils/config";
|
||||
|
||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
||||
import unicodeMapping from "../features/emoji/emoji_unicode_mapping_light";
|
||||
|
||||
export default class AutosuggestEmoji extends PureComponent {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class AutosuggestEmoji extends PureComponent {
|
||||
if (emoji.custom) {
|
||||
url = emoji.imageUrl;
|
||||
} else {
|
||||
const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')];
|
||||
const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, "")];
|
||||
|
||||
if (!mapping) {
|
||||
return null;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { ShortNumber } from "mastodon/components/short_number";
|
||||
|
||||
interface Props {
|
||||
tag: {
|
||||
name: string;
|
||||
url?: string;
|
||||
name: string,
|
||||
url?: string,
|
||||
history?: {
|
||||
uses: number;
|
||||
accounts: string;
|
||||
day: string;
|
||||
}[];
|
||||
following?: boolean;
|
||||
type: 'hashtag';
|
||||
};
|
||||
uses: number,
|
||||
accounts: string,
|
||||
day: string,
|
||||
}[],
|
||||
following?: boolean,
|
||||
type: "hashtag",
|
||||
},
|
||||
}
|
||||
|
||||
export const AutosuggestHashtag: React.FC<Props> = ({ tag }) => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||
import AutosuggestAccountContainer from "../features/compose/containers/autosuggest_account_container";
|
||||
|
||||
import AutosuggestEmoji from './autosuggest_emoji';
|
||||
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||
import AutosuggestEmoji from "./autosuggest_emoji";
|
||||
import { AutosuggestHashtag } from "./autosuggest_hashtag";
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
||||
let word;
|
||||
@@ -59,7 +59,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
autoFocus: true,
|
||||
searchTokens: ['@', ':', '#'],
|
||||
searchTokens: ["@", ":", "#"],
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -100,39 +100,39 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector('.ui').parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
case "Escape":
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector(".ui").parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
||||
}
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
||||
}
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
break;
|
||||
case "Enter":
|
||||
case "Tab":
|
||||
// Select suggestion
|
||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
}
|
||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.defaultPrevented || !this.props.onKeyDown) {
|
||||
@@ -151,7 +151,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
onSuggestionClick = (e) => {
|
||||
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
|
||||
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute("data-index"));
|
||||
e.preventDefault();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
|
||||
this.input.focus();
|
||||
@@ -171,19 +171,19 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
const { selectedSuggestion } = this.state;
|
||||
let inner, key;
|
||||
|
||||
if (suggestion.type === 'emoji') {
|
||||
if (suggestion.type === "emoji") {
|
||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||
key = suggestion.id;
|
||||
} else if (suggestion.type ==='hashtag') {
|
||||
} else if (suggestion.type ==="hashtag") {
|
||||
inner = <AutosuggestHashtag tag={suggestion} />;
|
||||
key = suggestion.name;
|
||||
} else if (suggestion.type === 'account') {
|
||||
} else if (suggestion.type === "account") {
|
||||
inner = <AutosuggestAccountContainer id={suggestion.id} />;
|
||||
key = suggestion.id;
|
||||
}
|
||||
|
||||
return (
|
||||
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames("autosuggest-textarea__suggestions__item", { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
@@ -196,7 +196,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='autosuggest-input'>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{placeholder}</span>
|
||||
<span style={{ display: "none" }}>{placeholder}</span>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
@@ -220,7 +220,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
|
||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? "" : "autosuggest-textarea__suggestions--visible"}`}>
|
||||
{suggestions.map(this.renderSuggestion)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import Textarea from "react-textarea-autosize";
|
||||
|
||||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||
import AutosuggestAccountContainer from "../features/compose/containers/autosuggest_account_container";
|
||||
|
||||
import AutosuggestEmoji from './autosuggest_emoji';
|
||||
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||
import AutosuggestEmoji from "./autosuggest_emoji";
|
||||
import { AutosuggestHashtag } from "./autosuggest_hashtag";
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||
let word;
|
||||
@@ -24,7 +24,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||
word = str.slice(left, right + caretPosition);
|
||||
}
|
||||
|
||||
if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
|
||||
if (!word || word.trim().length < 3 || ["@", ":", "#"].indexOf(word[0]) === -1) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
@@ -97,39 +97,39 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector('.ui').parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
case "Escape":
|
||||
if (suggestions.size === 0 || suggestionsHidden) {
|
||||
document.querySelector(".ui").parentElement.focus();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
||||
}
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
||||
}
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
case 'Tab':
|
||||
break;
|
||||
case "Enter":
|
||||
case "Tab":
|
||||
// Select suggestion
|
||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
}
|
||||
if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (e.defaultPrevented || !this.props.onKeyDown) {
|
||||
@@ -151,7 +151,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
onSuggestionClick = (e) => {
|
||||
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index'));
|
||||
const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute("data-index"));
|
||||
e.preventDefault();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
|
||||
this.textarea.focus();
|
||||
@@ -178,19 +178,19 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
const { selectedSuggestion } = this.state;
|
||||
let inner, key;
|
||||
|
||||
if (suggestion.type === 'emoji') {
|
||||
if (suggestion.type === "emoji") {
|
||||
inner = <AutosuggestEmoji emoji={suggestion} />;
|
||||
key = suggestion.id;
|
||||
} else if (suggestion.type === 'hashtag') {
|
||||
} else if (suggestion.type === "hashtag") {
|
||||
inner = <AutosuggestHashtag tag={suggestion} />;
|
||||
key = suggestion.name;
|
||||
} else if (suggestion.type === 'account') {
|
||||
} else if (suggestion.type === "account") {
|
||||
inner = <AutosuggestAccountContainer id={suggestion.id} />;
|
||||
key = suggestion.id;
|
||||
}
|
||||
|
||||
return (
|
||||
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames("autosuggest-textarea__suggestions__item", { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
@@ -204,7 +204,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
<div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
|
||||
<div className='autosuggest-textarea'>
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{placeholder}</span>
|
||||
<span style={{ display: "none" }}>{placeholder}</span>
|
||||
|
||||
<Textarea
|
||||
ref={this.setTextarea}
|
||||
@@ -229,7 +229,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
</div>,
|
||||
|
||||
<div className='autosuggest-textarea__suggestions-wrapper' key='suggestions-wrapper'>
|
||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
|
||||
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? "" : "autosuggest-textarea__suggestions--visible"}`}>
|
||||
{suggestions.map(this.renderSuggestion)}
|
||||
</div>
|
||||
</div>,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import type { Account } from '../../types/resources';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import { useHovering } from "../../hooks/useHovering";
|
||||
import { type Account } from "../../types/resources";
|
||||
import { autoPlayGif } from "../initial_state";
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
size: number;
|
||||
style?: React.CSSProperties;
|
||||
inline?: boolean;
|
||||
animate?: boolean;
|
||||
account: Account | undefined, // FIXME: remove `undefined` once we know for sure its always there
|
||||
size: number,
|
||||
style?: React.CSSProperties,
|
||||
inline?: boolean,
|
||||
animate?: boolean,
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<Props> = ({
|
||||
@@ -29,19 +29,19 @@ export const Avatar: React.FC<Props> = ({
|
||||
|
||||
const src =
|
||||
hovering || animate
|
||||
? account?.get('avatar')
|
||||
: account?.get('avatar_static');
|
||||
? account?.get("avatar")
|
||||
: account?.get("avatar_static");
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('account__avatar', {
|
||||
'account__avatar-inline': inline,
|
||||
className={classNames("account__avatar", {
|
||||
"account__avatar-inline": inline,
|
||||
})}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={style}
|
||||
>
|
||||
{src && <img src={src} alt={account?.get('acct')} />}
|
||||
{src && <img src={src} alt={account?.get("acct")} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import { autoPlayGif } from "../initial_state";
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import { Avatar } from "./avatar";
|
||||
|
||||
export default class AvatarComposite extends PureComponent {
|
||||
|
||||
@@ -24,10 +24,10 @@ export default class AvatarComposite extends PureComponent {
|
||||
|
||||
let width = 50;
|
||||
let height = 100;
|
||||
let top = 'auto';
|
||||
let left = 'auto';
|
||||
let bottom = 'auto';
|
||||
let right = 'auto';
|
||||
let top = "auto";
|
||||
let left = "auto";
|
||||
let bottom = "auto";
|
||||
let right = "auto";
|
||||
|
||||
if (size === 1) {
|
||||
width = 100;
|
||||
@@ -39,35 +39,35 @@ export default class AvatarComposite extends PureComponent {
|
||||
|
||||
if (size === 2) {
|
||||
if (index === 0) {
|
||||
right = '1px';
|
||||
right = "1px";
|
||||
} else {
|
||||
left = '1px';
|
||||
left = "1px";
|
||||
}
|
||||
} else if (size === 3) {
|
||||
if (index === 0) {
|
||||
right = '1px';
|
||||
right = "1px";
|
||||
} else if (index > 0) {
|
||||
left = '1px';
|
||||
left = "1px";
|
||||
}
|
||||
|
||||
if (index === 1) {
|
||||
bottom = '1px';
|
||||
bottom = "1px";
|
||||
} else if (index > 1) {
|
||||
top = '1px';
|
||||
top = "1px";
|
||||
}
|
||||
} else if (size === 4) {
|
||||
if (index === 0 || index === 2) {
|
||||
right = '1px';
|
||||
right = "1px";
|
||||
}
|
||||
|
||||
if (index === 1 || index === 3) {
|
||||
left = '1px';
|
||||
left = "1px";
|
||||
}
|
||||
|
||||
if (index < 2) {
|
||||
bottom = '1px';
|
||||
bottom = "1px";
|
||||
} else {
|
||||
top = '1px';
|
||||
top = "1px";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export default class AvatarComposite extends PureComponent {
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={account.get('id')} style={style}>
|
||||
<div key={account.get("id")} style={style}>
|
||||
<Avatar account={account} animate={animate} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import type { Account } from '../../types/resources';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import { useHovering } from "../../hooks/useHovering";
|
||||
import { type Account } from "../../types/resources";
|
||||
import { autoPlayGif } from "../initial_state";
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
size?: number;
|
||||
baseSize?: number;
|
||||
overlaySize?: number;
|
||||
account: Account | undefined, // FIXME: remove `undefined` once we know for sure its always there
|
||||
friend: Account | undefined, // FIXME: remove `undefined` once we know for sure its always there
|
||||
size?: number,
|
||||
baseSize?: number,
|
||||
overlaySize?: number,
|
||||
}
|
||||
|
||||
export const AvatarOverlay: React.FC<Props> = ({
|
||||
@@ -20,11 +20,11 @@ export const AvatarOverlay: React.FC<Props> = ({
|
||||
const { hovering, handleMouseEnter, handleMouseLeave } =
|
||||
useHovering(autoPlayGif);
|
||||
const accountSrc = hovering
|
||||
? account?.get('avatar')
|
||||
: account?.get('avatar_static');
|
||||
? account?.get("avatar")
|
||||
: account?.get("avatar_static");
|
||||
const friendSrc = hovering
|
||||
? friend?.get('avatar')
|
||||
: friend?.get('avatar_static');
|
||||
? friend?.get("avatar")
|
||||
: friend?.get("avatar_static");
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -38,7 +38,7 @@ export const AvatarOverlay: React.FC<Props> = ({
|
||||
className='account__avatar'
|
||||
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
|
||||
>
|
||||
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
|
||||
{accountSrc && <img src={accountSrc} alt={account?.get("acct")} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='account__avatar-overlay-overlay'>
|
||||
@@ -46,7 +46,7 @@ export const AvatarOverlay: React.FC<Props> = ({
|
||||
className='account__avatar'
|
||||
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
|
||||
>
|
||||
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
|
||||
{friendSrc && <img src={friendSrc} alt={friend?.get("acct")} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { ReactComponent as GroupsIcon } from '@material-design-icons/svg/outlined/group.svg';
|
||||
import { ReactComponent as PersonIcon } from '@material-design-icons/svg/outlined/person.svg';
|
||||
import { ReactComponent as SmartToyIcon } from '@material-design-icons/svg/outlined/smart_toy.svg';
|
||||
import { ReactComponent as GroupsIcon } from "@material-design-icons/svg/outlined/group.svg";
|
||||
import { ReactComponent as PersonIcon } from "@material-design-icons/svg/outlined/person.svg";
|
||||
import { ReactComponent as SmartToyIcon } from "@material-design-icons/svg/outlined/smart_toy.svg";
|
||||
|
||||
|
||||
export const Badge = ({ icon, label, domain }) => (
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { memo, useRef, useEffect } from 'react';
|
||||
import { memo, useRef, useEffect } from "react";
|
||||
|
||||
import { decode } from 'blurhash';
|
||||
import { decode } from "blurhash";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLCanvasElement> {
|
||||
hash: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
|
||||
children?: never;
|
||||
hash: string,
|
||||
width?: number,
|
||||
height?: number,
|
||||
dummy?: boolean, // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
|
||||
children?: never,
|
||||
}
|
||||
const Blurhash: React.FC<Props> = ({
|
||||
hash,
|
||||
@@ -25,16 +25,18 @@ const Blurhash: React.FC<Props> = ({
|
||||
// eslint-disable-next-line no-self-assign
|
||||
canvas.width = canvas.width; // resets canvas
|
||||
|
||||
if (dummy || !hash) return;
|
||||
if (dummy || !hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const pixels = decode(hash, width, height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imageData = new ImageData(pixels, width, height);
|
||||
|
||||
ctx?.putImageData(imageData, 0, 0);
|
||||
} catch (err) {
|
||||
console.error('Blurhash decoding failure', { err, hash });
|
||||
console.error("Blurhash decoding failure", { err, hash });
|
||||
}
|
||||
}, [dummy, hash, width, height]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
export default class Button extends PureComponent {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default class Button extends PureComponent {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
type: 'button',
|
||||
type: "button",
|
||||
};
|
||||
|
||||
handleClick = (e) => {
|
||||
@@ -36,9 +36,9 @@ export default class Button extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const className = classNames('button', this.props.className, {
|
||||
'button-secondary': this.props.secondary,
|
||||
'button--block': this.props.block,
|
||||
const className = classNames("button", this.props.className, {
|
||||
"button-secondary": this.props.secondary,
|
||||
"button--block": this.props.block,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
interface Props {
|
||||
size: number;
|
||||
strokeWidth: number;
|
||||
size: number,
|
||||
strokeWidth: number,
|
||||
}
|
||||
|
||||
export const CircularProgress: React.FC<Props> = ({ size, strokeWidth }) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import { supportsPassiveEvents } from "detect-passive-events";
|
||||
|
||||
import { scrollTop } from '../scroll';
|
||||
import { scrollTop } from "../scroll";
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||
|
||||
@@ -21,7 +21,7 @@ export default class Column extends PureComponent {
|
||||
if (this.props.bindToDocument) {
|
||||
scrollable = document.scrollingElement;
|
||||
} else {
|
||||
scrollable = this.node.querySelector('.scrollable');
|
||||
scrollable = this.node.querySelector(".scrollable");
|
||||
}
|
||||
|
||||
if (!scrollable) {
|
||||
@@ -32,7 +32,7 @@ export default class Column extends PureComponent {
|
||||
}
|
||||
|
||||
handleWheel = () => {
|
||||
if (typeof this._interruptScrollAnimation !== 'function') {
|
||||
if (typeof this._interruptScrollAnimation !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,17 +45,17 @@ export default class Column extends PureComponent {
|
||||
|
||||
componentDidMount () {
|
||||
if (this.props.bindToDocument) {
|
||||
document.addEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
document.addEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
} else {
|
||||
this.node.addEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
this.node.addEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.props.bindToDocument) {
|
||||
document.removeEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
document.removeEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
} else {
|
||||
this.node.removeEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
this.node.removeEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
|
||||
export default class ColumnBackButton extends PureComponent {
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class ColumnBackButton extends PureComponent {
|
||||
} else if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
} else {
|
||||
router.history.push('/');
|
||||
router.history.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class ColumnBackButton extends PureComponent {
|
||||
// The portal container and the component may be rendered to the DOM in
|
||||
// the same React render pass, so the container might not be available at
|
||||
// the time `render()` is called.
|
||||
const container = document.getElementById('tabs-bar__portal');
|
||||
const container = document.getElementById("tabs-bar__portal");
|
||||
if (container === null) {
|
||||
// The container wasn't available, force a re-render so that the
|
||||
// component can eventually be inserted in the container and not scroll
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
|
||||
import ColumnBackButton from './column_back_button';
|
||||
import ColumnBackButton from "./column_back_button";
|
||||
|
||||
export default class ColumnBackButtonSlim extends ColumnBackButton {
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl, defineMessages } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
|
||||
const messages = defineMessages({
|
||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
||||
moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
|
||||
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
|
||||
show: { id: "column_header.show_settings", defaultMessage: "Show settings" },
|
||||
hide: { id: "column_header.hide_settings", defaultMessage: "Hide settings" },
|
||||
moveLeft: { id: "column_header.moveLeft_settings", defaultMessage: "Move column to the left" },
|
||||
moveRight: { id: "column_header.moveRight_settings", defaultMessage: "Move column to the right" },
|
||||
});
|
||||
|
||||
class ColumnHeader extends PureComponent {
|
||||
@@ -68,7 +68,7 @@ class ColumnHeader extends PureComponent {
|
||||
if (router.history.location?.state?.fromMastodon) {
|
||||
router.history.goBack();
|
||||
} else {
|
||||
router.history.push('/');
|
||||
router.history.push("/");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ class ColumnHeader extends PureComponent {
|
||||
|
||||
handlePin = () => {
|
||||
if (!this.props.pinned) {
|
||||
this.context.router.history.replace('/');
|
||||
this.context.router.history.replace("/");
|
||||
}
|
||||
|
||||
this.props.onPin();
|
||||
@@ -89,21 +89,21 @@ class ColumnHeader extends PureComponent {
|
||||
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
|
||||
const { collapsed, animating } = this.state;
|
||||
|
||||
const wrapperClassName = classNames('column-header__wrapper', {
|
||||
'active': active,
|
||||
const wrapperClassName = classNames("column-header__wrapper", {
|
||||
"active": active,
|
||||
});
|
||||
|
||||
const buttonClassName = classNames('column-header', {
|
||||
'active': active,
|
||||
const buttonClassName = classNames("column-header", {
|
||||
"active": active,
|
||||
});
|
||||
|
||||
const collapsibleClassName = classNames('column-header__collapsible', {
|
||||
'collapsed': collapsed,
|
||||
'animating': animating,
|
||||
const collapsibleClassName = classNames("column-header__collapsible", {
|
||||
"collapsed": collapsed,
|
||||
"animating": animating,
|
||||
});
|
||||
|
||||
const collapsibleButtonClassName = classNames('column-header__button', {
|
||||
'active': !collapsed,
|
||||
const collapsibleButtonClassName = classNames("column-header__button", {
|
||||
"active": !collapsed,
|
||||
});
|
||||
|
||||
let extraContent, pinButton, moveButtons, backButton, collapseButton;
|
||||
@@ -200,7 +200,7 @@ class ColumnHeader extends PureComponent {
|
||||
// The portal container and the component may be rendered to the DOM in
|
||||
// the same React render pass, so the container might not be available at
|
||||
// the time `render()` is called.
|
||||
const container = document.getElementById('tabs-bar__portal');
|
||||
const container = document.getElementById("tabs-bar__portal");
|
||||
if (container === null) {
|
||||
// The container wasn't available, force a re-render so that the
|
||||
// component can eventually be inserted in the container and not scroll
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export const StatusesCounter = (
|
||||
displayNumber: React.ReactNode,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { type PropsWithChildren } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import { bannerSettings } from 'mastodon/settings';
|
||||
import { bannerSettings } from "mastodon/settings";
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
import { IconButton } from "./icon_button";
|
||||
|
||||
const messages = defineMessages({
|
||||
dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
|
||||
dismiss: { id: "dismissable_banner.dismiss", defaultMessage: "Dismiss" },
|
||||
});
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
id: string,
|
||||
}
|
||||
|
||||
export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import type { List } from 'immutable';
|
||||
import { type List } from "immutable";
|
||||
|
||||
import type { Account } from '../../types/resources';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import { type Account } from "../../types/resources";
|
||||
import { autoPlayGif } from "../initial_state";
|
||||
|
||||
import { Skeleton } from './skeleton';
|
||||
import { Skeleton } from "./skeleton";
|
||||
|
||||
interface Props {
|
||||
account?: Account;
|
||||
others?: List<Account>;
|
||||
localDomain?: string;
|
||||
account?: Account,
|
||||
others?: List<Account>,
|
||||
localDomain?: string,
|
||||
}
|
||||
|
||||
export class DisplayName extends React.PureComponent<Props> {
|
||||
@@ -22,11 +22,13 @@ export class DisplayName extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
const emojis =
|
||||
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
||||
currentTarget.querySelectorAll<HTMLImageElement>("img.custom-emoji");
|
||||
|
||||
emojis.forEach((emoji) => {
|
||||
const originalSrc = emoji.getAttribute('data-original');
|
||||
if (originalSrc != null) emoji.src = originalSrc;
|
||||
const originalSrc = emoji.getAttribute("data-original");
|
||||
if (originalSrc != null) {
|
||||
emoji.src = originalSrc;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -38,11 +40,13 @@ export class DisplayName extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
const emojis =
|
||||
currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji');
|
||||
currentTarget.querySelectorAll<HTMLImageElement>("img.custom-emoji");
|
||||
|
||||
emojis.forEach((emoji) => {
|
||||
const staticSrc = emoji.getAttribute('data-static');
|
||||
if (staticSrc != null) emoji.src = staticSrc;
|
||||
const staticSrc = emoji.getAttribute("data-static");
|
||||
if (staticSrc != null) {
|
||||
emoji.src = staticSrc;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -63,22 +67,22 @@ export class DisplayName extends React.PureComponent<Props> {
|
||||
displayName = others
|
||||
.take(2)
|
||||
.map((a) => (
|
||||
<bdi key={a.get('id')}>
|
||||
<bdi key={a.get("id")}>
|
||||
<strong
|
||||
className='display-name__html'
|
||||
dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }}
|
||||
dangerouslySetInnerHTML={{ __html: a.get("display_name_html") }}
|
||||
/>
|
||||
</bdi>
|
||||
))
|
||||
.reduce((prev, cur) => [prev, ', ', cur]);
|
||||
.reduce((prev, cur) => [prev, ", ", cur]);
|
||||
|
||||
if (others.size - 2 > 0) {
|
||||
suffix = `+${others.size - 2}`;
|
||||
}
|
||||
} else if (account) {
|
||||
let acct = account.get('acct');
|
||||
let acct = account.get("acct");
|
||||
|
||||
if (!acct.includes('@') && localDomain) {
|
||||
if (!acct.includes("@") && localDomain) {
|
||||
acct = `${acct}@${localDomain}`;
|
||||
}
|
||||
|
||||
@@ -87,7 +91,7 @@ export class DisplayName extends React.PureComponent<Props> {
|
||||
<strong
|
||||
className='display-name__html'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: account.get('display_name_html'),
|
||||
__html: account.get("display_name_html"),
|
||||
}}
|
||||
/>
|
||||
</bdi>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
import { IconButton } from "./icon_button";
|
||||
|
||||
const messages = defineMessages({
|
||||
unblockDomain: {
|
||||
id: 'account.unblock_domain',
|
||||
defaultMessage: 'Unblock domain {domain}',
|
||||
id: "account.unblock_domain",
|
||||
defaultMessage: "Unblock domain {domain}",
|
||||
},
|
||||
});
|
||||
|
||||
interface Props {
|
||||
domain: string;
|
||||
onUnblockDomain: (domain: string) => void;
|
||||
domain: string,
|
||||
onUnblockDomain: (domain: string) => void,
|
||||
}
|
||||
|
||||
export const Domain: React.FC<Props> = ({ domain, onUnblockDomain }) => {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent, cloneElement, Children } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent, cloneElement, Children } from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
import { supportsPassiveEvents } from "detect-passive-events";
|
||||
import Overlay from "react-overlays/Overlay";
|
||||
|
||||
import { CircularProgress } from "./circular_progress";
|
||||
import { IconButton } from './icon_button';
|
||||
import { IconButton } from "./icon_button";
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||
let id = 0;
|
||||
@@ -44,9 +44,9 @@ class DropdownMenu extends PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.addEventListener('keydown', this.handleKeyDown, { capture: true });
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
document.addEventListener("click", this.handleDocumentClick, { capture: true });
|
||||
document.addEventListener("keydown", this.handleKeyDown, { capture: true });
|
||||
document.addEventListener("touchend", this.handleDocumentClick, listenerOptions);
|
||||
|
||||
if (this.focusedItem && this.props.openedViaKeyboard) {
|
||||
this.focusedItem.focus({ preventScroll: true });
|
||||
@@ -54,9 +54,9 @@ class DropdownMenu extends PureComponent {
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.removeEventListener('keydown', this.handleKeyDown, { capture: true });
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
document.removeEventListener("click", this.handleDocumentClick, { capture: true });
|
||||
document.removeEventListener("keydown", this.handleKeyDown, { capture: true });
|
||||
document.removeEventListener("touchend", this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
@@ -68,33 +68,33 @@ class DropdownMenu extends PureComponent {
|
||||
};
|
||||
|
||||
handleKeyDown = e => {
|
||||
const items = Array.from(this.node.querySelectorAll('a, button'));
|
||||
const items = Array.from(this.node.querySelectorAll("a, button"));
|
||||
const index = items.indexOf(document.activeElement);
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowDown':
|
||||
element = items[index+1] || items[0];
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = items[index-1] || items[items.length-1];
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = items[index-1] || items[items.length-1];
|
||||
} else {
|
||||
case "ArrowDown":
|
||||
element = items[index+1] || items[0];
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = items[0];
|
||||
break;
|
||||
case 'End':
|
||||
element = items[items.length-1];
|
||||
break;
|
||||
case 'Escape':
|
||||
this.props.onClose();
|
||||
break;
|
||||
break;
|
||||
case "ArrowUp":
|
||||
element = items[index-1] || items[items.length-1];
|
||||
break;
|
||||
case "Tab":
|
||||
if (e.shiftKey) {
|
||||
element = items[index-1] || items[items.length-1];
|
||||
} else {
|
||||
element = items[index+1] || items[0];
|
||||
}
|
||||
break;
|
||||
case "Home":
|
||||
element = items[0];
|
||||
break;
|
||||
case "End":
|
||||
element = items[items.length-1];
|
||||
break;
|
||||
case "Escape":
|
||||
this.props.onClose();
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
@@ -105,7 +105,7 @@ class DropdownMenu extends PureComponent {
|
||||
};
|
||||
|
||||
handleItemKeyPress = e => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
this.handleClick(e);
|
||||
}
|
||||
};
|
||||
@@ -120,10 +120,10 @@ class DropdownMenu extends PureComponent {
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
||||
const { text, href = '#', target = '_blank', method, dangerous } = option;
|
||||
const { text, href = "#", target = "_blank", method, dangerous } = option;
|
||||
|
||||
return (
|
||||
<li className={classNames('dropdown-menu__item', { 'dropdown-menu__item--dangerous': dangerous })} key={`${text}-${i}`}>
|
||||
<li className={classNames("dropdown-menu__item", { "dropdown-menu__item--dangerous": dangerous })} key={`${text}-${i}`}>
|
||||
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||
{text}
|
||||
</a>
|
||||
@@ -137,7 +137,7 @@ class DropdownMenu extends PureComponent {
|
||||
let renderItem = this.props.renderItem || this.renderItem;
|
||||
|
||||
return (
|
||||
<div className={classNames('dropdown-menu__container', { 'dropdown-menu__container--loading': loading })} ref={this.setRef}>
|
||||
<div className={classNames("dropdown-menu__container", { "dropdown-menu__container--loading": loading })} ref={this.setRef}>
|
||||
{loading && (
|
||||
<CircularProgress size={30} strokeWidth={3.5} />
|
||||
)}
|
||||
@@ -149,7 +149,7 @@ class DropdownMenu extends PureComponent {
|
||||
)}
|
||||
|
||||
{!loading && (
|
||||
<ul className={classNames('dropdown-menu__container__list', { 'dropdown-menu__container__list--scrollable': scrollable })}>
|
||||
<ul className={classNames("dropdown-menu__container__list", { "dropdown-menu__container__list--scrollable": scrollable })}>
|
||||
{items.map((option, i) => renderItem(option, i, { onClick: this.handleClick, onKeyPress: this.handleItemKeyPress }))}
|
||||
</ul>
|
||||
)}
|
||||
@@ -186,7 +186,7 @@ export default class Dropdown extends PureComponent {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
title: 'Menu',
|
||||
title: "Menu",
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -197,7 +197,7 @@ export default class Dropdown extends PureComponent {
|
||||
if (this.state.id === this.props.openDropdownId) {
|
||||
this.handleClose();
|
||||
} else {
|
||||
this.props.onOpen(this.state.id, this.handleItemClick, type !== 'click');
|
||||
this.props.onOpen(this.state.id, this.handleItemClick, type !== "click");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -217,35 +217,35 @@ export default class Dropdown extends PureComponent {
|
||||
|
||||
handleButtonKeyDown = (e) => {
|
||||
switch(e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleMouseDown();
|
||||
break;
|
||||
case " ":
|
||||
case "Enter":
|
||||
this.handleMouseDown();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyPress = (e) => {
|
||||
switch(e.key) {
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case " ":
|
||||
case "Enter":
|
||||
this.handleClick(e);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleItemClick = e => {
|
||||
const { onItemClick } = this.props;
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const i = Number(e.currentTarget.getAttribute("data-index"));
|
||||
const item = this.props.items[i];
|
||||
|
||||
this.handleClose();
|
||||
|
||||
if (typeof onItemClick === 'function') {
|
||||
if (typeof onItemClick === "function") {
|
||||
e.preventDefault();
|
||||
onItemClick(item, i);
|
||||
} else if (item && typeof item.action === 'function') {
|
||||
} else if (item && typeof item.action === "function") {
|
||||
e.preventDefault();
|
||||
item.action();
|
||||
} else if (item && item.to) {
|
||||
@@ -297,7 +297,7 @@ export default class Dropdown extends PureComponent {
|
||||
onKeyPress: this.handleKeyPress,
|
||||
}) : (
|
||||
<IconButton
|
||||
icon={!open ? icon : 'close'}
|
||||
icon={!open ? icon : "close"}
|
||||
title={title}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
@@ -314,7 +314,7 @@ export default class Dropdown extends PureComponent {
|
||||
<span ref={this.setTargetRef}>
|
||||
{button}
|
||||
</span>
|
||||
<Overlay show={open} offset={[5, 5]} placement={'bottom'} flip target={this.findTarget} popperConfig={{ strategy: 'fixed' }}>
|
||||
<Overlay show={open} offset={[5, 5]} placement={"bottom"} flip target={this.findTarget} popperConfig={{ strategy: "fixed" }}>
|
||||
{({ props, arrowProps, placement }) => (
|
||||
<div {...props}>
|
||||
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { openDropdownMenu, closeDropdownMenu } from 'mastodon/actions/dropdown_menu';
|
||||
import { fetchHistory } from 'mastodon/actions/history';
|
||||
import DropdownMenu from 'mastodon/components/dropdown_menu';
|
||||
import { openDropdownMenu, closeDropdownMenu } from "mastodon/actions/dropdown_menu";
|
||||
import { fetchHistory } from "mastodon/actions/history";
|
||||
import DropdownMenu from "mastodon/components/dropdown_menu";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -12,8 +12,8 @@ import DropdownMenu from 'mastodon/components/dropdown_menu';
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
openDropdownId: state.dropdownMenu.openId,
|
||||
openedViaKeyboard: state.dropdownMenu.keyboard,
|
||||
items: state.getIn(['history', statusId, 'items']),
|
||||
loading: state.getIn(['history', statusId, 'loading']),
|
||||
items: state.getIn(["history", statusId, "items"]),
|
||||
loading: state.getIn(["history", statusId, "loading"]),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { statusId }) => ({
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from "react-intl";
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import InlineAccount from 'mastodon/components/inline_account';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { openModal } from "mastodon/actions/modal";
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
import InlineAccount from "mastodon/components/inline_account";
|
||||
import { RelativeTimestamp } from "mastodon/components/relative_timestamp";
|
||||
|
||||
import DropdownMenu from './containers/dropdown_menu_container';
|
||||
import DropdownMenu from "./containers/dropdown_menu_container";
|
||||
|
||||
const mapDispatchToProps = (dispatch, { statusId }) => ({
|
||||
|
||||
onItemClick (index) {
|
||||
dispatch(openModal({
|
||||
modalType: 'COMPARE_HISTORY',
|
||||
modalType: "COMPARE_HISTORY",
|
||||
modalProps: { index, statusId },
|
||||
}));
|
||||
},
|
||||
@@ -44,17 +44,17 @@ class EditedTimestamp extends PureComponent {
|
||||
};
|
||||
|
||||
renderItem = (item, index, { onClick, onKeyPress }) => {
|
||||
const formattedDate = <RelativeTimestamp timestamp={item.get('created_at')} short={false} />;
|
||||
const formattedName = <InlineAccount accountId={item.get('account')} />;
|
||||
const formattedDate = <RelativeTimestamp timestamp={item.get("created_at")} short={false} />;
|
||||
const formattedName = <InlineAccount accountId={item.get("account")} />;
|
||||
|
||||
const label = item.get('original') ? (
|
||||
const label = item.get("original") ? (
|
||||
<FormattedMessage id='status.history.created' defaultMessage='{name} created {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
) : (
|
||||
<FormattedMessage id='status.history.edited' defaultMessage='{name} edited {date}' values={{ name: formattedName, date: formattedDate }} />
|
||||
);
|
||||
|
||||
return (
|
||||
<li className='dropdown-menu__item edited-timestamp__history__item' key={item.get('created_at')}>
|
||||
<li className='dropdown-menu__item edited-timestamp__history__item' key={item.get("created_at")}>
|
||||
<button data-index={index} onClick={onClick} onKeyPress={onKeyPress}>{label}</button>
|
||||
</li>
|
||||
);
|
||||
@@ -66,7 +66,7 @@ class EditedTimestamp extends PureComponent {
|
||||
return (
|
||||
<DropdownMenu statusId={statusId} renderItem={this.renderItem} scrollable renderHeader={this.renderHeader} onItemClick={this.handleItemClick}>
|
||||
<button className='dropdown-menu__text-button'>
|
||||
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) }} /> <Icon id='caret-down' />
|
||||
<FormattedMessage id='status.edited' defaultMessage='Edited {date}' values={{ date: intl.formatDate(timestamp, { hour12: false, month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" }) }} /> <Icon id='caret-down' />
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import { DisplayName } from "mastodon/components/display_name";
|
||||
import { Skeleton } from "mastodon/components/skeleton";
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
minimal?: boolean;
|
||||
size?: number,
|
||||
minimal?: boolean,
|
||||
}
|
||||
|
||||
export const EmptyAccount: React.FC<Props> = ({
|
||||
@@ -15,7 +15,7 @@ export const EmptyAccount: React.FC<Props> = ({
|
||||
minimal = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className={classNames("account", { "account--minimal": minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<div className='account__display-name'>
|
||||
<div className='account__avatar-wrapper'>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
import StackTrace from 'stacktrace-js';
|
||||
import StackTrace from "stacktrace-js";
|
||||
|
||||
import { version, source_url } from 'mastodon/initial_state';
|
||||
import { version, source_url } from "mastodon/initial_state";
|
||||
|
||||
export default class ErrorBoundary extends PureComponent {
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class ErrorBoundary extends PureComponent {
|
||||
|
||||
StackTrace.fromError(error).then((stackframes) => {
|
||||
this.setState({
|
||||
mappedStackTrace: stackframes.map((sf) => sf.toString()).join('\n'),
|
||||
mappedStackTrace: stackframes.map((sf) => sf.toString()).join("\n"),
|
||||
});
|
||||
}).catch(() => {
|
||||
this.setState({
|
||||
@@ -45,23 +45,23 @@ export default class ErrorBoundary extends PureComponent {
|
||||
|
||||
handleCopyStackTrace = () => {
|
||||
const { errorMessage, stackTrace, mappedStackTrace } = this.state;
|
||||
const textarea = document.createElement('textarea');
|
||||
const textarea = document.createElement("textarea");
|
||||
|
||||
let contents = [errorMessage, stackTrace];
|
||||
if (mappedStackTrace) {
|
||||
contents.push(mappedStackTrace);
|
||||
}
|
||||
|
||||
textarea.textContent = contents.join('\n\n\n');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.textContent = contents.join("\n\n\n");
|
||||
textarea.style.position = "fixed";
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.execCommand("copy");
|
||||
} catch (e) {
|
||||
|
||||
console.error(e);
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ export default class ErrorBoundary extends PureComponent {
|
||||
return this.props.children;
|
||||
}
|
||||
|
||||
const likelyBrowserAddonIssue = errorMessage && errorMessage.includes('NotFoundError');
|
||||
const likelyBrowserAddonIssue = errorMessage && errorMessage.includes("NotFoundError");
|
||||
|
||||
return (
|
||||
<div className='error-boundary'>
|
||||
@@ -98,7 +98,7 @@ export default class ErrorBoundary extends PureComponent {
|
||||
)}
|
||||
</p>
|
||||
|
||||
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
|
||||
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? "copied" : ""}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
key: string;
|
||||
alt?: string;
|
||||
lang?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
onClick?: () => void;
|
||||
src: string,
|
||||
key: string,
|
||||
alt?: string,
|
||||
lang?: string,
|
||||
width: number,
|
||||
height: number,
|
||||
onClick?: () => void,
|
||||
}
|
||||
|
||||
export const GIFV: React.FC<Props> = ({
|
||||
@@ -36,7 +36,7 @@ export const GIFV: React.FC<Props> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='gifv' style={{ position: 'relative' }}>
|
||||
<div className='gifv' style={{ position: "relative" }}>
|
||||
{loading && (
|
||||
<canvas
|
||||
width={width}
|
||||
@@ -63,7 +63,7 @@ export const GIFV: React.FC<Props> = ({
|
||||
playsInline
|
||||
onClick={handleClick}
|
||||
onLoadedData={handleLoadedData}
|
||||
style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
|
||||
style={{ position: loading ? "absolute" : "static", top: 0, left: 0 }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// @ts-check
|
||||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { Component } from "react";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
|
||||
import { Sparklines, SparklinesCurve } from 'react-sparklines';
|
||||
import { Sparklines, SparklinesCurve } from "react-sparklines";
|
||||
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import { ShortNumber } from "mastodon/components/short_number";
|
||||
import { Skeleton } from "mastodon/components/skeleton";
|
||||
|
||||
class SilentErrorBoundary extends Component {
|
||||
|
||||
@@ -57,11 +57,11 @@ export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
||||
// @ts-expect-error
|
||||
export const ImmutableHashtag = ({ hashtag }) => (
|
||||
<Hashtag
|
||||
name={hashtag.get('name')}
|
||||
to={`/tags/${hashtag.get('name')}`}
|
||||
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
|
||||
name={hashtag.get("name")}
|
||||
to={`/tags/${hashtag.get("name")}`}
|
||||
people={hashtag.getIn(["history", 0, "accounts"]) * 1 + hashtag.getIn(["history", 1, "accounts"]) * 1}
|
||||
// @ts-expect-error
|
||||
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
|
||||
history={hashtag.get("history").reverse().map((day) => day.get("uses")).toArray()}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ ImmutableHashtag.propTypes = {
|
||||
|
||||
// @ts-expect-error
|
||||
const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
|
||||
<div className={classNames('trends__item', className)}>
|
||||
<div className={classNames("trends__item", className)}>
|
||||
<div className='trends__item__name'>
|
||||
<Link to={to}>
|
||||
{name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
|
||||
@@ -80,11 +80,11 @@ const Hashtag = ({ name, to, people, uses, history, className, description, with
|
||||
{description ? (
|
||||
<span>{description}</span>
|
||||
) : (
|
||||
typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
|
||||
typeof people !== "undefined" ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{typeof uses !== 'undefined' && (
|
||||
{typeof uses !== "undefined" && (
|
||||
<div className='trends__item__current'>
|
||||
<ShortNumber value={uses} />
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ const Hashtag = ({ name, to, people, uses, history, className, description, with
|
||||
<div className='trends__item__sparkline'>
|
||||
<SilentErrorBoundary>
|
||||
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
|
||||
<SparklinesCurve style={{ fill: 'none' }} />
|
||||
<SparklinesCurve style={{ fill: "none" }} />
|
||||
</Sparklines>
|
||||
</SilentErrorBoundary>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useState, useCallback } from "react";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import type { List, Record } from 'immutable';
|
||||
import { type List, type Record } from "immutable";
|
||||
|
||||
import { groupBy, minBy } from 'lodash';
|
||||
import { groupBy, minBy } from "lodash";
|
||||
|
||||
import { getStatusContent } from './status_content';
|
||||
import { getStatusContent } from "./status_content";
|
||||
|
||||
// Fit on a single line on desktop
|
||||
const VISIBLE_HASHTAGS = 3;
|
||||
@@ -16,27 +16,27 @@ const VISIBLE_HASHTAGS = 3;
|
||||
// Those types are not correct, they need to be replaced once this part of the state is typed
|
||||
export type TagLike = Record<{ name: string }>;
|
||||
export type StatusLike = Record<{
|
||||
tags: List<TagLike>;
|
||||
contentHTML: string;
|
||||
media_attachments: List<unknown>;
|
||||
spoiler_text?: string;
|
||||
tags: List<TagLike>,
|
||||
contentHTML: string,
|
||||
media_attachments: List<unknown>,
|
||||
spoiler_text?: string,
|
||||
}>;
|
||||
|
||||
function normalizeHashtag(hashtag: string) {
|
||||
return (
|
||||
hashtag && hashtag.startsWith('#') ? hashtag.slice(1) : hashtag
|
||||
).normalize('NFKC');
|
||||
hashtag && hashtag.startsWith("#") ? hashtag.slice(1) : hashtag
|
||||
).normalize("NFKC");
|
||||
}
|
||||
|
||||
function isNodeLinkHashtag(element: Node): element is HTMLLinkElement {
|
||||
return (
|
||||
element instanceof HTMLAnchorElement &&
|
||||
// it may be a <a> starting with a hashtag
|
||||
(element.textContent.startsWith('#') ||
|
||||
(element.textContent.startsWith("#") ||
|
||||
// or a #<a>
|
||||
element.previousSibling?.textContent?.[
|
||||
element.previousSibling.textContent.length - 1
|
||||
] === '#')
|
||||
] === "#")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,11 +48,13 @@ function isNodeLinkHashtag(element: Node): element is HTMLLinkElement {
|
||||
*/
|
||||
function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
|
||||
const groups = groupBy(hashtags, (tag) =>
|
||||
tag.normalize('NFKD').toLowerCase(),
|
||||
tag.normalize("NFKD").toLowerCase(),
|
||||
);
|
||||
|
||||
return Object.values(groups).map((tags) => {
|
||||
if (tags.length === 1) return tags[0];
|
||||
if (tags.length === 1) {
|
||||
return tags[0];
|
||||
}
|
||||
|
||||
// The best match is the one where we have the less difference between upper and lower case letter count
|
||||
const best = minBy(tags, (tag) => {
|
||||
@@ -72,27 +74,27 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) {
|
||||
|
||||
// Create the collator once, this is much more efficient
|
||||
const collator = new Intl.Collator(undefined, {
|
||||
sensitivity: 'base', // we use this to emulate the ASCII folding done on the server-side, hopefuly more efficiently
|
||||
sensitivity: "base", // we use this to emulate the ASCII folding done on the server-side, hopefuly more efficiently
|
||||
});
|
||||
|
||||
function localeAwareInclude(collection: string[], value: string) {
|
||||
const normalizedValue = value.normalize('NFKC');
|
||||
const normalizedValue = value.normalize("NFKC");
|
||||
|
||||
return !!collection.find(
|
||||
(item) => collator.compare(item.normalize('NFKC'), normalizedValue) === 0,
|
||||
(item) => collator.compare(item.normalize("NFKC"), normalizedValue) === 0,
|
||||
);
|
||||
}
|
||||
|
||||
// We use an intermediate function here to make it easier to test
|
||||
export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
statusContentProps: { statusContent: string };
|
||||
hashtagsInBar: string[];
|
||||
statusContentProps: { statusContent: string },
|
||||
hashtagsInBar: string[],
|
||||
} {
|
||||
let statusContent = getStatusContent(status);
|
||||
|
||||
const tagNames = status
|
||||
.get('tags')
|
||||
.map((tag) => tag.get('name'))
|
||||
.get("tags")
|
||||
.map((tag) => tag.get("name"))
|
||||
.toJS();
|
||||
|
||||
// this is returned if we stop the processing early, it does not change what is displayed
|
||||
@@ -102,24 +104,30 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
};
|
||||
|
||||
// return early if this status does not have any tags
|
||||
if (tagNames.length === 0) return defaultResult;
|
||||
if (tagNames.length === 0) {
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
const template = document.createElement('template');
|
||||
const template = document.createElement("template");
|
||||
template.innerHTML = statusContent.trim();
|
||||
|
||||
const lastChild = template.content.lastChild;
|
||||
|
||||
if (!lastChild || lastChild.nodeType === Node.TEXT_NODE) return defaultResult;
|
||||
if (!lastChild || lastChild.nodeType === Node.TEXT_NODE) {
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
template.content.removeChild(lastChild);
|
||||
const contentWithoutLastLine = template;
|
||||
|
||||
// First, try to parse
|
||||
const contentHashtags = Array.from(
|
||||
contentWithoutLastLine.content.querySelectorAll<HTMLLinkElement>('a[href]'),
|
||||
contentWithoutLastLine.content.querySelectorAll<HTMLLinkElement>("a[href]"),
|
||||
).reduce<string[]>((result, link) => {
|
||||
if (isNodeLinkHashtag(link)) {
|
||||
if (link.textContent) result.push(normalizeHashtag(link.textContent));
|
||||
if (link.textContent) {
|
||||
result.push(normalizeHashtag(link.textContent));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, []);
|
||||
@@ -129,7 +137,7 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
// try to see if the last line is only hashtags
|
||||
let onlyHashtags = true;
|
||||
|
||||
const normalizedTagNames = tagNames.map((tag) => tag.normalize('NFKC'));
|
||||
const normalizedTagNames = tagNames.map((tag) => tag.normalize("NFKC"));
|
||||
|
||||
Array.from(lastChild.childNodes).forEach((node) => {
|
||||
if (isNodeLinkHashtag(node) && node.textContent) {
|
||||
@@ -141,9 +149,10 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!localeAwareInclude(contentHashtags, normalized))
|
||||
// only add it if it does not appear in the rest of the content
|
||||
// only add it if it does not appear in the rest of the content
|
||||
if (!localeAwareInclude(contentHashtags, normalized)) {
|
||||
lastLineHashtags.push(normalized);
|
||||
}
|
||||
} else if (node.nodeType !== Node.TEXT_NODE || node.nodeValue?.trim()) {
|
||||
// not a space
|
||||
onlyHashtags = false;
|
||||
@@ -151,7 +160,7 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
});
|
||||
|
||||
const hashtagsInBar = tagNames.filter((tag) => {
|
||||
const normalizedTag = tag.normalize('NFKC');
|
||||
const normalizedTag = tag.normalize("NFKC");
|
||||
// the tag does not appear at all in the status content, it is an out-of-band tag
|
||||
return (
|
||||
!localeAwareInclude(contentHashtags, normalizedTag) &&
|
||||
@@ -160,8 +169,8 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
});
|
||||
|
||||
const isOnlyOneLine = contentWithoutLastLine.content.childElementCount === 0;
|
||||
const hasMedia = status.get('media_attachments').size > 0;
|
||||
const hasSpoiler = !!status.get('spoiler_text');
|
||||
const hasMedia = status.get("media_attachments").size > 0;
|
||||
const hasSpoiler = !!status.get("spoiler_text");
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- due to https://github.com/microsoft/TypeScript/issues/9998
|
||||
if (onlyHashtags && ((hasMedia && !hasSpoiler) || !isOnlyOneLine)) {
|
||||
@@ -179,25 +188,8 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will process a status to, at the same time (avoiding parsing it twice):
|
||||
* - build the HashtagBar for this status
|
||||
* - remove the last-line hashtags from the status content
|
||||
* @param status The status to process
|
||||
* @returns Props to be passed to the <StatusContent> component, and the hashtagBar to render
|
||||
*/
|
||||
export function getHashtagBarForStatus(status: StatusLike) {
|
||||
const { statusContentProps, hashtagsInBar } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
return {
|
||||
statusContentProps,
|
||||
hashtagBar: <HashtagBar hashtags={hashtagsInBar} />,
|
||||
};
|
||||
}
|
||||
|
||||
const HashtagBar: React.FC<{
|
||||
hashtags: string[];
|
||||
hashtags: string[],
|
||||
}> = ({ hashtags }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const handleClick = useCallback(() => {
|
||||
@@ -232,3 +224,21 @@ const HashtagBar: React.FC<{
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will process a status to, at the same time (avoiding parsing it twice):
|
||||
* - build the HashtagBar for this status
|
||||
* - remove the last-line hashtags from the status content
|
||||
* @param status The status to process
|
||||
* @returns Props to be passed to the <StatusContent> component, and the hashtagBar to render
|
||||
*/
|
||||
export function getHashtagBarForStatus(status: StatusLike) {
|
||||
const { statusContentProps, hashtagsInBar } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
return {
|
||||
statusContentProps,
|
||||
hashtagBar: <HashtagBar hashtags={hashtagsInBar} />,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLImageElement> {
|
||||
id: string;
|
||||
className?: string;
|
||||
fixedWidth?: boolean;
|
||||
children?: never;
|
||||
id: string,
|
||||
className?: string,
|
||||
fixedWidth?: boolean,
|
||||
children?: never,
|
||||
}
|
||||
|
||||
export const Icon: React.FC<Props> = ({
|
||||
@@ -14,7 +14,7 @@ export const Icon: React.FC<Props> = ({
|
||||
...other
|
||||
}) => (
|
||||
<i
|
||||
className={classNames('fa', `fa-${id}`, className, { 'fa-fw': fixedWidth })}
|
||||
className={classNames("fa", `fa-${id}`, className, { "fa-fw": fixedWidth })}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
import { Icon } from './icon';
|
||||
import { AnimatedNumber } from "./animated_number";
|
||||
import { Icon } from "./icon";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>;
|
||||
size: number;
|
||||
active: boolean;
|
||||
expanded?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
activeStyle?: React.CSSProperties;
|
||||
disabled: boolean;
|
||||
inverted?: boolean;
|
||||
animate: boolean;
|
||||
overlay: boolean;
|
||||
tabIndex: number;
|
||||
counter?: number;
|
||||
href?: string;
|
||||
ariaHidden: boolean;
|
||||
className?: string,
|
||||
title: string,
|
||||
icon: string,
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>,
|
||||
onMouseDown?: React.MouseEventHandler<HTMLButtonElement>,
|
||||
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>,
|
||||
onKeyPress?: React.KeyboardEventHandler<HTMLButtonElement>,
|
||||
size: number,
|
||||
active: boolean,
|
||||
expanded?: boolean,
|
||||
style?: React.CSSProperties,
|
||||
activeStyle?: React.CSSProperties,
|
||||
disabled: boolean,
|
||||
inverted?: boolean,
|
||||
animate: boolean,
|
||||
overlay: boolean,
|
||||
tabIndex: number,
|
||||
counter?: number,
|
||||
href?: string,
|
||||
ariaHidden: boolean,
|
||||
}
|
||||
interface States {
|
||||
activate: boolean;
|
||||
deactivate: boolean;
|
||||
activate: boolean,
|
||||
deactivate: boolean,
|
||||
}
|
||||
export class IconButton extends PureComponent<Props, States> {
|
||||
static defaultProps = {
|
||||
@@ -48,7 +48,9 @@ export class IconButton extends PureComponent<Props, States> {
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
||||
if (!nextProps.animate) return;
|
||||
if (!nextProps.animate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.active && !nextProps.active) {
|
||||
this.setState({ activate: false, deactivate: true });
|
||||
@@ -65,12 +67,6 @@ export class IconButton extends PureComponent<Props, States> {
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyPress: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {
|
||||
if (this.props.onKeyPress && !this.props.disabled) {
|
||||
this.props.onKeyPress(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseDown: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
if (!this.props.disabled && this.props.onMouseDown) {
|
||||
this.props.onMouseDown(e);
|
||||
@@ -110,24 +106,24 @@ export class IconButton extends PureComponent<Props, States> {
|
||||
|
||||
const { activate, deactivate } = this.state;
|
||||
|
||||
const classes = classNames(className, 'icon-button', {
|
||||
const classes = classNames(className, "icon-button", {
|
||||
active,
|
||||
disabled,
|
||||
inverted,
|
||||
activate,
|
||||
deactivate,
|
||||
overlayed: overlay,
|
||||
'icon-button--with-counter': typeof counter !== 'undefined',
|
||||
"icon-button--with-counter": typeof counter !== "undefined",
|
||||
});
|
||||
|
||||
if (typeof counter !== 'undefined') {
|
||||
style.width = 'auto';
|
||||
if (typeof counter !== "undefined") {
|
||||
style.width = "auto";
|
||||
}
|
||||
|
||||
let contents = (
|
||||
<>
|
||||
<Icon id={icon} fixedWidth aria-hidden='true' />{' '}
|
||||
{typeof counter !== 'undefined' && (
|
||||
<Icon id={icon} fixedWidth aria-hidden='true' />{" "}
|
||||
{typeof counter !== "undefined" && (
|
||||
<span className='icon-button__counter'>
|
||||
<AnimatedNumber value={counter} />
|
||||
</span>
|
||||
@@ -154,7 +150,6 @@ export class IconButton extends PureComponent<Props, States> {
|
||||
onClick={this.handleClick}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
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);
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
count: number;
|
||||
issueBadge: boolean;
|
||||
className: string;
|
||||
id: string,
|
||||
count: number,
|
||||
issueBadge: boolean,
|
||||
className: string,
|
||||
}
|
||||
export const IconWithBadge: React.FC<Props> = ({
|
||||
id,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
import { Avatar } from "mastodon/components/avatar";
|
||||
import { makeGetAccount } from "mastodon/selectors";
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
@@ -27,7 +27,7 @@ class InlineAccount extends PureComponent {
|
||||
|
||||
return (
|
||||
<span className='inline-account'>
|
||||
<Avatar size={13} account={account} /> <strong>{account.get('username')}</strong>
|
||||
<Avatar size={13} account={account} /> <strong>{account.get("username")}</strong>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { cloneElement, Component } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { cloneElement, Component } from "react";
|
||||
|
||||
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
|
||||
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
|
||||
import getRectFromEntry from "../features/ui/util/get_rect_from_entry";
|
||||
import scheduleIdleTask from "../features/ui/util/schedule_idle_task";
|
||||
|
||||
// Diff these props in the "unrendered" state
|
||||
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
|
||||
const updateOnPropsForUnrendered = ["id", "index", "listLength", "cachedHeight"];
|
||||
|
||||
export default class IntersectionObserverArticle extends Component {
|
||||
|
||||
@@ -112,7 +112,7 @@ export default class IntersectionObserverArticle extends Component {
|
||||
ref={this.handleRef}
|
||||
aria-posinset={index + 1}
|
||||
aria-setsize={listLength}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: "hidden" }}
|
||||
data-id={id}
|
||||
tabIndex={-1}
|
||||
>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import { useIntl, defineMessages } from "react-intl";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
|
||||
const messages = defineMessages({
|
||||
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
||||
load_more: { id: "status.load_more", defaultMessage: "Load more" },
|
||||
});
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
maxId: string;
|
||||
onClick: (maxId: string) => void;
|
||||
disabled: boolean,
|
||||
maxId: string,
|
||||
onClick: (maxId: string) => void,
|
||||
}
|
||||
|
||||
export const LoadGap: React.FC<Props> = ({ disabled, maxId, onClick }) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface Props {
|
||||
onClick: (event: React.MouseEvent) => void;
|
||||
disabled?: boolean;
|
||||
visible?: boolean;
|
||||
onClick: (event: React.MouseEvent) => void,
|
||||
disabled?: boolean,
|
||||
visible?: boolean,
|
||||
}
|
||||
export const LoadMore: React.FC<Props> = ({
|
||||
onClick,
|
||||
@@ -15,7 +15,7 @@ export const LoadMore: React.FC<Props> = ({
|
||||
type='button'
|
||||
className='load-more'
|
||||
disabled={disabled || !visible}
|
||||
style={{ visibility: visible ? 'visible' : 'hidden' }}
|
||||
style={{ visibility: visible ? "visible" : "hidden" }}
|
||||
onClick={onClick}
|
||||
>
|
||||
<FormattedMessage id='status.load_more' defaultMessage='Load more' />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface Props {
|
||||
onClick: (event: React.MouseEvent) => void;
|
||||
count: number;
|
||||
onClick: (event: React.MouseEvent) => void,
|
||||
count: number,
|
||||
}
|
||||
|
||||
export const LoadPending: React.FC<Props> = ({ onClick, count }) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CircularProgress } from './circular_progress';
|
||||
import { CircularProgress } from "./circular_progress";
|
||||
|
||||
export const LoadingIndicator: React.FC = () => (
|
||||
<div className='loading-indicator'>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import noop from 'lodash/noop';
|
||||
import noop from "lodash/noop";
|
||||
|
||||
import Bundle from 'mastodon/features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from 'mastodon/features/ui/util/async-components';
|
||||
import Bundle from "mastodon/features/ui/components/bundle";
|
||||
import { MediaGallery, Video, Audio } from "mastodon/features/ui/util/async-components";
|
||||
|
||||
export default class MediaAttachments extends ImmutablePureComponent {
|
||||
|
||||
@@ -23,7 +23,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
updateOnProps = [
|
||||
'status',
|
||||
"status",
|
||||
];
|
||||
|
||||
renderLoadingMediaGallery = () => {
|
||||
@@ -52,53 +52,53 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||
|
||||
render () {
|
||||
const { status, width, height } = this.props;
|
||||
const mediaAttachments = status.get('media_attachments');
|
||||
const language = status.getIn(['language', 'translation']) || status.get('language') || this.props.lang;
|
||||
const mediaAttachments = status.get("media_attachments");
|
||||
const language = status.getIn(["language", "translation"]) || status.get("language") || this.props.lang;
|
||||
|
||||
if (mediaAttachments.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mediaAttachments.getIn([0, 'type']) === 'audio') {
|
||||
if (mediaAttachments.getIn([0, "type"]) === "audio") {
|
||||
const audio = mediaAttachments.get(0);
|
||||
const description = audio.getIn(['translation', 'description']) || audio.get('description');
|
||||
const description = audio.getIn(["translation", "description"]) || audio.get("description");
|
||||
|
||||
return (
|
||||
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
src={audio.get('url')}
|
||||
src={audio.get("url")}
|
||||
alt={description}
|
||||
lang={language}
|
||||
width={width}
|
||||
height={height}
|
||||
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
backgroundColor={audio.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={audio.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={audio.getIn(['meta', 'colors', 'accent'])}
|
||||
duration={audio.getIn(['meta', 'original', 'duration'], 0)}
|
||||
poster={audio.get("preview_url") || status.getIn(["account", "avatar_static"])}
|
||||
backgroundColor={audio.getIn(["meta", "colors", "background"])}
|
||||
foregroundColor={audio.getIn(["meta", "colors", "foreground"])}
|
||||
accentColor={audio.getIn(["meta", "colors", "accent"])}
|
||||
duration={audio.getIn(["meta", "original", "duration"], 0)}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else if (mediaAttachments.getIn([0, 'type']) === 'video') {
|
||||
} else if (mediaAttachments.getIn([0, "type"]) === "video") {
|
||||
const video = mediaAttachments.get(0);
|
||||
const description = video.getIn(['translation', 'description']) || video.get('description');
|
||||
const description = video.getIn(["translation", "description"]) || video.get("description");
|
||||
|
||||
return (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
preview={video.get('preview_url')}
|
||||
frameRate={video.getIn(['meta', 'original', 'frame_rate'])}
|
||||
blurhash={video.get('blurhash')}
|
||||
src={video.get('url')}
|
||||
preview={video.get("preview_url")}
|
||||
frameRate={video.getIn(["meta", "original", "frame_rate"])}
|
||||
blurhash={video.get("blurhash")}
|
||||
src={video.get("url")}
|
||||
alt={description}
|
||||
lang={language}
|
||||
width={width}
|
||||
height={height}
|
||||
inline
|
||||
sensitive={status.get('sensitive')}
|
||||
sensitive={status.get("sensitive")}
|
||||
onOpenVideo={noop}
|
||||
/>
|
||||
)}
|
||||
@@ -111,7 +111,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||
<Component
|
||||
media={mediaAttachments}
|
||||
lang={language}
|
||||
sensitive={status.get('sensitive')}
|
||||
sensitive={status.get("sensitive")}
|
||||
defaultWidth={width}
|
||||
height={height}
|
||||
onOpenMedia={noop}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { is } from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { is } from "immutable";
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import { debounce } from "lodash";
|
||||
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
import { Blurhash } from "mastodon/components/blurhash";
|
||||
|
||||
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
|
||||
import { autoPlayGif, displayMedia, useBlurhash } from "../initial_state";
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
import { IconButton } from "./icon_button";
|
||||
|
||||
const messages = defineMessages({
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: '{number, plural, one {Hide image} other {Hide images}}' },
|
||||
toggle_visible: { id: "media_gallery.toggle_visible", defaultMessage: "{number, plural, one {Hide image} other {Hide images}}" },
|
||||
});
|
||||
|
||||
class Item extends PureComponent {
|
||||
@@ -63,7 +63,7 @@ class Item extends PureComponent {
|
||||
|
||||
hoverToPlay () {
|
||||
const { attachment } = this.props;
|
||||
return !this.getAutoPlay() && attachment.get('type') === 'gifv';
|
||||
return !this.getAutoPlay() && attachment.get("type") === "gifv";
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
@@ -101,45 +101,45 @@ class Item extends PureComponent {
|
||||
height = 50;
|
||||
}
|
||||
|
||||
if (attachment.get('description')?.length > 0) {
|
||||
if (attachment.get("description")?.length > 0) {
|
||||
badges.push(<span key='alt' className='media-gallery__gifv__label'>ALT</span>);
|
||||
}
|
||||
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
const description = attachment.getIn(["translation", "description"]) || attachment.get("description");
|
||||
|
||||
if (attachment.get('type') === 'unknown') {
|
||||
if (attachment.get("type") === "unknown") {
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={description} lang={lang} target='_blank' rel='noopener noreferrer'>
|
||||
<div className={classNames("media-gallery__item", { standalone, "media-gallery__item--tall": height === 100, "media-gallery__item--wide": width === 100 })} key={attachment.get("id")}>
|
||||
<a className='media-gallery__item-thumbnail' href={attachment.get("remote_url") || attachment.get("url")} style={{ cursor: "pointer" }} title={description} lang={lang} target='_blank' rel='noopener noreferrer'>
|
||||
<Blurhash
|
||||
hash={attachment.get('blurhash')}
|
||||
hash={attachment.get("blurhash")}
|
||||
className='media-gallery__preview'
|
||||
dummy={!useBlurhash}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else if (attachment.get('type') === 'image') {
|
||||
const previewUrl = attachment.get('preview_url');
|
||||
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
|
||||
} else if (attachment.get("type") === "image") {
|
||||
const previewUrl = attachment.get("preview_url");
|
||||
const previewWidth = attachment.getIn(["meta", "small", "width"]);
|
||||
|
||||
const originalUrl = attachment.get('url');
|
||||
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
|
||||
const originalUrl = attachment.get("url");
|
||||
const originalWidth = attachment.getIn(["meta", "original", "width"]);
|
||||
|
||||
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
|
||||
const hasSize = typeof originalWidth === "number" && typeof previewWidth === "number";
|
||||
|
||||
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
|
||||
const sizes = hasSize && (displayWidth > 0) ? `${displayWidth * (width / 100)}px` : null;
|
||||
|
||||
const focusX = attachment.getIn(['meta', 'focus', 'x']) || 0;
|
||||
const focusY = attachment.getIn(['meta', 'focus', 'y']) || 0;
|
||||
const focusX = attachment.getIn(["meta", "focus", "x"]) || 0;
|
||||
const focusY = attachment.getIn(["meta", "focus", "y"]) || 0;
|
||||
const x = ((focusX / 2) + .5) * 100;
|
||||
const y = ((focusY / -2) + .5) * 100;
|
||||
|
||||
thumbnail = (
|
||||
<a
|
||||
className='media-gallery__item-thumbnail'
|
||||
href={attachment.get('remote_url') || originalUrl}
|
||||
href={attachment.get("remote_url") || originalUrl}
|
||||
onClick={this.handleClick}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
@@ -156,20 +156,20 @@ class Item extends PureComponent {
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
} else if (attachment.get('type') === 'gifv') {
|
||||
} else if (attachment.get("type") === "gifv") {
|
||||
const autoPlay = this.getAutoPlay();
|
||||
|
||||
badges.push(<span key='gif' className='media-gallery__gifv__label'>GIF</span>);
|
||||
|
||||
thumbnail = (
|
||||
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
||||
<div className={classNames("media-gallery__gifv", { autoplay: autoPlay })}>
|
||||
<video
|
||||
className='media-gallery__item-gifv-thumbnail'
|
||||
aria-label={description}
|
||||
title={description}
|
||||
lang={lang}
|
||||
role='application'
|
||||
src={attachment.get('url')}
|
||||
src={attachment.get("url")}
|
||||
onClick={this.handleClick}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
@@ -183,12 +183,12 @@ class Item extends PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('media-gallery__item', { standalone, 'media-gallery__item--tall': height === 100, 'media-gallery__item--wide': width === 100 })} key={attachment.get('id')}>
|
||||
<div className={classNames("media-gallery__item", { standalone, "media-gallery__item--tall": height === 100, "media-gallery__item--wide": width === 100 })} key={attachment.get("id")}>
|
||||
<Blurhash
|
||||
hash={attachment.get('blurhash')}
|
||||
hash={attachment.get("blurhash")}
|
||||
dummy={!useBlurhash}
|
||||
className={classNames('media-gallery__preview', {
|
||||
'media-gallery__preview--hidden': visible && this.state.loaded,
|
||||
className={classNames("media-gallery__preview", {
|
||||
"media-gallery__preview--hidden": visible && this.state.loaded,
|
||||
})}
|
||||
/>
|
||||
|
||||
@@ -223,21 +223,21 @@ class MediaGallery extends PureComponent {
|
||||
};
|
||||
|
||||
state = {
|
||||
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
||||
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== "hide_all" && !this.props.sensitive || displayMedia === "show_all"),
|
||||
width: this.props.defaultWidth,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('resize', this.handleResize, { passive: true });
|
||||
window.addEventListener("resize", this.handleResize, { passive: true });
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
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) {
|
||||
this.setState({ visible: nextProps.visible });
|
||||
}
|
||||
@@ -286,7 +286,7 @@ class MediaGallery extends PureComponent {
|
||||
|
||||
isFullSizeEligible() {
|
||||
const { media } = this.props;
|
||||
return media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
|
||||
return media.size === 1 && media.getIn([0, "meta", "small", "aspect"]);
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -299,18 +299,18 @@ class MediaGallery extends PureComponent {
|
||||
const style = {};
|
||||
|
||||
if (this.isFullSizeEligible()) {
|
||||
style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
|
||||
style.aspectRatio = `${this.props.media.getIn([0, "meta", "small", "aspect"])}`;
|
||||
} else {
|
||||
style.aspectRatio = '3 / 2';
|
||||
style.aspectRatio = "3 / 2";
|
||||
}
|
||||
|
||||
const size = media.take(4).size;
|
||||
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
||||
const uncached = media.every(attachment => attachment.get("type") === "unknown");
|
||||
|
||||
if (this.isFullSizeEligible()) {
|
||||
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
|
||||
} else {
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get("id")} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);
|
||||
}
|
||||
|
||||
if (uncached) {
|
||||
@@ -337,7 +337,7 @@ class MediaGallery extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className='media-gallery' style={style} ref={this.handleRef}>
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}>
|
||||
<div className={classNames("spoiler-button", { "spoiler-button--minified": visible && !uncached, "spoiler-button--click-thru": uncached })}>
|
||||
{spoilerButton}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import 'wicg-inert';
|
||||
import { multiply } from 'color-blend';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import "wicg-inert";
|
||||
import { multiply } from "color-blend";
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
export default class ModalRoot extends PureComponent {
|
||||
|
||||
@@ -25,15 +25,15 @@ export default class ModalRoot extends PureComponent {
|
||||
activeElement = this.props.children ? document.activeElement : null;
|
||||
|
||||
handleKeyUp = (e) => {
|
||||
if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
|
||||
if ((e.key === "Escape" || e.key === "Esc" || e.keyCode === 27)
|
||||
&& !!this.props.children) {
|
||||
this.props.onClose();
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
const focusable = Array.from(this.node.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((x) => window.getComputedStyle(x).display !== 'none');
|
||||
if (e.key === "Tab") {
|
||||
const focusable = Array.from(this.node.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])")).filter((x) => window.getComputedStyle(x).display !== "none");
|
||||
const index = focusable.indexOf(e.target);
|
||||
|
||||
let element;
|
||||
@@ -53,8 +53,8 @@ export default class ModalRoot extends PureComponent {
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('keyup', this.handleKeyUp, false);
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
window.addEventListener("keyup", this.handleKeyUp, false);
|
||||
window.addEventListener("keydown", this.handleKeyDown, false);
|
||||
this.history = this.context.router ? this.context.router.history : createBrowserHistory();
|
||||
}
|
||||
|
||||
@@ -62,13 +62,13 @@ export default class ModalRoot extends PureComponent {
|
||||
if (!!nextProps.children && !this.props.children) {
|
||||
this.activeElement = document.activeElement;
|
||||
|
||||
this.getSiblings().forEach(sibling => sibling.setAttribute('inert', true));
|
||||
this.getSiblings().forEach(sibling => sibling.setAttribute("inert", true));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.props.children && !!prevProps.children) {
|
||||
this.getSiblings().forEach(sibling => sibling.removeAttribute('inert'));
|
||||
this.getSiblings().forEach(sibling => sibling.removeAttribute("inert"));
|
||||
|
||||
// Because of the wicg-inert polyfill, the activeElement may not be
|
||||
// immediately selectable, we have to wait for observers to run, as
|
||||
@@ -91,14 +91,14 @@ export default class ModalRoot extends PureComponent {
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keyup', this.handleKeyUp);
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
window.removeEventListener("keyup", this.handleKeyUp);
|
||||
window.removeEventListener("keydown", this.handleKeyDown);
|
||||
}
|
||||
|
||||
_handleModalOpen () {
|
||||
this._modalHistoryKey = Date.now();
|
||||
this.unlistenHistory = this.history.listen((_, action) => {
|
||||
if (action === 'POP') {
|
||||
if (action === "POP") {
|
||||
this.props.onClose();
|
||||
}
|
||||
});
|
||||
@@ -147,7 +147,7 @@ export default class ModalRoot extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className='modal-root' ref={this.setRef}>
|
||||
<div style={{ pointerEvents: visible ? 'auto' : 'none' }}>
|
||||
<div style={{ pointerEvents: visible ? "auto" : "none" }}>
|
||||
<div role='presentation' className='modal-root__overlay' onClick={onClose} style={{ backgroundColor: backgroundColor ? `rgba(${backgroundColor.r}, ${backgroundColor.g}, ${backgroundColor.b}, 0.7)` : null }} />
|
||||
<div role='dialog' className='modal-root__container'>{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { Switch, Route, withRouter } from 'react-router-dom';
|
||||
import { Switch, Route, withRouter } from "react-router-dom";
|
||||
|
||||
import AccountNavigation from 'mastodon/features/account/navigation';
|
||||
import Trends from 'mastodon/features/getting_started/containers/trends_container';
|
||||
import { showTrends } from 'mastodon/initial_state';
|
||||
import AccountNavigation from "mastodon/features/account/navigation";
|
||||
import Trends from "mastodon/features/getting_started/containers/trends_container";
|
||||
import { showTrends } from "mastodon/initial_state";
|
||||
|
||||
const DefaultNavigation = () => (
|
||||
showTrends ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export const NotSignedInIndicator: React.FC = () => (
|
||||
<div className='scrollable scrollable--flex'>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { removePictureInPicture } from "mastodon/actions/picture_in_picture";
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
|
||||
class PictureInPicturePlaceholder extends PureComponent {
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import escapeTextContentForBrowser from "escape-html";
|
||||
import spring from "react-motion/lib/spring";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
import Motion from 'mastodon/features/ui/util/optional_motion';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
import emojify from "mastodon/features/emoji/emoji";
|
||||
import Motion from "mastodon/features/ui/util/optional_motion";
|
||||
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
import { RelativeTimestamp } from "./relative_timestamp";
|
||||
|
||||
const messages = defineMessages({
|
||||
closed: {
|
||||
id: 'poll.closed',
|
||||
defaultMessage: 'Closed',
|
||||
id: "poll.closed",
|
||||
defaultMessage: "Closed",
|
||||
},
|
||||
voted: {
|
||||
id: 'poll.voted',
|
||||
defaultMessage: 'You voted for this answer',
|
||||
id: "poll.voted",
|
||||
defaultMessage: "You voted for this answer",
|
||||
},
|
||||
votes: {
|
||||
id: 'poll.votes',
|
||||
defaultMessage: '{votes, plural, one {# vote} other {# votes}}',
|
||||
id: "poll.votes",
|
||||
defaultMessage: "{votes, plural, one {# vote} other {# votes}}",
|
||||
},
|
||||
});
|
||||
|
||||
const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
|
||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
|
||||
const makeEmojiMap = record => record.get("emojis").reduce((obj, emoji) => {
|
||||
obj[`:${emoji.get("shortcode")}:`] = emoji.toJS();
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
@@ -58,8 +58,8 @@ class Poll extends ImmutablePureComponent {
|
||||
|
||||
static getDerivedStateFromProps (props, state) {
|
||||
const { poll } = props;
|
||||
const expires_at = poll.get('expires_at');
|
||||
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
|
||||
const expires_at = poll.get("expires_at");
|
||||
const expired = poll.get("expired") || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
|
||||
return (expired === state.expired) ? null : { expired };
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class Poll extends ImmutablePureComponent {
|
||||
const { poll } = this.props;
|
||||
clearTimeout(this._timer);
|
||||
if (!this.state.expired) {
|
||||
const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now();
|
||||
const delay = (new Date(poll.get("expires_at"))).getTime() - Date.now();
|
||||
this._timer = setTimeout(() => {
|
||||
this.setState({ expired: true });
|
||||
}, delay);
|
||||
@@ -87,7 +87,7 @@ class Poll extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
_toggleOption = value => {
|
||||
if (this.props.poll.get('multiple')) {
|
||||
if (this.props.poll.get("multiple")) {
|
||||
const tmp = { ...this.state.selected };
|
||||
if (tmp[value]) {
|
||||
delete tmp[value];
|
||||
@@ -107,8 +107,8 @@ class Poll extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleOptionKeyPress = (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
this._toggleOption(e.target.getAttribute('data-index'));
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
this._toggleOption(e.target.getAttribute("data-index"));
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -136,14 +136,14 @@ class Poll extends ImmutablePureComponent {
|
||||
|
||||
renderOption (option, optionIndex, showResults) {
|
||||
const { poll, lang, disabled, intl } = this.props;
|
||||
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
|
||||
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
|
||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
|
||||
const pollVotesCount = poll.get("voters_count") || poll.get("votes_count");
|
||||
const percent = pollVotesCount === 0 ? 0 : (option.get("votes_count") / pollVotesCount) * 100;
|
||||
const leading = poll.get("options").filterNot(other => other.get("title") === option.get("title")).every(other => option.get("votes_count") >= other.get("votes_count"));
|
||||
const active = !!this.state.selected[`${optionIndex}`];
|
||||
const voted = option.get('voted') || (poll.get('own_votes') && poll.get('own_votes').includes(optionIndex));
|
||||
const voted = option.get("voted") || (poll.get("own_votes") && poll.get("own_votes").includes(optionIndex));
|
||||
|
||||
const title = option.getIn(['translation', 'title']) || option.get('title');
|
||||
let titleHtml = option.getIn(['translation', 'titleHtml']) || option.get('titleHtml');
|
||||
const title = option.getIn(["translation", "title"]) || option.get("title");
|
||||
let titleHtml = option.getIn(["translation", "titleHtml"]) || option.get("titleHtml");
|
||||
|
||||
if (!titleHtml) {
|
||||
const emojiMap = makeEmojiMap(poll);
|
||||
@@ -151,11 +151,11 @@ class Poll extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={option.get('title')}>
|
||||
<label className={classNames('poll__option', { selectable: !showResults })}>
|
||||
<li key={option.get("title")}>
|
||||
<label className={classNames("poll__option", { selectable: !showResults })}>
|
||||
<input
|
||||
name='vote-options'
|
||||
type={poll.get('multiple') ? 'checkbox' : 'radio'}
|
||||
type={poll.get("multiple") ? "checkbox" : "radio"}
|
||||
value={optionIndex}
|
||||
checked={active}
|
||||
onChange={this.handleOptionChange}
|
||||
@@ -164,9 +164,9 @@ class Poll extends ImmutablePureComponent {
|
||||
|
||||
{!showResults && (
|
||||
<span
|
||||
className={classNames('poll__input', { checkbox: poll.get('multiple'), active })}
|
||||
className={classNames("poll__input", { checkbox: poll.get("multiple"), active })}
|
||||
tabIndex={0}
|
||||
role={poll.get('multiple') ? 'checkbox' : 'radio'}
|
||||
role={poll.get("multiple") ? "checkbox" : "radio"}
|
||||
onKeyPress={this.handleOptionKeyPress}
|
||||
aria-checked={active}
|
||||
aria-label={title}
|
||||
@@ -178,7 +178,7 @@ class Poll extends ImmutablePureComponent {
|
||||
<span
|
||||
className='poll__number'
|
||||
title={intl.formatMessage(messages.votes, {
|
||||
votes: option.get('votes_count'),
|
||||
votes: option.get("votes_count"),
|
||||
})}
|
||||
>
|
||||
{Math.round(percent)}%
|
||||
@@ -199,7 +199,7 @@ class Poll extends ImmutablePureComponent {
|
||||
{showResults && (
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
|
||||
{({ width }) =>
|
||||
<span className={classNames('poll__chart', { leading })} style={{ width: `${width}%` }} />
|
||||
<span className={classNames("poll__chart", { leading })} style={{ width: `${width}%` }} />
|
||||
}
|
||||
</Motion>
|
||||
)}
|
||||
@@ -215,22 +215,22 @@ class Poll extends ImmutablePureComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
|
||||
const showResults = poll.get('voted') || revealed || expired;
|
||||
const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get("expires_at")} futureDate />;
|
||||
const showResults = poll.get("voted") || revealed || expired;
|
||||
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
|
||||
|
||||
let votesCount = null;
|
||||
|
||||
if (poll.get('voters_count') !== null && poll.get('voters_count') !== undefined) {
|
||||
votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get('voters_count') }} />;
|
||||
if (poll.get("voters_count") !== null && poll.get("voters_count") !== undefined) {
|
||||
votesCount = <FormattedMessage id='poll.total_people' defaultMessage='{count, plural, one {# person} other {# people}}' values={{ count: poll.get("voters_count") }} />;
|
||||
} else {
|
||||
votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get('votes_count') }} />;
|
||||
votesCount = <FormattedMessage id='poll.total_votes' defaultMessage='{count, plural, one {# vote} other {# votes}}' values={{ count: poll.get("votes_count") }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='poll'>
|
||||
<ul>
|
||||
{poll.get('options').map((option, i) => this.renderOption(option, i, showResults))}
|
||||
{poll.get("options").map((option, i) => this.renderOption(option, i, showResults))}
|
||||
</ul>
|
||||
|
||||
<div className='poll__footer'>
|
||||
@@ -238,7 +238,7 @@ class Poll extends ImmutablePureComponent {
|
||||
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
|
||||
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
|
||||
{votesCount}
|
||||
{poll.get('expires_at') && <> · {timeRemaining}</>}
|
||||
{poll.get("expires_at") && <> · {timeRemaining}</>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
checked: boolean;
|
||||
name: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
label: React.ReactNode;
|
||||
value: string,
|
||||
checked: boolean,
|
||||
name: string,
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void,
|
||||
label: React.ReactNode,
|
||||
}
|
||||
|
||||
export const RadioButton: React.FC<Props> = ({
|
||||
@@ -25,7 +25,7 @@ export const RadioButton: React.FC<Props> = ({
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<span className={classNames('radio-button__input', { checked })} />
|
||||
<span className={classNames("radio-button__input", { checked })} />
|
||||
|
||||
<span>{label}</span>
|
||||
</label>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import illustration from 'mastodon/../images/elephant_ui_working.svg';
|
||||
import illustration from "mastodon/../images/elephant_ui_working.svg";
|
||||
|
||||
const RegenerationIndicator = () => (
|
||||
<div className='regeneration-indicator'>
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
import { Component } from 'react';
|
||||
import { Component } from "react";
|
||||
|
||||
import type { IntlShape } from 'react-intl';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
import { type IntlShape } from "react-intl";
|
||||
import { injectIntl, defineMessages } from "react-intl";
|
||||
|
||||
const messages = defineMessages({
|
||||
today: { id: 'relative_time.today', defaultMessage: 'today' },
|
||||
just_now: { id: 'relative_time.just_now', defaultMessage: 'now' },
|
||||
today: { id: "relative_time.today", defaultMessage: "today" },
|
||||
just_now: { id: "relative_time.just_now", defaultMessage: "now" },
|
||||
just_now_full: {
|
||||
id: 'relative_time.full.just_now',
|
||||
defaultMessage: 'just now',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
id: "relative_time.full.days",
|
||||
defaultMessage: "{number, plural, one {# day} other {# days}} ago",
|
||||
},
|
||||
moments_remaining: {
|
||||
id: 'time_remaining.moments',
|
||||
defaultMessage: 'Moments remaining',
|
||||
id: "time_remaining.moments",
|
||||
defaultMessage: "Moments remaining",
|
||||
},
|
||||
seconds_remaining: {
|
||||
id: 'time_remaining.seconds',
|
||||
defaultMessage: '{number, plural, one {# second} other {# seconds}} left',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
id: "time_remaining.days",
|
||||
defaultMessage: "{number, plural, one {# day} other {# days}} left",
|
||||
},
|
||||
});
|
||||
|
||||
const dateFormatOptions = {
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
} as const;
|
||||
|
||||
const shortDateFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
} as const;
|
||||
|
||||
const SECOND = 1000;
|
||||
@@ -77,25 +77,25 @@ const selectUnits = (delta: number) => {
|
||||
const absDelta = Math.abs(delta);
|
||||
|
||||
if (absDelta < MINUTE) {
|
||||
return 'second';
|
||||
return "second";
|
||||
} else if (absDelta < HOUR) {
|
||||
return 'minute';
|
||||
return "minute";
|
||||
} else if (absDelta < DAY) {
|
||||
return 'hour';
|
||||
return "hour";
|
||||
}
|
||||
|
||||
return 'day';
|
||||
return "day";
|
||||
};
|
||||
|
||||
const getUnitDelay = (units: string) => {
|
||||
switch (units) {
|
||||
case 'second':
|
||||
case "second":
|
||||
return SECOND;
|
||||
case 'minute':
|
||||
case "minute":
|
||||
return MINUTE;
|
||||
case 'hour':
|
||||
case "hour":
|
||||
return HOUR;
|
||||
case 'day':
|
||||
case "day":
|
||||
return DAY;
|
||||
default:
|
||||
return MAX_DELAY;
|
||||
@@ -147,7 +147,7 @@ export const timeAgoString = (
|
||||
} else {
|
||||
relativeTime = intl.formatDate(date, {
|
||||
...shortDateFormatOptions,
|
||||
year: 'numeric',
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -190,14 +190,14 @@ const timeRemainingString = (
|
||||
};
|
||||
|
||||
interface Props {
|
||||
intl: IntlShape;
|
||||
timestamp: string;
|
||||
year: number;
|
||||
futureDate?: boolean;
|
||||
short?: boolean;
|
||||
intl: IntlShape,
|
||||
timestamp: string,
|
||||
year: number,
|
||||
futureDate?: boolean,
|
||||
short?: boolean,
|
||||
}
|
||||
interface States {
|
||||
now: number;
|
||||
now: number,
|
||||
}
|
||||
class RelativeTimestamp extends Component<Props, States> {
|
||||
state = {
|
||||
@@ -260,7 +260,7 @@ class RelativeTimestamp extends Component<Props, States> {
|
||||
render() {
|
||||
const { timestamp, intl, year, futureDate, short } = this.props;
|
||||
|
||||
const timeGiven = timestamp.includes('T');
|
||||
const timeGiven = timestamp.includes("T");
|
||||
const date = new Date(timestamp);
|
||||
const relativeTime = futureDate
|
||||
? timeRemainingString(intl, date, this.state.now, timeGiven)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React from 'react';
|
||||
import { type PropsWithChildren } from "react";
|
||||
import React from "react";
|
||||
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { Router as OriginalRouter } from 'react-router';
|
||||
import { createBrowserHistory } from "history";
|
||||
import { Router as OriginalRouter } from "react-router";
|
||||
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
import { layoutFromWindow } from "mastodon/is_mobile";
|
||||
|
||||
interface MastodonLocationState {
|
||||
fromMastodon?: boolean;
|
||||
mastodonModalKey?: string;
|
||||
fromMastodon?: boolean,
|
||||
mastodonModalKey?: string,
|
||||
}
|
||||
|
||||
const browserHistory = createBrowserHistory<
|
||||
@@ -21,7 +21,7 @@ browserHistory.push = (path: string, state?: MastodonLocationState) => {
|
||||
state = state ?? {};
|
||||
state.fromMastodon = true;
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
|
||||
if (layoutFromWindow() === "multi-column" && !path.startsWith("/deck")) {
|
||||
originalPush(`/deck${path}`, state);
|
||||
} else {
|
||||
originalPush(path, state);
|
||||
@@ -34,7 +34,7 @@ browserHistory.replace = (path: string, state?: MastodonLocationState) => {
|
||||
state.fromMastodon = true;
|
||||
}
|
||||
|
||||
if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) {
|
||||
if (layoutFromWindow() === "multi-column" && !path.startsWith("/deck")) {
|
||||
originalReplace(`/deck${path}`, state);
|
||||
} else {
|
||||
originalReplace(path, state);
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Children, cloneElement, PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { Children, cloneElement, PureComponent } from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
import { List as ImmutableList } from "immutable";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import { throttle } from 'lodash';
|
||||
import { supportsPassiveEvents } from "detect-passive-events";
|
||||
import { throttle } from "lodash";
|
||||
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
import ScrollContainer from "mastodon/containers/scroll_container";
|
||||
|
||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
||||
import IntersectionObserverArticleContainer from "../containers/intersection_observer_article_container";
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from "../features/ui/util/fullscreen";
|
||||
import IntersectionObserverWrapper from "../features/ui/util/intersection_observer_wrapper";
|
||||
|
||||
import { LoadMore } from './load_more';
|
||||
import { LoadPending } from './load_pending';
|
||||
import { LoadingIndicator } from './loading_indicator';
|
||||
import { LoadMore } from "./load_more";
|
||||
import { LoadPending } from "./load_pending";
|
||||
import { LoadingIndicator } from "./loading_indicator";
|
||||
|
||||
const MOUSE_IDLE_DELAY = 300;
|
||||
|
||||
@@ -236,7 +236,7 @@ class ScrollableList extends PureComponent {
|
||||
attachIntersectionObserver () {
|
||||
let nodeOptions = {
|
||||
root: this.node,
|
||||
rootMargin: '300% 0px',
|
||||
rootMargin: "300% 0px",
|
||||
};
|
||||
|
||||
this.intersectionObserverWrapper
|
||||
@@ -249,21 +249,21 @@ class ScrollableList extends PureComponent {
|
||||
|
||||
attachScrollListener () {
|
||||
if (this.props.bindToDocument) {
|
||||
document.addEventListener('scroll', this.handleScroll);
|
||||
document.addEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
document.addEventListener("scroll", this.handleScroll);
|
||||
document.addEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
} else {
|
||||
this.node.addEventListener('scroll', this.handleScroll);
|
||||
this.node.addEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
this.node.addEventListener("scroll", this.handleScroll);
|
||||
this.node.addEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
detachScrollListener () {
|
||||
if (this.props.bindToDocument) {
|
||||
document.removeEventListener('scroll', this.handleScroll);
|
||||
document.removeEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
document.removeEventListener("scroll", this.handleScroll);
|
||||
document.removeEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
} else {
|
||||
this.node.removeEventListener('scroll', this.handleScroll);
|
||||
this.node.removeEventListener('wheel', this.handleWheel, listenerOptions);
|
||||
this.node.removeEventListener("scroll", this.handleScroll);
|
||||
this.node.removeEventListener("wheel", this.handleWheel, listenerOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ class ScrollableList extends PureComponent {
|
||||
);
|
||||
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
||||
scrollableArea = (
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||
<div className={classNames("scrollable", { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||
<div role='feed' className='item-list'>
|
||||
{prepend}
|
||||
|
||||
@@ -356,7 +356,7 @@ class ScrollableList extends PureComponent {
|
||||
);
|
||||
} else {
|
||||
scrollableArea = (
|
||||
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef}>
|
||||
<div className={classNames("scrollable scrollable--flex", { fullscreen })} ref={this.setRef}>
|
||||
{alwaysPrepend && prepend}
|
||||
|
||||
<div className='empty-column-indicator'>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from "react-intl";
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { fetchServer } from 'mastodon/actions/server';
|
||||
import { ServerHeroImage } from 'mastodon/components/server_hero_image';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
import { fetchServer } from "mastodon/actions/server";
|
||||
import { ServerHeroImage } from "mastodon/components/server_hero_image";
|
||||
import { ShortNumber } from "mastodon/components/short_number";
|
||||
import { Skeleton } from "mastodon/components/skeleton";
|
||||
import Account from "mastodon/containers/account_container";
|
||||
import { domain } from "mastodon/initial_state";
|
||||
|
||||
const messages = defineMessages({
|
||||
aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' },
|
||||
aboutActiveUsers: { id: "server_banner.about_active_users", defaultMessage: "People using this server during the last 30 days (Monthly Active Users)" },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
server: state.getIn(['server', 'server']),
|
||||
server: state.getIn(["server", "server"]),
|
||||
});
|
||||
|
||||
class ServerBanner extends PureComponent {
|
||||
@@ -37,15 +37,15 @@ class ServerBanner extends PureComponent {
|
||||
|
||||
render () {
|
||||
const { server, intl } = this.props;
|
||||
const isLoading = server.get('isLoading');
|
||||
const isLoading = server.get("isLoading");
|
||||
|
||||
return (
|
||||
<div className='server-banner'>
|
||||
<div className='server-banner__introduction'>
|
||||
<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' rel="noreferrer">Mastodon</a> }} />
|
||||
</div>
|
||||
|
||||
<ServerHeroImage 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'>
|
||||
{isLoading ? (
|
||||
@@ -56,14 +56,14 @@ class ServerBanner extends PureComponent {
|
||||
<br />
|
||||
<Skeleton width='70%' />
|
||||
</>
|
||||
) : server.get('description')}
|
||||
) : server.get("description")}
|
||||
</div>
|
||||
|
||||
<div className='server-banner__meta'>
|
||||
<div className='server-banner__meta__column'>
|
||||
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
|
||||
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} minimal />
|
||||
<Account id={server.getIn(["contact", "account", "id"])} size={36} minimal />
|
||||
</div>
|
||||
|
||||
<div className='server-banner__meta__column'>
|
||||
@@ -77,7 +77,7 @@ class ServerBanner extends PureComponent {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong className='server-banner__number'><ShortNumber value={server.getIn(['usage', 'users', 'active_month'])} /></strong>
|
||||
<strong className='server-banner__number'><ShortNumber value={server.getIn(["usage", "users", "active_month"])} /></strong>
|
||||
<br />
|
||||
<span className='server-banner__number-label' title={intl.formatMessage(messages.aboutActiveUsers)}><FormattedMessage id='server_banner.active_users' defaultMessage='active users' /></span>
|
||||
</>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import { Blurhash } from './blurhash';
|
||||
import { Blurhash } from "./blurhash";
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
srcSet?: string;
|
||||
blurhash?: string;
|
||||
className?: string;
|
||||
src: string,
|
||||
srcSet?: string,
|
||||
blurhash?: string,
|
||||
className?: string,
|
||||
}
|
||||
|
||||
export const ServerHeroImage: React.FC<Props> = ({
|
||||
@@ -25,7 +25,7 @@ export const ServerHeroImage: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('image', { loaded }, className)}
|
||||
className={classNames("image", { loaded }, className)}
|
||||
role='presentation'
|
||||
>
|
||||
{blurhash && <Blurhash hash={blurhash} className='image__preview' />}
|
||||
|
||||
@@ -1,48 +1,19 @@
|
||||
import { memo } from 'react';
|
||||
import React, { memo } from "react";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
|
||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||
|
||||
import { toShortNumber, pluralReady, DECIMAL_UNITS } from '../utils/numbers';
|
||||
import { toShortNumber, pluralReady, DECIMAL_UNITS } from "../utils/numbers";
|
||||
|
||||
type ShortNumberRenderer = (
|
||||
displayNumber: JSX.Element,
|
||||
displayNumber: React.JSX.Element,
|
||||
pluralReady: number,
|
||||
) => JSX.Element;
|
||||
) => React.JSX.Element;
|
||||
|
||||
interface ShortNumberProps {
|
||||
value: number;
|
||||
renderer?: ShortNumberRenderer;
|
||||
children?: ShortNumberRenderer;
|
||||
value: number,
|
||||
renderer?: ShortNumberRenderer,
|
||||
children?: ShortNumberRenderer,
|
||||
}
|
||||
|
||||
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
|
||||
value,
|
||||
renderer,
|
||||
children,
|
||||
}) => {
|
||||
const shortNumber = toShortNumber(value);
|
||||
const [, division] = shortNumber;
|
||||
|
||||
if (children && renderer) {
|
||||
console.warn(
|
||||
'Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.',
|
||||
);
|
||||
}
|
||||
|
||||
const customRenderer = children ?? renderer ?? null;
|
||||
|
||||
const displayNumber = <ShortNumberCounter value={shortNumber} />;
|
||||
|
||||
return (
|
||||
customRenderer?.(displayNumber, pluralReady(value, division)) ??
|
||||
displayNumber
|
||||
);
|
||||
};
|
||||
export const ShortNumber = memo(ShortNumberRenderer);
|
||||
|
||||
interface ShortNumberCounterProps {
|
||||
value: number[];
|
||||
}
|
||||
const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
|
||||
const [rawNumber, unit, maxFractionDigits = 0] = value;
|
||||
|
||||
@@ -88,3 +59,33 @@ const ShortNumberCounter: React.FC<ShortNumberCounterProps> = ({ value }) => {
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
export const ShortNumberRenderer: React.FC<ShortNumberProps> = ({
|
||||
value,
|
||||
renderer,
|
||||
children,
|
||||
}) => {
|
||||
const shortNumber = toShortNumber(value);
|
||||
const [, division] = shortNumber;
|
||||
|
||||
if (children && renderer) {
|
||||
console.warn(
|
||||
"Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.",
|
||||
);
|
||||
}
|
||||
|
||||
const customRenderer = children ?? renderer ?? null;
|
||||
|
||||
const displayNumber = <ShortNumberCounter value={shortNumber} />;
|
||||
|
||||
return (
|
||||
customRenderer?.(displayNumber, pluralReady(value, division)) ??
|
||||
displayNumber
|
||||
);
|
||||
};
|
||||
export const ShortNumber = memo(ShortNumberRenderer);
|
||||
|
||||
interface ShortNumberCounterProps {
|
||||
value: number[],
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
interface Props {
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
width?: number | string,
|
||||
height?: number | string,
|
||||
}
|
||||
|
||||
export const Skeleton: React.FC<Props> = ({ width, height }) => (
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, defineMessages, FormattedMessage } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
import { HotKeys } from "react-hotkeys";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
import PictureInPicturePlaceholder from "mastodon/components/picture_in_picture_placeholder";
|
||||
|
||||
import Card from '../features/status/components/card';
|
||||
import Card from "../features/status/components/card";
|
||||
// We use the component (and not the container) since we do not want
|
||||
// to use the progress bar to show download progress
|
||||
import Bundle from '../features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { displayMedia } from '../initial_state';
|
||||
import Bundle from "../features/ui/components/bundle";
|
||||
import { MediaGallery, Video, Audio } from "../features/ui/util/async-components";
|
||||
import { displayMedia } from "../initial_state";
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
import { DisplayName } from './display_name';
|
||||
import { getHashtagBarForStatus } from './hashtag_bar';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
import StatusContent from './status_content';
|
||||
import { Avatar } from "./avatar";
|
||||
import { AvatarOverlay } from "./avatar_overlay";
|
||||
import { DisplayName } from "./display_name";
|
||||
import { getHashtagBarForStatus } from "./hashtag_bar";
|
||||
import { RelativeTimestamp } from "./relative_timestamp";
|
||||
import StatusActionBar from "./status_action_bar";
|
||||
import StatusContent from "./status_content";
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
const displayName = status.getIn(['account', 'display_name']);
|
||||
const displayName = status.getIn(["account", "display_name"]);
|
||||
|
||||
const spoilerText = status.getIn(['translation', 'spoiler_text']) || status.get('spoiler_text');
|
||||
const contentHtml = status.getIn(['translation', 'contentHtml']) || status.get('contentHtml');
|
||||
const contentText = domParser.parseFromString(contentHtml, 'text/html').documentElement.textContent;
|
||||
const spoilerText = status.getIn(["translation", "spoiler_text"]) || status.get("spoiler_text");
|
||||
const contentHtml = status.getIn(["translation", "contentHtml"]) || status.get("contentHtml");
|
||||
const contentText = domParser.parseFromString(contentHtml, "text/html").documentElement.textContent;
|
||||
|
||||
const values = [
|
||||
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
|
||||
spoilerText && status.get('hidden') ? spoilerText : contentText,
|
||||
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
|
||||
status.getIn(['account', 'acct']),
|
||||
displayName.length === 0 ? status.getIn(["account", "acct"]).split("@")[0] : displayName,
|
||||
spoilerText && status.get("hidden") ? spoilerText : contentText,
|
||||
intl.formatDate(status.get("created_at"), { hour: "2-digit", minute: "2-digit", month: "short", day: "numeric" }),
|
||||
status.getIn(["account", "acct"]),
|
||||
];
|
||||
|
||||
if (rebloggedByText) {
|
||||
values.push(rebloggedByText);
|
||||
}
|
||||
|
||||
return values.join(', ');
|
||||
return values.join(", ");
|
||||
};
|
||||
|
||||
export const defaultMediaVisibility = (status) => {
|
||||
@@ -55,19 +55,19 @@ export const defaultMediaVisibility = (status) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
status = status.get('reblog');
|
||||
if (status.get("reblog", null) !== null && typeof status.get("reblog") === "object") {
|
||||
status = status.get("reblog");
|
||||
}
|
||||
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
|
||||
return (displayMedia !== "hide_all" && !status.get("sensitive") || displayMedia === "show_all");
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||
public_short: { id: "privacy.public.short", defaultMessage: "Public" },
|
||||
unlisted_short: { id: "privacy.unlisted.short", defaultMessage: "Unlisted" },
|
||||
private_short: { id: "privacy.private.short", defaultMessage: "Followers only" },
|
||||
direct_short: { id: "privacy.direct.short", defaultMessage: "Mentioned people only" },
|
||||
edited: { id: "status.edited", defaultMessage: "Edited {date}" },
|
||||
});
|
||||
|
||||
class Status extends ImmutablePureComponent {
|
||||
@@ -121,12 +121,12 @@ class Status extends ImmutablePureComponent {
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
// evaluate to false. See react-immutable-pure-component for usage.
|
||||
updateOnProps = [
|
||||
'status',
|
||||
'account',
|
||||
'muted',
|
||||
'hidden',
|
||||
'unread',
|
||||
'pictureInPicture',
|
||||
"status",
|
||||
"account",
|
||||
"muted",
|
||||
"hidden",
|
||||
"unread",
|
||||
"pictureInPicture",
|
||||
];
|
||||
|
||||
state = {
|
||||
@@ -136,10 +136,10 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
|
||||
if (nextProps.status && nextProps.status.get("id") !== prevState.statusId) {
|
||||
return {
|
||||
showMedia: defaultMediaVisibility(nextProps.status),
|
||||
statusId: nextProps.status.get('id'),
|
||||
statusId: nextProps.status.get("id"),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
@@ -192,14 +192,14 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
getAttachmentAspectRatio () {
|
||||
const attachments = this._properStatus().get('media_attachments');
|
||||
const attachments = this._properStatus().get("media_attachments");
|
||||
|
||||
if (attachments.getIn([0, 'type']) === 'video') {
|
||||
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
|
||||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
if (attachments.getIn([0, "type"]) === "video") {
|
||||
return `${attachments.getIn([0, "meta", "original", "width"])} / ${attachments.getIn([0, "meta", "original", "height"])}`;
|
||||
} else if (attachments.getIn([0, "type"]) === "audio") {
|
||||
return "16 / 9";
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
|
||||
return (attachments.size === 1 && attachments.getIn([0, "meta", "small", "aspect"])) ? attachments.getIn([0, "meta", "small", "aspect"]) : "3 / 2";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,14 +223,14 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
handleOpenVideo = (options) => {
|
||||
const status = this._properStatus();
|
||||
const lang = status.getIn(['translation', 'language']) || status.get('language');
|
||||
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, options);
|
||||
const lang = status.getIn(["translation", "language"]) || status.get("language");
|
||||
this.props.onOpenVideo(status.get("id"), status.getIn(["media_attachments", 0]), lang, options);
|
||||
};
|
||||
|
||||
handleOpenMedia = (media, index) => {
|
||||
const status = this._properStatus();
|
||||
const lang = status.getIn(['translation', 'language']) || status.get('language');
|
||||
this.props.onOpenMedia(status.get('id'), media, index, lang);
|
||||
const lang = status.getIn(["translation", "language"]) || status.get("language");
|
||||
this.props.onOpenMedia(status.get("id"), media, index, lang);
|
||||
};
|
||||
|
||||
handleHotkeyOpenMedia = e => {
|
||||
@@ -239,12 +239,12 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
const lang = status.getIn(['translation', 'language']) || status.get('language');
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
|
||||
if (status.get("media_attachments").size > 0) {
|
||||
const lang = status.getIn(["translation", "language"]) || status.get("language");
|
||||
if (status.getIn(["media_attachments", 0, "type"]) === "video") {
|
||||
onOpenVideo(status.get("id"), status.getIn(["media_attachments", 0]), lang, { startTime: 0 });
|
||||
} else {
|
||||
onOpenMedia(status.get('id'), status.get('media_attachments'), 0, lang);
|
||||
onOpenMedia(status.get("id"), status.get("media_attachments"), 0, lang);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -271,7 +271,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
handleHotkeyMention = e => {
|
||||
e.preventDefault();
|
||||
this.props.onMention(this._properStatus().get('account'), this.context.router.history);
|
||||
this.props.onMention(this._properStatus().get("account"), this.context.router.history);
|
||||
};
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
@@ -287,7 +287,7 @@ class Status extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
router.history.push(`/@${status.getIn(["account", "acct"])}/${status.get("id")}`);
|
||||
};
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
@@ -302,15 +302,15 @@ class Status extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}`);
|
||||
router.history.push(`/@${status.getIn(["account", "acct"])}`);
|
||||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
this.props.onMoveUp(this.props.status.get("id"), e.target.getAttribute("data-featured"));
|
||||
};
|
||||
|
||||
handleHotkeyMoveDown = e => {
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
this.props.onMoveDown(this.props.status.get("id"), e.target.getAttribute("data-featured"));
|
||||
};
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
@@ -333,8 +333,8 @@ class Status extends ImmutablePureComponent {
|
||||
_properStatus () {
|
||||
const { status } = this.props;
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
return status.get('reblog');
|
||||
if (status.get("reblog", null) !== null && typeof status.get("reblog") === "object") {
|
||||
return status.get("reblog");
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
@@ -372,18 +372,18 @@ class Status extends ImmutablePureComponent {
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
<div ref={this.handleRef} className={classNames("status__wrapper", { focusable: !this.props.muted })} tabIndex={0}>
|
||||
<span>{status.getIn(["account", "display_name"]) || status.getIn(["account", "username"])}</span>
|
||||
<span>{status.get("content")}</span>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
const matchedFilters = status.get('matched_filters');
|
||||
const connectUp = previousId && previousId === status.get("in_reply_to_id");
|
||||
const connectToRoot = rootId && rootId === status.get("in_reply_to_id");
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get("id");
|
||||
const matchedFilters = status.get("matched_filters");
|
||||
|
||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||
const minHandlers = this.props.muted ? {} : {
|
||||
@@ -394,8 +394,8 @@ class Status extends ImmutablePureComponent {
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(", ")}.
|
||||
{" "}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
|
||||
</button>
|
||||
@@ -411,89 +411,89 @@ class Status extends ImmutablePureComponent {
|
||||
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
|
||||
</div>
|
||||
);
|
||||
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
} else if (status.get("reblog", null) !== null && typeof status.get("reblog") === "object") {
|
||||
const display_name_html = { __html: status.getIn(["account", "display_name_html"]) };
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(["account", "id"])} href={`/@${status.getIn(["account", "acct"])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) });
|
||||
rebloggedByText = intl.formatMessage({ id: "status.reblogged_by", defaultMessage: "{name} boosted" }, { name: status.getIn(["account", "acct"]) });
|
||||
|
||||
account = status.get('account');
|
||||
status = status.get('reblog');
|
||||
} else if (status.get('visibility') === 'direct') {
|
||||
account = status.get("account");
|
||||
status = status.get("reblog");
|
||||
} else if (status.get("visibility") === "direct") {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='at' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
|
||||
</div>
|
||||
);
|
||||
} else if (showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
} else if (showThread && status.get("in_reply_to_id") && status.get("in_reply_to_account_id") === status.getIn(["account", "id"])) {
|
||||
const display_name_html = { __html: status.getIn(["account", "display_name_html"]) };
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='reply' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(["account", "id"])} href={`/@${status.getIn(["account", "acct"])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
if (pictureInPicture.get("inUse")) {
|
||||
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
} else if (status.get("media_attachments").size > 0) {
|
||||
const language = status.getIn(["translation", "language"]) || status.get("language");
|
||||
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
if (status.getIn(["media_attachments", 0, "type"]) === "audio") {
|
||||
const attachment = status.getIn(["media_attachments", 0]);
|
||||
const description = attachment.getIn(["translation", "description"]) || attachment.get("description");
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
src={attachment.get('url')}
|
||||
src={attachment.get("url")}
|
||||
alt={description}
|
||||
lang={language}
|
||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
|
||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||
poster={attachment.get("preview_url") || status.getIn(["account", "avatar_static"])}
|
||||
backgroundColor={attachment.getIn(["meta", "colors", "background"])}
|
||||
foregroundColor={attachment.getIn(["meta", "colors", "foreground"])}
|
||||
accentColor={attachment.getIn(["meta", "colors", "accent"])}
|
||||
duration={attachment.getIn(["meta", "original", "duration"], 0)}
|
||||
width={this.props.cachedMediaWidth}
|
||||
height={110}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
sensitive={status.get('sensitive')}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
deployPictureInPicture={pictureInPicture.get("available") ? this.handleDeployPictureInPicture : undefined}
|
||||
sensitive={status.get("sensitive")}
|
||||
blurhash={attachment.get("blurhash")}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
} else if (status.getIn(["media_attachments", 0, "type"]) === "video") {
|
||||
const attachment = status.getIn(["media_attachments", 0]);
|
||||
const description = attachment.getIn(["translation", "description"]) || attachment.get("description");
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
preview={attachment.get('preview_url')}
|
||||
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
||||
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
src={attachment.get('url')}
|
||||
preview={attachment.get("preview_url")}
|
||||
frameRate={attachment.getIn(["meta", "original", "frame_rate"])}
|
||||
aspectRatio={`${attachment.getIn(["meta", "original", "width"])} / ${attachment.getIn(["meta", "original", "height"])}`}
|
||||
blurhash={attachment.get("blurhash")}
|
||||
src={attachment.get("url")}
|
||||
alt={description}
|
||||
lang={language}
|
||||
sensitive={status.get('sensitive')}
|
||||
sensitive={status.get("sensitive")}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
deployPictureInPicture={pictureInPicture.get("available") ? this.handleDeployPictureInPicture : undefined}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
/>
|
||||
@@ -505,9 +505,9 @@ class Status extends ImmutablePureComponent {
|
||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||
{Component => (
|
||||
<Component
|
||||
media={status.get('media_attachments')}
|
||||
media={status.get("media_attachments")}
|
||||
lang={language}
|
||||
sensitive={status.get('sensitive')}
|
||||
sensitive={status.get("sensitive")}
|
||||
height={110}
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
@@ -519,56 +519,56 @@ class Status extends ImmutablePureComponent {
|
||||
</Bundle>
|
||||
);
|
||||
}
|
||||
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
|
||||
} else if (status.get("spoiler_text").length === 0 && status.get("card")) {
|
||||
media = (
|
||||
<Card
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
card={status.get('card')}
|
||||
card={status.get("card")}
|
||||
compact
|
||||
sensitive={status.get('sensitive')}
|
||||
sensitive={status.get("sensitive")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (account === undefined || account === null) {
|
||||
statusAvatar = <Avatar account={status.get('account')} size={46} />;
|
||||
statusAvatar = <Avatar account={status.get("account")} size={46} />;
|
||||
} else {
|
||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
statusAvatar = <AvatarOverlay account={status.get("account")} friend={account} />;
|
||||
}
|
||||
|
||||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
"public": { icon: "globe", text: intl.formatMessage(messages.public_short) },
|
||||
"unlisted": { icon: "unlock", text: intl.formatMessage(messages.unlisted_short) },
|
||||
"private": { icon: "lock", text: intl.formatMessage(messages.private_short) },
|
||||
"direct": { icon: "at", text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
const visibilityIcon = visibilityIconInfo[status.get("visibility")];
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
|
||||
const expanded = !status.get("hidden") || status.get("spoiler_text").length === 0;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
<div className={classNames("status__wrapper", `status__wrapper-${status.get("visibility")}`, { "status__wrapper-reply": !!status.get("in_reply_to_id"), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? "true" : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(["account", "noindex"], true) || undefined}>
|
||||
{prepend}
|
||||
|
||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
<div className={classNames("status", `status-${status.get("visibility")}`, { "status-reply": !!status.get("in_reply_to_id"), "status--in-thread": !!rootId, "status--first-in-thread": previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get("id")}>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames("status__line", { "status__line--full": connectReply, "status__line--first": !status.get("in_reply_to_id") && !connectToRoot })} />}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={this.handleClick} className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<a href={`/@${status.getIn(["account", "acct"])}/${status.get("id")}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
<RelativeTimestamp timestamp={status.get("created_at")} />{status.get("edited_at") && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get("edited_at"), { hour12: false, year: "numeric", month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit" }) })}> *</abbr>}
|
||||
</a>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(["account", "acct"])}`} title={status.getIn(["account", "acct"])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
<DisplayName account={status.get("account")} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from "react-intl";
|
||||
|
||||
import classNames from 'classnames';
|
||||
import classNames from "classnames";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from "mastodon/permissions";
|
||||
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import { me } from '../initial_state';
|
||||
import DropdownMenuContainer from "../containers/dropdown_menu_container";
|
||||
import { me } from "../initial_state";
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
import { IconButton } from "./icon_button";
|
||||
|
||||
const messages = defineMessages({
|
||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
|
||||
edit: { id: 'status.edit', defaultMessage: 'Edit' },
|
||||
direct: { id: 'status.direct', defaultMessage: 'Privately mention @{name}' },
|
||||
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||
more: { id: 'status.more', defaultMessage: 'More' },
|
||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
|
||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
||||
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
||||
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
||||
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
|
||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
||||
hide: { id: 'status.hide', defaultMessage: 'Hide post' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
delete: { id: "status.delete", defaultMessage: "Delete" },
|
||||
redraft: { id: "status.redraft", defaultMessage: "Delete & re-draft" },
|
||||
edit: { id: "status.edit", defaultMessage: "Edit" },
|
||||
direct: { id: "status.direct", defaultMessage: "Privately mention @{name}" },
|
||||
mention: { id: "status.mention", defaultMessage: "Mention @{name}" },
|
||||
mute: { id: "account.mute", defaultMessage: "Mute @{name}" },
|
||||
block: { id: "account.block", defaultMessage: "Block @{name}" },
|
||||
reply: { id: "status.reply", defaultMessage: "Reply" },
|
||||
share: { id: "status.share", defaultMessage: "Share" },
|
||||
more: { id: "status.more", defaultMessage: "More" },
|
||||
replyAll: { id: "status.replyAll", defaultMessage: "Reply to thread" },
|
||||
reblog: { id: "status.reblog", defaultMessage: "Boost" },
|
||||
reblog_private: { id: "status.reblog_private", defaultMessage: "Boost with original visibility" },
|
||||
cancel_reblog_private: { id: "status.cancel_reblog_private", defaultMessage: "Unboost" },
|
||||
cannot_reblog: { id: "status.cannot_reblog", defaultMessage: "This post cannot be boosted" },
|
||||
favourite: { id: "status.favourite", defaultMessage: "Favorite" },
|
||||
bookmark: { id: "status.bookmark", defaultMessage: "Bookmark" },
|
||||
removeBookmark: { id: "status.remove_bookmark", defaultMessage: "Remove bookmark" },
|
||||
open: { id: "status.open", defaultMessage: "Expand this status" },
|
||||
report: { id: "status.report", defaultMessage: "Report @{name}" },
|
||||
muteConversation: { id: "status.mute_conversation", defaultMessage: "Mute conversation" },
|
||||
unmuteConversation: { id: "status.unmute_conversation", defaultMessage: "Unmute conversation" },
|
||||
pin: { id: "status.pin", defaultMessage: "Pin on profile" },
|
||||
unpin: { id: "status.unpin", defaultMessage: "Unpin from profile" },
|
||||
embed: { id: "status.embed", defaultMessage: "Embed" },
|
||||
admin_account: { id: "status.admin_account", defaultMessage: "Open moderation interface for @{name}" },
|
||||
admin_status: { id: "status.admin_status", defaultMessage: "Open this post in the moderation interface" },
|
||||
admin_domain: { id: "status.admin_domain", defaultMessage: "Open moderation interface for {domain}" },
|
||||
copy: { id: "status.copy", defaultMessage: "Copy link to post" },
|
||||
hide: { id: "status.hide", defaultMessage: "Hide post" },
|
||||
blockDomain: { id: "account.block_domain", defaultMessage: "Block domain {domain}" },
|
||||
unblockDomain: { id: "account.unblock_domain", defaultMessage: "Unblock domain {domain}" },
|
||||
unmute: { id: "account.unmute", defaultMessage: "Unmute @{name}" },
|
||||
unblock: { id: "account.unblock", defaultMessage: "Unblock @{name}" },
|
||||
filter: { id: "status.filter", defaultMessage: "Filter this post" },
|
||||
openOriginalPage: { id: "account.open_original_page", defaultMessage: "Open original page" },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
relationship: state.getIn(["relationships", status.getIn(["account", "id"])]),
|
||||
});
|
||||
|
||||
class StatusActionBar extends ImmutablePureComponent {
|
||||
@@ -97,9 +97,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
// evaluate to false. See react-immutable-pure-component for usage.
|
||||
updateOnProps = [
|
||||
'status',
|
||||
'relationship',
|
||||
'withDismiss',
|
||||
"status",
|
||||
"relationship",
|
||||
"withDismiss",
|
||||
];
|
||||
|
||||
handleReplyClick = () => {
|
||||
@@ -108,15 +108,17 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
if (signedIn) {
|
||||
this.props.onReply(this.props.status, this.context.router.history);
|
||||
} else {
|
||||
this.props.onInteractionModal('reply', this.props.status);
|
||||
this.props.onInteractionModal("reply", this.props.status);
|
||||
}
|
||||
};
|
||||
|
||||
handleShareClick = () => {
|
||||
navigator.share({
|
||||
url: this.props.status.get('url'),
|
||||
url: this.props.status.get("url"),
|
||||
}).catch((e) => {
|
||||
if (e.name !== 'AbortError') console.error(e);
|
||||
if (e.name !== "AbortError") {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -126,7 +128,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
if (signedIn) {
|
||||
this.props.onFavourite(this.props.status);
|
||||
} else {
|
||||
this.props.onInteractionModal('favourite', this.props.status);
|
||||
this.props.onInteractionModal("favourite", this.props.status);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,7 +138,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
if (signedIn) {
|
||||
this.props.onReblog(this.props.status, e);
|
||||
} else {
|
||||
this.props.onInteractionModal('reblog', this.props.status);
|
||||
this.props.onInteractionModal("reblog", this.props.status);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,18 +163,18 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleMentionClick = () => {
|
||||
this.props.onMention(this.props.status.get('account'), this.context.router.history);
|
||||
this.props.onMention(this.props.status.get("account"), this.context.router.history);
|
||||
};
|
||||
|
||||
handleDirectClick = () => {
|
||||
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
|
||||
this.props.onDirect(this.props.status.get("account"), this.context.router.history);
|
||||
};
|
||||
|
||||
handleMuteClick = () => {
|
||||
const { status, relationship, onMute, onUnmute } = this.props;
|
||||
const account = status.get('account');
|
||||
const account = status.get("account");
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
if (relationship && relationship.get("muting")) {
|
||||
onUnmute(account);
|
||||
} else {
|
||||
onMute(account);
|
||||
@@ -181,9 +183,9 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
handleBlockClick = () => {
|
||||
const { status, relationship, onBlock, onUnblock } = this.props;
|
||||
const account = status.get('account');
|
||||
const account = status.get("account");
|
||||
|
||||
if (relationship && relationship.get('blocking')) {
|
||||
if (relationship && relationship.get("blocking")) {
|
||||
onUnblock(account);
|
||||
} else {
|
||||
onBlock(status);
|
||||
@@ -192,20 +194,20 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
handleBlockDomain = () => {
|
||||
const { status, onBlockDomain } = this.props;
|
||||
const account = status.get('account');
|
||||
const account = status.get("account");
|
||||
|
||||
onBlockDomain(account.get('acct').split('@')[1]);
|
||||
onBlockDomain(account.get("acct").split("@")[1]);
|
||||
};
|
||||
|
||||
handleUnblockDomain = () => {
|
||||
const { status, onUnblockDomain } = this.props;
|
||||
const account = status.get('account');
|
||||
const account = status.get("account");
|
||||
|
||||
onUnblockDomain(account.get('acct').split('@')[1]);
|
||||
onUnblockDomain(account.get("acct").split("@")[1]);
|
||||
};
|
||||
|
||||
handleOpen = () => {
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`);
|
||||
this.context.router.history.push(`/@${this.props.status.getIn(["account", "acct"])}/${this.props.status.get("id")}`);
|
||||
};
|
||||
|
||||
handleEmbed = () => {
|
||||
@@ -225,7 +227,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const url = this.props.status.get("url");
|
||||
navigator.clipboard.writeText(url);
|
||||
};
|
||||
|
||||
@@ -237,24 +239,24 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
const { signedIn, permissions } = this.context.identity;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
const publicStatus = ["public", "unlisted"].includes(status.get("visibility"));
|
||||
const pinnableStatus = ["public", "unlisted", "private"].includes(status.get("visibility"));
|
||||
const mutingConversation = status.get("muted");
|
||||
const account = status.get("account");
|
||||
const writtenByMe = status.getIn(["account", "id"]) === me;
|
||||
const isRemote = status.getIn(["account", "username"]) !== status.getIn(["account", "acct"]);
|
||||
|
||||
let menu = [];
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||
|
||||
if (publicStatus && isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get("url") });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
|
||||
if (publicStatus && 'share' in navigator) {
|
||||
if (publicStatus && "share" in navigator) {
|
||||
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
|
||||
}
|
||||
|
||||
@@ -265,10 +267,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
if (signedIn) {
|
||||
menu.push(null);
|
||||
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
menu.push({ text: intl.formatMessage(status.get("bookmarked") ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||
|
||||
if (writtenByMe && pinnableStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
menu.push({ text: intl.formatMessage(status.get("pinned") ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
@@ -283,20 +285,20 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick, dangerous: true });
|
||||
menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick, dangerous: true });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.handleDirectClick });
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get("username") }), action: this.handleMentionClick });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get("username") }), action: this.handleDirectClick });
|
||||
menu.push(null);
|
||||
|
||||
if (relationship && relationship.get('muting')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.handleMuteClick });
|
||||
if (relationship && relationship.get("muting")) {
|
||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get("username") }), action: this.handleMuteClick });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.handleMuteClick, dangerous: true });
|
||||
menu.push({ text: intl.formatMessage(messages.mute, { name: account.get("username") }), action: this.handleMuteClick, dangerous: true });
|
||||
}
|
||||
|
||||
if (relationship && relationship.get('blocking')) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.handleBlockClick });
|
||||
if (relationship && relationship.get("blocking")) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get("username") }), action: this.handleBlockClick });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.handleBlockClick, dangerous: true });
|
||||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get("username") }), action: this.handleBlockClick, dangerous: true });
|
||||
}
|
||||
|
||||
if (!this.props.onFilter) {
|
||||
@@ -305,14 +307,14 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.handleReport, dangerous: true });
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get("username") }), action: this.handleReport, dangerous: true });
|
||||
|
||||
if (account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
if (account.get("acct") !== account.get("username")) {
|
||||
const domain = account.get("acct").split("@")[1];
|
||||
|
||||
menu.push(null);
|
||||
|
||||
if (relationship && relationship.get('domain_blocking')) {
|
||||
if (relationship && relationship.get("domain_blocking")) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.handleUnblockDomain });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.handleBlockDomain, dangerous: true });
|
||||
@@ -322,11 +324,11 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS || (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION)) {
|
||||
menu.push(null);
|
||||
if ((permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) {
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get("username") }), href: `/admin/accounts/${status.getIn(["account", "id"])}` });
|
||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(["account", "id"])}/statuses/${status.get("id")}` });
|
||||
}
|
||||
if (isRemote && (permissions & PERMISSION_MANAGE_FEDERATION) === PERMISSION_MANAGE_FEDERATION) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
const domain = account.get("acct").split("@")[1];
|
||||
menu.push({ text: intl.formatMessage(messages.admin_domain, { domain: domain }), href: `/admin/instances/${domain}` });
|
||||
}
|
||||
}
|
||||
@@ -335,18 +337,18 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
let replyIcon;
|
||||
let replyTitle;
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
if (status.get("in_reply_to_id", null) === null) {
|
||||
replyIcon = "reply";
|
||||
replyTitle = intl.formatMessage(messages.reply);
|
||||
} else {
|
||||
replyIcon = 'reply-all';
|
||||
replyIcon = "reply-all";
|
||||
replyTitle = intl.formatMessage(messages.replyAll);
|
||||
}
|
||||
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
const reblogPrivate = status.getIn(["account", "id"]) === me && status.get("visibility") === "private";
|
||||
|
||||
let reblogTitle = '';
|
||||
if (status.get('reblogged')) {
|
||||
let reblogTitle = "";
|
||||
if (status.get("reblogged")) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
@@ -362,10 +364,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={status.get("in_reply_to_account_id") === status.getIn(["account", "id"]) ? "reply" : replyIcon} onClick={this.handleReplyClick} counter={status.get("replies_count")} />
|
||||
<IconButton className={classNames("status__action-bar__button", { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get("reblogged")} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get("reblogs_count") : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get("favourited")} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get("favourites_count") : undefined} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get("bookmarked")} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
|
||||
{filterButton}
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { PureComponent } from "react";
|
||||
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { FormattedMessage, injectIntl } from "react-intl";
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classnames from "classnames";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
import { autoPlayGif } from 'mastodon/initial_state';
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
import PollContainer from "mastodon/containers/poll_container";
|
||||
import { autoPlayGif } from "mastodon/initial_state";
|
||||
|
||||
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||
|
||||
@@ -21,7 +21,7 @@ const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getStatusContent(status) {
|
||||
return status.getIn(['translation', 'contentHtml']) || status.get('contentHtml');
|
||||
return status.getIn(["translation", "contentHtml"]) || status.get("contentHtml");
|
||||
}
|
||||
|
||||
class StatusContent extends PureComponent {
|
||||
@@ -54,42 +54,42 @@ class StatusContent extends PureComponent {
|
||||
}
|
||||
|
||||
const { status, onCollapsedToggle } = this.props;
|
||||
const links = node.querySelectorAll('a');
|
||||
const links = node.querySelectorAll("a");
|
||||
|
||||
let link, mention;
|
||||
|
||||
for (var i = 0; i < links.length; ++i) {
|
||||
link = links[i];
|
||||
|
||||
if (link.classList.contains('status-link')) {
|
||||
if (link.classList.contains("status-link")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
link.classList.add('status-link');
|
||||
link.classList.add("status-link");
|
||||
|
||||
mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
|
||||
mention = this.props.status.get("mentions").find(item => link.href === item.get("url"));
|
||||
|
||||
if (mention) {
|
||||
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
|
||||
link.setAttribute('title', `@${mention.get('acct')}`);
|
||||
link.setAttribute('href', `/@${mention.get('acct')}`);
|
||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
||||
link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
|
||||
link.addEventListener("click", this.onMentionClick.bind(this, mention), false);
|
||||
link.setAttribute("title", `@${mention.get("acct")}`);
|
||||
link.setAttribute("href", `/@${mention.get("acct")}`);
|
||||
} else if (link.textContent[0] === "#" || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === "#")) {
|
||||
link.addEventListener("click", this.onHashtagClick.bind(this, link.text), false);
|
||||
link.setAttribute("href", `/tags/${link.text.replace(/^#/, "")}`);
|
||||
} else {
|
||||
link.setAttribute('title', link.href);
|
||||
link.classList.add('unhandled-link');
|
||||
link.setAttribute("title", link.href);
|
||||
link.classList.add("unhandled-link");
|
||||
}
|
||||
}
|
||||
|
||||
if (status.get('collapsed', null) === null && onCollapsedToggle) {
|
||||
if (status.get("collapsed", null) === null && onCollapsedToggle) {
|
||||
const { collapsible, onClick } = this.props;
|
||||
|
||||
const collapsed =
|
||||
collapsible
|
||||
&& onClick
|
||||
&& node.clientHeight > MAX_HEIGHT
|
||||
&& status.get('spoiler_text').length === 0;
|
||||
&& status.get("spoiler_text").length === 0;
|
||||
|
||||
onCollapsedToggle(collapsed);
|
||||
}
|
||||
@@ -100,11 +100,11 @@ class StatusContent extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
const emojis = currentTarget.querySelectorAll(".custom-emoji");
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
emoji.src = emoji.getAttribute('data-original');
|
||||
emoji.src = emoji.getAttribute("data-original");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,11 +113,11 @@ class StatusContent extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const emojis = currentTarget.querySelectorAll('.custom-emoji');
|
||||
const emojis = currentTarget.querySelectorAll(".custom-emoji");
|
||||
|
||||
for (var i = 0; i < emojis.length; i++) {
|
||||
let emoji = emojis[i];
|
||||
emoji.src = emoji.getAttribute('data-static');
|
||||
emoji.src = emoji.getAttribute("data-static");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,12 +132,12 @@ class StatusContent extends PureComponent {
|
||||
onMentionClick = (mention, e) => {
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.context.router.history.push(`/@${mention.get('acct')}`);
|
||||
this.context.router.history.push(`/@${mention.get("acct")}`);
|
||||
}
|
||||
};
|
||||
|
||||
onHashtagClick = (hashtag, e) => {
|
||||
hashtag = hashtag.replace(/^#/, '');
|
||||
hashtag = hashtag.replace(/^#/, "");
|
||||
|
||||
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
@@ -159,7 +159,7 @@ class StatusContent extends PureComponent {
|
||||
|
||||
let element = e.target;
|
||||
while (element) {
|
||||
if (element.localName === 'button' || element.localName === 'a' || element.localName === 'label') {
|
||||
if (element.localName === "button" || element.localName === "a" || element.localName === "label") {
|
||||
return;
|
||||
}
|
||||
element = element.parentNode;
|
||||
@@ -195,15 +195,15 @@ class StatusContent extends PureComponent {
|
||||
const { status, statusContent } = this.props;
|
||||
|
||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||
const renderReadMore = this.props.onClick && status.get("collapsed");
|
||||
|
||||
const content = { __html: statusContent ?? getStatusContent(status) };
|
||||
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
const classNames = classnames('status__content', {
|
||||
'status__content--with-action': this.props.onClick && this.context.router,
|
||||
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
||||
'status__content--collapsed': renderReadMore,
|
||||
const spoilerContent = { __html: status.getIn(["translation", "spoilerHtml"]) || status.get("spoilerHtml") };
|
||||
const language = status.getIn(["translation", "language"]) || status.get("language");
|
||||
const classNames = classnames("status__content", {
|
||||
"status__content--with-action": this.props.onClick && this.context.router,
|
||||
"status__content--with-spoiler": status.get("spoiler_text").length > 0,
|
||||
"status__content--collapsed": renderReadMore,
|
||||
});
|
||||
|
||||
const readMoreButton = renderReadMore && (
|
||||
@@ -212,18 +212,18 @@ class StatusContent extends PureComponent {
|
||||
</button>
|
||||
);
|
||||
|
||||
const poll = !!status.get('poll') && (
|
||||
<PollContainer pollId={status.get('poll')} lang={language} />
|
||||
const poll = !!status.get("poll") && (
|
||||
<PollContainer pollId={status.get("poll")} lang={language} />
|
||||
);
|
||||
|
||||
if (status.get('spoiler_text').length > 0) {
|
||||
let mentionsPlaceholder = '';
|
||||
if (status.get("spoiler_text").length > 0) {
|
||||
let mentionsPlaceholder = "";
|
||||
|
||||
const mentionLinks = status.get('mentions').map(item => (
|
||||
<Link to={`/@${item.get('acct')}`} key={item.get('id')} className='status-link mention'>
|
||||
@<span>{item.get('username')}</span>
|
||||
const mentionLinks = status.get("mentions").map(item => (
|
||||
<Link to={`/@${item.get("acct")}`} key={item.get("id")} className='status-link mention'>
|
||||
@<span>{item.get("username")}</span>
|
||||
</Link>
|
||||
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);
|
||||
)).reduce((aggregate, item) => [...aggregate, item, " "], []);
|
||||
|
||||
const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />;
|
||||
|
||||
@@ -233,15 +233,15 @@ class StatusContent extends PureComponent {
|
||||
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||
<p style={{ marginBottom: hidden && status.get("mentions").isEmpty() ? "0px" : null }}>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={language} />
|
||||
{' '}
|
||||
<button type='button' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick} aria-expanded={!hidden}>{toggleText}</button>
|
||||
{" "}
|
||||
<button type='button' className={`status__content__spoiler-link ${hidden ? "status__content__spoiler-link--show-more" : "status__content__spoiler-link--show-less"}`} onClick={this.handleSpoilerClick} aria-expanded={!hidden}>{toggleText}</button>
|
||||
</p>
|
||||
|
||||
{mentionsPlaceholder}
|
||||
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''} translate`} lang={language} dangerouslySetInnerHTML={content} />
|
||||
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? "status__content__text--visible" : ""} translate`} lang={language} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{!hidden && poll}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from "react-immutable-proptypes";
|
||||
import ImmutablePureComponent from "react-immutable-pure-component";
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import { debounce } from "lodash";
|
||||
|
||||
import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
|
||||
import RegenerationIndicator from "mastodon/components/regeneration_indicator";
|
||||
|
||||
import StatusContainer from '../containers/status_container';
|
||||
import StatusContainer from "../containers/status_container";
|
||||
|
||||
import { LoadGap } from './load_gap';
|
||||
import ScrollableList from './scrollable_list';
|
||||
import { LoadGap } from "./load_gap";
|
||||
import ScrollableList from "./scrollable_list";
|
||||
|
||||
export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
@@ -93,7 +93,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
let scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||
statusIds.map((statusId, index) => statusId === null ? (
|
||||
<LoadGap
|
||||
key={'gap:' + statusIds.get(index + 1)}
|
||||
key={"gap:" + statusIds.get(index + 1)}
|
||||
disabled={isLoading}
|
||||
maxId={index > 0 ? statusIds.get(index - 1) : null}
|
||||
onClick={onLoadMore}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import React from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
interface Props {
|
||||
resource: JSX.Element;
|
||||
url: string;
|
||||
resource: React.JSX.Element,
|
||||
url: string,
|
||||
}
|
||||
|
||||
export const TimelineHint: React.FC<Props> = ({ resource, url }) => (
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { Icon } from './icon';
|
||||
import { Icon } from "./icon";
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
const stripRelMe = (html: string) => {
|
||||
const document = domParser.parseFromString(html, 'text/html').documentElement;
|
||||
const document = domParser.parseFromString(html, "text/html").documentElement;
|
||||
|
||||
document.querySelectorAll<HTMLAnchorElement>('a[rel]').forEach((link) => {
|
||||
document.querySelectorAll<HTMLAnchorElement>("a[rel]").forEach((link) => {
|
||||
link.rel = link.rel
|
||||
.split(' ')
|
||||
.filter((x: string) => x !== 'me')
|
||||
.join(' ');
|
||||
.split(" ")
|
||||
.filter((x: string) => x !== "me")
|
||||
.join(" ");
|
||||
});
|
||||
|
||||
const body = document.querySelector('body');
|
||||
const body = document.querySelector("body");
|
||||
return body ? { __html: body.innerHTML } : undefined;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
link: string;
|
||||
link: string,
|
||||
}
|
||||
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
||||
<span className='verified-badge'>
|
||||
|
||||
Reference in New Issue
Block a user