diff --git a/app/soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json b/app/soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json
new file mode 100644
index 000000000..76c722a45
--- /dev/null
+++ b/app/soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json
@@ -0,0 +1,236 @@
+{
+ "account": {
+ "acct": "alex",
+ "avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png",
+ "bot": false,
+ "created_at": "2020-01-08T01:25:43.000Z",
+ "display_name": "Alex Gleason",
+ "emojis": [],
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "followers_count": 2467,
+ "following_count": 1581,
+ "fqn": "alex@gleasonator.com",
+ "header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png",
+ "id": "9v5bmRalQvjOy0ECcC",
+ "last_status_at": "2022-03-11T01:33:19",
+ "locked": false,
+ "note": "I create Fediverse software that empowers people online.
I'm vegan btw
Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "accepts_chat_messages": true,
+ "also_known_as": [
+ "https://mitra.social/users/alex"
+ ],
+ "ap_id": "https://gleasonator.com/users/alex",
+ "background_image": null,
+ "birthday": "1993-07-03",
+ "favicon": "https://gleasonator.com/favicon.png",
+ "hide_favorites": true,
+ "hide_followers": false,
+ "hide_followers_count": false,
+ "hide_follows": false,
+ "hide_follows_count": false,
+ "is_admin": true,
+ "is_confirmed": true,
+ "is_moderator": false,
+ "is_suggested": true,
+ "relationship": {},
+ "skip_thread_containment": false,
+ "tags": []
+ },
+ "source": {
+ "fields": [
+ {
+ "name": "Website",
+ "value": "https://alexgleason.me"
+ },
+ {
+ "name": "Soapbox",
+ "value": "https://soapbox.pub"
+ },
+ {
+ "name": "Email",
+ "value": "alex@alexgleason.me"
+ },
+ {
+ "name": "Gender identity",
+ "value": "Soyboy"
+ },
+ {
+ "name": "Donate (PayPal)",
+ "value": "https://paypal.me/gleasonator"
+ },
+ {
+ "name": "$BTC",
+ "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n"
+ },
+ {
+ "name": "$ETH",
+ "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717"
+ },
+ {
+ "name": "$DOGE",
+ "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D"
+ },
+ {
+ "name": "$XMR",
+ "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK"
+ }
+ ],
+ "note": "I create Fediverse software that empowers people online.\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.",
+ "pleroma": {
+ "actor_type": "Person",
+ "discoverable": false
+ },
+ "sensitive": false
+ },
+ "statuses_count": 23651,
+ "url": "https://gleasonator.com/users/alex",
+ "username": "alex"
+ },
+ "application": {
+ "name": "Soapbox FE",
+ "website": "https://soapbox.pub/"
+ },
+ "bookmarked": false,
+ "card": null,
+ "content": "
Test poll
",
+ "created_at": "2022-03-11T01:33:18.000Z",
+ "emojis": [
+ {
+ "shortcode": "gleason_excited",
+ "static_url": "https://gleasonator.com/emoji/gleason_emojis/gleason_excited.png",
+ "url": "https://gleasonator.com/emoji/gleason_emojis/gleason_excited.png",
+ "visible_in_picker": false
+ },
+ {
+ "shortcode": "soapbox",
+ "static_url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
+ "url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
+ "visible_in_picker": false
+ }
+ ],
+ "favourited": false,
+ "favourites_count": 1,
+ "id": "AHHue68kB59xtUv7MO",
+ "in_reply_to_account_id": null,
+ "in_reply_to_id": null,
+ "language": null,
+ "media_attachments": [],
+ "mentions": [],
+ "muted": false,
+ "pinned": false,
+ "pleroma": {
+ "content": {
+ "text/plain": "Test poll"
+ },
+ "conversation_id": "AHHue65YMwbjjbQZO4",
+ "direct_conversation_id": null,
+ "emoji_reactions": [],
+ "expires_at": null,
+ "in_reply_to_account_acct": null,
+ "local": true,
+ "parent_visible": false,
+ "pinned_at": null,
+ "quote": null,
+ "quote_url": null,
+ "quote_visible": false,
+ "spoiler_text": {
+ "text/plain": ""
+ },
+ "thread_muted": false
+ },
+ "poll": {
+ "emojis": [
+ {
+ "shortcode": "gleason_excited",
+ "static_url": "https://gleasonator.com/emoji/gleason_emojis/gleason_excited.png",
+ "url": "https://gleasonator.com/emoji/gleason_emojis/gleason_excited.png",
+ "visible_in_picker": false
+ },
+ {
+ "shortcode": "soapbox",
+ "static_url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
+ "url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png",
+ "visible_in_picker": false
+ }
+ ],
+ "expired": false,
+ "expires_at": "2022-03-12T01:33:18.000Z",
+ "id": "AHHue67gF2JDqCQGhc",
+ "multiple": false,
+ "options": [
+ {
+ "title": "Regular emoji 😍 ",
+ "votes_count": 0
+ },
+ {
+ "title": "Custom emoji :gleason_excited: ",
+ "votes_count": 1
+ },
+ {
+ "title": "No emoji",
+ "votes_count": 0
+ },
+ {
+ "title": "🤔 😮 😠 ",
+ "votes_count": 1
+ },
+ {
+ "title": ":soapbox:",
+ "votes_count": 1
+ }
+ ],
+ "voters_count": 3,
+ "votes_count": 3
+ },
+ "reblog": null,
+ "reblogged": false,
+ "reblogs_count": 1,
+ "replies_count": 1,
+ "sensitive": false,
+ "spoiler_text": "",
+ "tags": [],
+ "text": null,
+ "uri": "https://gleasonator.com/objects/46d2ab26-3497-442b-999f-612fe717b0a3",
+ "url": "https://gleasonator.com/notice/AHHue68kB59xtUv7MO",
+ "visibility": "public"
+}
diff --git a/app/soapbox/actions/importer/index.js b/app/soapbox/actions/importer/index.js
index 56efcf651..1668ad71e 100644
--- a/app/soapbox/actions/importer/index.js
+++ b/app/soapbox/actions/importer/index.js
@@ -1,9 +1,6 @@
import { getSettings } from '../settings';
-import {
- normalizeAccount,
- normalizePoll,
-} from './normalizer';
+import { normalizeAccount } from './normalizer';
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
@@ -130,7 +127,7 @@ export function importFetchedStatuses(statuses) {
}
if (status.poll?.id) {
- polls.push(normalizePoll(status.poll));
+ polls.push(status.poll);
}
}
@@ -144,7 +141,7 @@ export function importFetchedStatuses(statuses) {
export function importFetchedPoll(poll) {
return dispatch => {
- dispatch(importPolls([normalizePoll(poll)]));
+ dispatch(importPolls([poll]));
};
}
diff --git a/app/soapbox/actions/importer/normalizer.js b/app/soapbox/actions/importer/normalizer.js
index 4de148c0b..a294f4680 100644
--- a/app/soapbox/actions/importer/normalizer.js
+++ b/app/soapbox/actions/importer/normalizer.js
@@ -41,20 +41,6 @@ export function normalizeAccount(account) {
return account;
}
-export function normalizePoll(poll) {
- const normalPoll = { ...poll };
-
- const emojiMap = makeEmojiMap(normalPoll);
-
- normalPoll.options = poll.options.map((option, index) => ({
- ...option,
- voted: Boolean(poll.own_votes?.includes(index)),
- title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
- }));
-
- return normalPoll;
-}
-
export function normalizeChat(chat, normalOldChat) {
const normalChat = { ...chat };
const { account, last_message: lastMessage } = chat;
diff --git a/app/soapbox/components/poll.js b/app/soapbox/components/poll.js
index 9b0327798..ee31d25c0 100644
--- a/app/soapbox/components/poll.js
+++ b/app/soapbox/components/poll.js
@@ -1,5 +1,4 @@
import classNames from 'classnames';
-import escapeTextContentForBrowser from 'escape-html';
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -10,8 +9,8 @@ import spring from 'react-motion/lib/spring';
import { openModal } from 'soapbox/actions/modals';
import { vote, fetchPoll } from 'soapbox/actions/polls';
import Icon from 'soapbox/components/icon';
-import emojify from 'soapbox/features/emoji/emoji';
import Motion from 'soapbox/features/ui/util/optional_motion';
+import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
import RelativeTimestamp from './relative_timestamp';
@@ -21,11 +20,6 @@ const messages = defineMessages({
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();
- return obj;
-}, {});
-
export default @injectIntl
class Poll extends ImmutablePureComponent {
@@ -34,6 +28,7 @@ class Poll extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func,
disabled: PropTypes.bool,
+ me: SoapboxPropTypes.me,
status: PropTypes.string,
};
@@ -104,12 +99,7 @@ class Poll extends ImmutablePureComponent {
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 = poll.own_votes?.includes(optionIndex);
-
- let titleEmojified = option.get('title_emojified');
- if (!titleEmojified) {
- const emojiMap = makeEmojiMap(poll);
- titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
- }
+ const titleEmojified = option.get('title_emojified');
return (
diff --git a/app/soapbox/containers/poll_container.js b/app/soapbox/containers/poll_container.js
index 4c110cdc9..50d21517a 100644
--- a/app/soapbox/containers/poll_container.js
+++ b/app/soapbox/containers/poll_container.js
@@ -4,6 +4,7 @@ import Poll from 'soapbox/components/poll';
const mapStateToProps = (state, { pollId }) => ({
poll: state.getIn(['polls', pollId]),
+ me: state.get('me'),
});
diff --git a/app/soapbox/normalizers/__tests__/status-test.js b/app/soapbox/normalizers/__tests__/status-test.js
index f3c836f23..cd368ea0e 100644
--- a/app/soapbox/normalizers/__tests__/status-test.js
+++ b/app/soapbox/normalizers/__tests__/status-test.js
@@ -32,71 +32,79 @@ describe('normalizeStatus', () => {
it('adds mention to self in self-reply on Mastodon', () => {
const status = fromJS(require('soapbox/__fixtures__/mastodon-reply-to-self.json'));
- const expected = fromJS([{
+ const expected = {
id: '106801667066418367',
username: 'benis911',
acct: 'benis911',
url: 'https://mastodon.social/@benis911',
- }]);
+ };
- const result = normalizeStatus(status).get('mentions');
+ const result = normalizeStatus(status).mentions;
- expect(result).toEqual(expected);
+ expect(result.size).toBe(1);
+ expect(result.get(0).toJS()).toMatchObject(expected);
+ expect(result.get(0).id).toEqual('106801667066418367');
+ expect(ImmutableRecord.isRecord(result.get(0))).toBe(true);
});
it('normalizes mentions with only acct', () => {
const status = fromJS({ mentions: [{ acct: 'alex@gleasonator.com' }] });
- const expected = fromJS([{
+ const expected = [{
+ id: '',
acct: 'alex@gleasonator.com',
username: 'alex',
url: '',
- }]);
+ }];
const result = normalizeStatus(status).get('mentions');
- expect(result).toEqual(expected);
+ expect(result.toJS()).toEqual(expected);
});
it('normalizes Mitra attachments', () => {
const status = fromJS(require('soapbox/__fixtures__/mitra-status-with-attachments.json'));
- const expected = fromJS([{
+ const expected = [{
id: '017eeb0e-e5df-30a4-77a7-a929145cb836',
type: 'image',
url: 'https://mitra.social/media/8e04e6091bbbac79641b5812508683ce72c38693661c18d16040553f2371e18d.png',
preview_url: 'https://mitra.social/media/8e04e6091bbbac79641b5812508683ce72c38693661c18d16040553f2371e18d.png',
- remote_url: 'https://mitra.social/media/8e04e6091bbbac79641b5812508683ce72c38693661c18d16040553f2371e18d.png',
+ remote_url: null,
}, {
id: '017eeb0e-e5e4-2a48-2889-afdebf368a54',
type: 'unknown',
url: 'https://mitra.social/media/8f72dc2e98572eb4ba7c3a902bca5f69c448fc4391837e5f8f0d4556280440ac',
preview_url: 'https://mitra.social/media/8f72dc2e98572eb4ba7c3a902bca5f69c448fc4391837e5f8f0d4556280440ac',
- remote_url: 'https://mitra.social/media/8f72dc2e98572eb4ba7c3a902bca5f69c448fc4391837e5f8f0d4556280440ac',
+ remote_url: null,
}, {
id: '017eeb0e-e5e5-79fd-6054-8b6869b1db49',
type: 'unknown',
url: 'https://mitra.social/media/55a81a090247cc4fc127e5716bcf7964f6e0df9b584f85f4696c0b994747a4d0.oga',
preview_url: 'https://mitra.social/media/55a81a090247cc4fc127e5716bcf7964f6e0df9b584f85f4696c0b994747a4d0.oga',
- remote_url: 'https://mitra.social/media/55a81a090247cc4fc127e5716bcf7964f6e0df9b584f85f4696c0b994747a4d0.oga',
+ remote_url: null,
}, {
id: '017eeb0e-e5e6-c416-a444-21e560c47839',
type: 'unknown',
url: 'https://mitra.social/media/0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0',
preview_url: 'https://mitra.social/media/0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0',
- remote_url: 'https://mitra.social/media/0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0',
- }]);
+ remote_url: null,
+ }];
const result = normalizeStatus(status);
- expect(result.media_attachments).toEqual(expected);
+ expect(result.media_attachments.toJS()).toMatchObject(expected);
});
it('leaves Pleroma attachments alone', () => {
const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-attachments.json'));
- const result = normalizeStatus(status);
+ const result = normalizeStatus(status).media_attachments;
- expect(status.get('media_attachments')).toEqual(result.media_attachments);
+ expect(result.size).toBe(4);
+ expect(result.get(0).text_url).toBe(undefined);
+ expect(result.get(1).meta).toEqual(fromJS({}));
+ expect(result.getIn([1, 'pleroma', 'mime_type'])).toBe('application/x-nes-rom');
+ expect(ImmutableRecord.isRecord(result.get(3))).toBe(true);
});
it('normalizes Pleroma quote post', () => {
@@ -165,4 +173,17 @@ describe('normalizeStatus', () => {
expect(result.poll.voted).toBe(false);
expect(result.poll.own_votes).toBe(null);
});
+
+ it('normalizes poll with emojis', () => {
+ const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'));
+ const result = normalizeStatus(status);
+
+ // Emojifies poll options
+ expect(result.poll.options.get(1).title_emojified)
+ .toEqual('Custom emoji ');
+
+ // Parses emojis as Immutable.Record's
+ expect(ImmutableRecord.isRecord(result.poll.emojis.get(0))).toBe(true);
+ expect(result.poll.emojis.get(1).shortcode).toEqual('soapbox');
+ });
});
diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts
index f3006f3c2..d6e81033b 100644
--- a/app/soapbox/normalizers/account.ts
+++ b/app/soapbox/normalizers/account.ts
@@ -97,6 +97,13 @@ const normalizeLocation = (account: ImmutableMap) => {
});
};
+// Set username from acct, if applicable
+const fixUsername = (account: ImmutableMap) => {
+ return account.update('username', username => (
+ username || (account.get('acct') || '').split('@')[0]
+ ));
+};
+
export const normalizeAccount = (account: ImmutableMap): IAccount => {
return AccountRecord(
account.withMutations(account => {
@@ -104,6 +111,7 @@ export const normalizeAccount = (account: ImmutableMap): IAccount =
normalizeVerified(account);
normalizeBirthday(account);
normalizeLocation(account);
+ fixUsername(account);
}),
);
};
diff --git a/app/soapbox/normalizers/instance.js b/app/soapbox/normalizers/instance.js
index 8ec6fde85..470c66f53 100644
--- a/app/soapbox/normalizers/instance.js
+++ b/app/soapbox/normalizers/instance.js
@@ -94,9 +94,8 @@ export const normalizeInstance = instance => {
return isNumber(value) ? value : getAttachmentLimit(software);
});
- // Merge defaults & cleanup
+ // Merge defaults
instance.mergeDeepWith(mergeDefined, InstanceRecord());
- instance.deleteAll(['max_toot_chars', 'poll_limits']);
}),
);
};
diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts
index 727206265..46f18b5c0 100644
--- a/app/soapbox/normalizers/status.ts
+++ b/app/soapbox/normalizers/status.ts
@@ -1,12 +1,14 @@
+import escapeTextContentForBrowser from 'escape-html';
import {
Map as ImmutableMap,
List as ImmutableList,
Record as ImmutableRecord,
} from 'immutable';
+import emojify from 'soapbox/features/emoji/emoji';
+import { normalizeAccount } from 'soapbox/normalizers/account';
import { IStatus } from 'soapbox/types';
-import { accountToMention } from 'soapbox/utils/accounts';
-import { mergeDefined } from 'soapbox/utils/normalizers';
+import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers';
const StatusRecord = ImmutableRecord({
account: ImmutableMap(),
@@ -47,9 +49,25 @@ const StatusRecord = ImmutableRecord({
spoilerHtml: '',
});
-const PollOptionRecord = ImmutableRecord({
- title: '',
- votes_count: 0,
+// https://docs.joinmastodon.org/entities/attachment/
+const AttachmentRecord = ImmutableRecord({
+ blurhash: undefined,
+ description: '',
+ id: '',
+ meta: ImmutableMap(),
+ pleroma: ImmutableMap(),
+ preview_url: '',
+ remote_url: null,
+ type: 'unknown',
+ url: '',
+});
+
+// https://docs.joinmastodon.org/entities/mention/
+const MentionRecord = ImmutableRecord({
+ id: '',
+ acct: '',
+ username: '',
+ url: '',
});
// https://docs.joinmastodon.org/entities/poll/
@@ -66,6 +84,24 @@ const PollRecord = ImmutableRecord({
voted: false,
});
+// Sub-entity of Poll
+const PollOptionRecord = ImmutableRecord({
+ title: '',
+ votes_count: 0,
+
+ // Internal fields
+ title_emojified: '',
+});
+
+// https://docs.joinmastodon.org/entities/emoji/
+const EmojiRecord = ImmutableRecord({
+ category: '',
+ shortcode: '',
+ static_url: '',
+ url: '',
+ visible_in_picker: true,
+});
+
// Ensure attachments have required fields
// https://docs.joinmastodon.org/entities/attachment/
const normalizeAttachment = (attachment: ImmutableMap) => {
@@ -78,10 +114,9 @@ const normalizeAttachment = (attachment: ImmutableMap) => {
const base = ImmutableMap({
url,
preview_url: url,
- remote_url: url,
});
- return attachment.mergeWith(mergeDefined, base);
+ return AttachmentRecord(attachment.mergeWith(mergeDefined, base));
};
const normalizeAttachments = (status: ImmutableMap) => {
@@ -92,13 +127,7 @@ const normalizeAttachments = (status: ImmutableMap) => {
// Normalize mentions
const normalizeMention = (mention: ImmutableMap) => {
- const base = ImmutableMap({
- acct: '',
- username: (mention.get('acct') || '').split('@')[0],
- url: '',
- });
-
- return mention.mergeWith(mergeDefined, base);
+ return MentionRecord(normalizeAccount(mention));
};
const normalizeMentions = (status: ImmutableMap) => {
@@ -107,10 +136,28 @@ const normalizeMentions = (status: ImmutableMap) => {
});
};
+// Normalize emojis
+const normalizeEmojis = (entity: ImmutableMap) => {
+ return entity.update('emojis', ImmutableList(), emojis => {
+ return emojis.map(EmojiRecord);
+ });
+};
+
+const normalizePollOption = (option: ImmutableMap, emojis: ImmutableList> = ImmutableList()) => {
+ const emojiMap = makeEmojiMap(emojis);
+ const titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
+
+ return PollOptionRecord(
+ option.set('title_emojified', titleEmojified),
+ );
+};
+
// Normalize poll options
const normalizePollOptions = (poll: ImmutableMap) => {
+ const emojis = poll.get('emojis');
+
return poll.update('options', (options: ImmutableList>) => {
- return options.map(PollOptionRecord);
+ return options.map(option => normalizePollOption(option, emojis));
});
};
@@ -132,6 +179,7 @@ const normalizePollVoted = (poll: ImmutableMap) => {
const normalizePoll = (poll: ImmutableMap) => {
return PollRecord(
poll.withMutations((poll: ImmutableMap) => {
+ normalizeEmojis(poll);
normalizePollOptions(poll);
normalizePollOwnVotes(poll);
normalizePollVoted(poll);
@@ -172,8 +220,8 @@ const addSelfMention = (status: ImmutableMap) => {
const isSelfReply = accountId === status.get('in_reply_to_account_id');
const hasSelfMention = accountId === status.getIn(['mentions', 0, 'id']);
- if (isSelfReply && !hasSelfMention) {
- const mention = accountToMention(status.get('account'));
+ if (isSelfReply && !hasSelfMention && accountId) {
+ const mention = normalizeMention(status.get('account'));
return status.update('mentions', ImmutableList(), mentions => (
ImmutableList([mention]).concat(mentions)
));
@@ -195,6 +243,7 @@ export const normalizeStatus = (status: ImmutableMap): IStatus => {
status.withMutations(status => {
normalizeAttachments(status);
normalizeMentions(status);
+ normalizeEmojis(status);
normalizeStatusPoll(status);
fixMentionsOrder(status);
addSelfMention(status);
diff --git a/app/soapbox/reducers/__tests__/statuses-test.js b/app/soapbox/reducers/__tests__/statuses-test.js
index 3ea21ab64..130aa4ec6 100644
--- a/app/soapbox/reducers/__tests__/statuses-test.js
+++ b/app/soapbox/reducers/__tests__/statuses-test.js
@@ -1,4 +1,8 @@
-import { Map as ImmutableMap, Record, fromJS } from 'immutable';
+import {
+ Map as ImmutableMap,
+ Record as ImmutableRecord,
+ fromJS,
+} from 'immutable';
import { STATUS_IMPORT } from 'soapbox/actions/importer';
import {
@@ -19,7 +23,7 @@ describe('statuses reducer', () => {
const action = { type: STATUS_IMPORT, status };
const result = reducer(undefined, action).get('AFmFMSpITT9xcOJKcK');
- expect(Record.isRecord(result)).toBe(true);
+ expect(ImmutableRecord.isRecord(result)).toBe(true);
});
it('fixes the order of mentions', () => {
@@ -52,42 +56,45 @@ describe('statuses reducer', () => {
const state = reducer(undefined, { type: STATUS_IMPORT, status });
- const expected = fromJS([{
+ const expected = [{
id: '017eeb0e-e5df-30a4-77a7-a929145cb836',
type: 'image',
url: 'https://mitra.social/media/8e04e6091bbbac79641b5812508683ce72c38693661c18d16040553f2371e18d.png',
preview_url: 'https://mitra.social/media/8e04e6091bbbac79641b5812508683ce72c38693661c18d16040553f2371e18d.png',
- remote_url: 'https://mitra.social/media/8e04e6091bbbac79641b5812508683ce72c38693661c18d16040553f2371e18d.png',
+ remote_url: null,
}, {
id: '017eeb0e-e5e4-2a48-2889-afdebf368a54',
type: 'unknown',
url: 'https://mitra.social/media/8f72dc2e98572eb4ba7c3a902bca5f69c448fc4391837e5f8f0d4556280440ac',
preview_url: 'https://mitra.social/media/8f72dc2e98572eb4ba7c3a902bca5f69c448fc4391837e5f8f0d4556280440ac',
- remote_url: 'https://mitra.social/media/8f72dc2e98572eb4ba7c3a902bca5f69c448fc4391837e5f8f0d4556280440ac',
+ remote_url: null,
}, {
id: '017eeb0e-e5e5-79fd-6054-8b6869b1db49',
type: 'unknown',
url: 'https://mitra.social/media/55a81a090247cc4fc127e5716bcf7964f6e0df9b584f85f4696c0b994747a4d0.oga',
preview_url: 'https://mitra.social/media/55a81a090247cc4fc127e5716bcf7964f6e0df9b584f85f4696c0b994747a4d0.oga',
- remote_url: 'https://mitra.social/media/55a81a090247cc4fc127e5716bcf7964f6e0df9b584f85f4696c0b994747a4d0.oga',
+ remote_url: null,
}, {
id: '017eeb0e-e5e6-c416-a444-21e560c47839',
type: 'unknown',
url: 'https://mitra.social/media/0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0',
preview_url: 'https://mitra.social/media/0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0',
- remote_url: 'https://mitra.social/media/0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0',
- }]);
+ remote_url: null,
+ }];
- expect(state.getIn(['017eeb0e-e5e7-98fe-6b2b-ad02349251fb', 'media_attachments'])).toEqual(expected);
+ expect(state.getIn(['017eeb0e-e5e7-98fe-6b2b-ad02349251fb', 'media_attachments']).toJS()).toMatchObject(expected);
});
- it('leaves Pleroma attachments alone', () => {
+ it('fixes Pleroma attachments', () => {
const status = require('soapbox/__fixtures__/pleroma-status-with-attachments.json');
const action = { type: STATUS_IMPORT, status };
const state = reducer(undefined, action);
- const expected = fromJS(status.media_attachments);
+ const result = state.get('AGNkA21auFR5lnEAHw').media_attachments;
- expect(state.getIn(['AGNkA21auFR5lnEAHw', 'media_attachments'])).toEqual(expected);
+ expect(result.size).toBe(4);
+ expect(result.get(0).text_url).toBe(undefined);
+ expect(result.get(1).meta).toEqual(ImmutableMap());
+ expect(result.getIn([1, 'pleroma', 'mime_type'])).toBe('application/x-nes-rom');
});
it('hides CWs', () => {
diff --git a/app/soapbox/reducers/statuses.js b/app/soapbox/reducers/statuses.js
index a7fe3b479..957640c9c 100644
--- a/app/soapbox/reducers/statuses.js
+++ b/app/soapbox/reducers/statuses.js
@@ -5,6 +5,7 @@ import emojify from 'soapbox/features/emoji/emoji';
import { normalizeStatus } from 'soapbox/normalizers/status';
import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts';
import { stripCompatibilityFeatures } from 'soapbox/utils/html';
+import { makeEmojiMap } from 'soapbox/utils/normalizers';
import {
EMOJI_REACT_REQUEST,
@@ -32,11 +33,6 @@ import { TIMELINE_DELETE } from '../actions/timelines';
const domParser = new DOMParser();
-const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
- obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
- return obj;
-}, {});
-
const minifyStatus = status => {
return status.mergeWith((o, n) => n || o, {
account: status.getIn(['account', 'id']),
@@ -59,7 +55,7 @@ export const calculateStatus = (status, oldStatus, expandSpoilers = false) => {
} else {
const spoilerText = status.get('spoiler_text') || '';
const searchContent = (ImmutableList([spoilerText, status.get('content')]).concat(status.getIn(['poll', 'options'], ImmutableList()).map(option => option.get('title')))).join('\n\n').replace(/
/g, '\n').replace(/<\/p>/g, '\n\n');
- const emojiMap = makeEmojiMap(status);
+ const emojiMap = makeEmojiMap(status.get('emojis'));
return status.merge({
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent,
diff --git a/app/soapbox/utils/__tests__/accounts-test.js b/app/soapbox/utils/__tests__/accounts-test.js
index a9a77ffe1..15a42ec57 100644
--- a/app/soapbox/utils/__tests__/accounts-test.js
+++ b/app/soapbox/utils/__tests__/accounts-test.js
@@ -6,7 +6,6 @@ import {
isStaff,
isAdmin,
isModerator,
- accountToMention,
} from '../accounts';
describe('getDomain', () => {
@@ -116,19 +115,3 @@ describe('isModerator', () => {
});
});
});
-
-describe('accountToMention', () => {
- it('converts the account to a mention', () => {
- const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json'));
-
- const expected = fromJS({
- id: '9v5bmRalQvjOy0ECcC',
- username: 'alex',
- acct: 'alex',
- url: 'https://gleasonator.com/users/alex',
- });
-
- const result = accountToMention(account);
- expect(result).toEqual(expected);
- });
-});
diff --git a/app/soapbox/utils/accounts.ts b/app/soapbox/utils/accounts.ts
index 2300b4363..e93138c86 100644
--- a/app/soapbox/utils/accounts.ts
+++ b/app/soapbox/utils/accounts.ts
@@ -62,12 +62,3 @@ export const isLocal = (account: ImmutableMap): boolean => {
};
export const isRemote = (account: ImmutableMap): boolean => !isLocal(account);
-
-export const accountToMention = (account: ImmutableMap): ImmutableMap => {
- return ImmutableMap({
- id: account.get('id'),
- username: account.get('username'),
- acct: account.get('acct'),
- url: account.get('url'),
- });
-};
diff --git a/app/soapbox/utils/normalizers.js b/app/soapbox/utils/normalizers.js
index 7d205f21c..d16b2a07c 100644
--- a/app/soapbox/utils/normalizers.js
+++ b/app/soapbox/utils/normalizers.js
@@ -1,2 +1,7 @@
// Use new value only if old value is undefined
export const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal;
+
+export const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => {
+ obj[`:${emoji.shortcode}:`] = emoji;
+ return obj;
+}, {});
diff --git a/package.json b/package.json
index dcdca8734..2608b779a 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
"@sentry/react": "^6.12.0",
"@sentry/tracing": "^6.12.0",
"@tabler/icons": "^1.53.0",
+ "@types/escape-html": "^1.0.1",
"array-includes": "^3.0.3",
"autoprefixer": "^10.0.0",
"axios": "^0.21.4",
diff --git a/yarn.lock b/yarn.lock
index 36ed26b5d..2127e09ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1747,6 +1747,11 @@
dependencies:
"@babel/types" "^7.3.0"
+"@types/escape-html@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.1.tgz#b19b4646915f0ae2c306bf984dc0a59c5cfc97ba"
+ integrity sha512-4mI1FuUUZiuT95fSVqvZxp/ssQK9zsa86S43h9x3zPOSU9BBJ+BfDkXwuaU7BfsD+e7U0/cUUfJFk3iW2M4okA==
+
"@types/eslint-scope@^3.7.0":
version "3.7.1"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e"