diff --git a/app/soapbox/__fixtures__/account-moved.json b/app/soapbox/__fixtures__/account-moved.json new file mode 100644 index 000000000..dbb194916 --- /dev/null +++ b/app/soapbox/__fixtures__/account-moved.json @@ -0,0 +1,46 @@ +{ + "id": "106801667066418367", + "username": "benis911", + "acct": "benis911", + "display_name": "", + "locked": false, + "bot": false, + "discoverable": null, + "group": false, + "created_at": "2021-08-22T00:00:00.000Z", + "note": "", + "url": "https://mastodon.social/@benis911", + "avatar": "https://mastodon.social/avatars/original/missing.png", + "avatar_static": "https://mastodon.social/avatars/original/missing.png", + "header": "https://mastodon.social/headers/original/missing.png", + "header_static": "https://mastodon.social/headers/original/missing.png", + "followers_count": 0, + "following_count": 0, + "statuses_count": 5, + "last_status_at": "2022-02-23", + "moved": { + "id": "107945464165013501", + "username": "alex", + "acct": "alex@fedibird.com", + "display_name": "", + "locked": false, + "bot": false, + "discoverable": false, + "group": false, + "created_at": "2020-01-27T00:00:00.000Z", + "note": "

", + "url": "https://fedibird.com/@alex", + "avatar": "https://mastodon.social/avatars/original/missing.png", + "avatar_static": "https://mastodon.social/avatars/original/missing.png", + "header": "https://mastodon.social/headers/original/missing.png", + "header_static": "https://mastodon.social/headers/original/missing.png", + "followers_count": 1, + "following_count": 1, + "statuses_count": 5, + "last_status_at": null, + "emojis": [], + "fields": [] + }, + "emojis": [], + "fields": [] +} diff --git a/app/soapbox/__fixtures__/account-with-emojis.json b/app/soapbox/__fixtures__/account-with-emojis.json new file mode 100644 index 000000000..19025e150 --- /dev/null +++ b/app/soapbox/__fixtures__/account-with-emojis.json @@ -0,0 +1,140 @@ +{ + "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 😂 :soapbox: :ablobcatrainbow:", + "emojis": [ + { + "shortcode": "ablobcatrainbow", + "static_url": "https://gleasonator.com/emoji/blobcat/ablobcatrainbow.png", + "url": "https://gleasonator.com/emoji/blobcat/ablobcatrainbow.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 + } + ], + "fields": [ + { + "name": "Website", + "value": "https://alexgleason.me" + }, + { + "name": "Soapbox :ablobcatrainbow:", + "value": "https://soapbox.pub :soapbox:" + }, + { + "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": 2476, + "following_count": 1584, + "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-12T16:35:10", + "locked": false, + "note": "I create Fediverse software that empowers people online. :soapbox:

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 :ablobcatrainbow:", + "value": "https://soapbox.pub :soapbox:" + }, + { + "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. :soapbox:\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": 23674, + "url": "https://gleasonator.com/users/alex", + "username": "alex" +} diff --git a/app/soapbox/__fixtures__/status-with-card.json b/app/soapbox/__fixtures__/status-with-card.json new file mode 100644 index 000000000..da2f83b94 --- /dev/null +++ b/app/soapbox/__fixtures__/status-with-card.json @@ -0,0 +1,210 @@ +{ + "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": [ + { + "shortcode": "soapbox", + "static_url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png", + "url": "https://gleasonator.com/emoji/Gleasonator/soapbox.png", + "visible_in_picker": false + } + ], + "fields": [ + { + "name": "Website", + "value": "https://alexgleason.me" + }, + { + "name": "Soapbox :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": 2476, + "following_count": 1584, + "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-12T16:35:10", + "locked": false, + "note": "I create Fediverse software that empowers people online. :soapbox:

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 :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. :soapbox:\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": 23674, + "url": "https://gleasonator.com/users/alex", + "username": "alex" + }, + "application": null, + "bookmarked": false, + "card": { + "author_name": "Alex Gleason", + "author_url": "https://soapbox.pub/author/alex/", + "blurhash": null, + "description": "On cryptocurrency I’ve always believed integrated donations would be a necessary part of the Fediverse. Admins do all the heavy lifting; it’s a thankless job. Meanwhile users want to help secure their new online home, but feel powerless to do so. I have been running an experimental payment platform based on Stripe alongside my Soapbox […]", + "embed_url": null, + "height": 338, + "html": "", + "image": "https://gleasonator.com/proxy/L2kUi5uxMdoC6LYYrnAdlJviPGQ/aHR0cHM6Ly9tZWRpYS5zb2FwYm94LnB1Yi91cGxvYWRzLzIwMjEvMDcvdi0xLTMtdGh1bWIucG5n/v-1-3-thumb.png", + "provider_name": "Soapbox", + "provider_url": "https://soapbox.pub", + "title": "Soapbox FE v1.3: The Crypto Release - Soapbox", + "type": "link", + "url": "https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/", + "width": 600 + }, + "content": "

Soapbox FE v1.3 released. Read about it here: https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/

Enjoy!

", + "created_at": "2021-07-02T20:49:39.000Z", + "emojis": [], + "favourited": false, + "favourites_count": 29, + "id": "A8tEMYF2GNnfPcL4dc", + "in_reply_to_account_id": null, + "in_reply_to_id": null, + "language": null, + "media_attachments": [], + "mentions": [], + "muted": false, + "pinned": true, + "pleroma": { + "content": { + "text/plain": "Soapbox FE v1.3 released. Read about it here: https://soapbox.pub/blog/soapbox-fe-v1.3-cryptocurrency-release/Enjoy!" + }, + "conversation_id": "16496668", + "direct_conversation_id": null, + "emoji_reactions": [ + { + "count": 5, + "me": false, + "name": "❤️" + }, + { + "count": 1, + "me": false, + "name": "👍" + } + ], + "expires_at": null, + "in_reply_to_account_acct": null, + "local": true, + "parent_visible": false, + "pinned_at": "2021-11-23T01:38:44.000Z", + "quote": null, + "quote_url": null, + "quote_visible": false, + "spoiler_text": { + "text/plain": "" + }, + "thread_muted": false + }, + "poll": null, + "reblog": null, + "reblogged": false, + "reblogs_count": 16, + "replies_count": 7, + "sensitive": false, + "spoiler_text": "", + "tags": [], + "text": null, + "uri": "https://gleasonator.com/objects/3eabaf63-47f4-4314-9ddb-ce7dbf46b393", + "url": "https://gleasonator.com/notice/A8tEMYF2GNnfPcL4dc", + "visibility": "public" +} diff --git a/app/soapbox/actions/importer/index.js b/app/soapbox/actions/importer/index.js index 1668ad71e..ee3af3ac3 100644 --- a/app/soapbox/actions/importer/index.js +++ b/app/soapbox/actions/importer/index.js @@ -1,7 +1,5 @@ import { getSettings } from '../settings'; -import { normalizeAccount } from './normalizer'; - export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT'; export const STATUS_IMPORT = 'STATUS_IMPORT'; @@ -45,7 +43,7 @@ export function importFetchedAccounts(accounts) { function processAccount(account) { if (!account.id) return; - normalAccounts.push(normalizeAccount(account)); + normalAccounts.push(account); if (account.moved) { processAccount(account.moved); diff --git a/app/soapbox/actions/importer/normalizer.js b/app/soapbox/actions/importer/normalizer.js deleted file mode 100644 index d61932957..000000000 --- a/app/soapbox/actions/importer/normalizer.js +++ /dev/null @@ -1,42 +0,0 @@ -import escapeTextContentForBrowser from 'escape-html'; - -import emojify from '../../features/emoji/emoji'; -import { unescapeHTML } from '../../utils/html'; - -const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => { - obj[`:${emoji.shortcode}:`] = emoji; - return obj; -}, {}); - -export function normalizeAccount(account) { - account = { ...account }; - - // Some backends can return null, or omit these required fields - if (!account.emojis) account.emojis = []; - if (!account.display_name) account.display_name = ''; - if (!account.note) account.note = ''; - if (!account.avatar) account.avatar = account.avatar_static || require('images/avatar-missing.png'); - if (!account.avatar_static) account.avatar_static = account.avatar; - - const emojiMap = makeEmojiMap(account); - const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name; - - account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap); - account.note_emojified = emojify(account.note, emojiMap); - account.note_plain = unescapeHTML(account.note); - - if (account.fields) { - account.fields = account.fields.map(pair => ({ - ...pair, - name_emojified: emojify(escapeTextContentForBrowser(pair.name)), - value_emojified: emojify(pair.value, emojiMap), - value_plain: unescapeHTML(pair.value), - })); - } - - if (account.moved) { - account.moved = account.moved.id; - } - - return account; -} diff --git a/app/soapbox/normalizers/__tests__/account-test.js b/app/soapbox/normalizers/__tests__/account-test.js index 5199e3492..9211a1cf8 100644 --- a/app/soapbox/normalizers/__tests__/account-test.js +++ b/app/soapbox/normalizers/__tests__/account-test.js @@ -1,20 +1,48 @@ -import { fromJS } from 'immutable'; +import { Record as ImmutableRecord, fromJS } from 'immutable'; import { normalizeAccount } from '../account'; +const AVATAR_MISSING = require('images/avatar-missing.png'); + describe('normalizeAccount()', () => { + it('adds base fields', () => { + const account = fromJS({}); + const result = normalizeAccount(account); + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.acct).toEqual(''); + expect(result.note).toEqual(''); + expect(result.avatar).toEqual(AVATAR_MISSING); + }); + + it('normalizes a mention', () => { + const mention = fromJS({ + acct: 'NEETzsche@iddqd.social', + id: '9v5bw7hEGBPc9nrpzc', + url: 'https://iddqd.social/users/NEETzsche', + username: 'NEETzsche', + }); + + const result = normalizeAccount(mention); + expect(result.emojis).toEqual(fromJS([])); + expect(result.display_name).toEqual('NEETzsche'); + expect(result.avatar).toEqual(AVATAR_MISSING); + expect(result.avatar_static).toEqual(AVATAR_MISSING); + expect(result.verified).toBe(false); + }); + it('normalizes Fedibird birthday', () => { const account = fromJS(require('soapbox/__fixtures__/fedibird-account.json')); const result = normalizeAccount(account); - expect(result.get('birthday')).toEqual('1993-07-03'); + expect(result.birthday).toEqual('1993-07-03'); }); it('normalizes Pleroma birthday', () => { const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); const result = normalizeAccount(account); - expect(result.get('birthday')).toEqual('1993-07-03'); + expect(result.birthday).toEqual('1993-07-03'); }); it('normalizes Pleroma legacy fields', () => { @@ -40,30 +68,79 @@ describe('normalizeAccount()', () => { it('normalizes a verified Pleroma user', () => { const account = fromJS(require('soapbox/__fixtures__/mk.json')); const result = normalizeAccount(account); - expect(result.get('verified')).toBe(true); + expect(result.verified).toBe(true); }); it('normalizes an unverified Pleroma user', () => { const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); const result = normalizeAccount(account); - expect(result.get('verified')).toBe(false); + expect(result.verified).toBe(false); }); it('normalizes a verified Truth Social user', () => { const account = fromJS(require('soapbox/__fixtures__/realDonaldTrump.json')); const result = normalizeAccount(account); - expect(result.get('verified')).toBe(true); + expect(result.verified).toBe(true); }); it('normalizes Fedibird location', () => { const account = fromJS(require('soapbox/__fixtures__/fedibird-account.json')); const result = normalizeAccount(account); - expect(result.get('location')).toBe('Texas, USA'); + expect(result.location).toBe('Texas, USA'); }); it('normalizes Truth Social location', () => { const account = fromJS(require('soapbox/__fixtures__/truthsocial-account.json')); const result = normalizeAccount(account); - expect(result.get('location')).toBe('Texas'); + expect(result.location).toBe('Texas'); + }); + + it('sets display_name from username', () => { + const account = fromJS({ username: 'alex' }); + const result = normalizeAccount(account); + expect(result.display_name).toBe('alex'); + }); + + it('sets display_name from acct', () => { + const account = fromJS({ acct: 'alex@gleasonator.com' }); + const result = normalizeAccount(account); + expect(result.display_name).toBe('alex'); + }); + + it('overrides a whitespace display_name', () => { + const account = fromJS({ username: 'alex', display_name: ' ' }); + const result = normalizeAccount(account); + expect(result.display_name).toBe('alex'); + }); + + it('emojifies display name as `display_name_html`', () => { + const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const result = normalizeAccount(account); + const expected = 'Alex Gleason 😂 :soapbox: :ablobcatrainbow:'; + expect(result.display_name_html).toBe(expected); + }); + + it('emojifies note as `note_emojified`', () => { + const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const result = normalizeAccount(account); + const expected = 'I create Fediverse software that empowers people online. :soapbox:

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.'; + expect(result.note_emojified).toBe(expected); + }); + + it('unescapes HTML note as `note_plain`', () => { + const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const result = normalizeAccount(account); + const expected = 'I create Fediverse software that empowers people online. :soapbox:\n\nI\'m vegan btw\n\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.'; + expect(result.note_plain).toBe(expected); + }); + + it('emojifies custom profile field', () => { + const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const result = normalizeAccount(account); + const field = result.fields.get(1); + + expect(field.name_emojified).toBe('Soapbox :ablobcatrainbow:'); + expect(field.value_emojified).toBe('https://soapbox.pub :soapbox:'); + expect(field.value_plain).toBe('https://soapbox.pub :soapbox:'); }); }); diff --git a/app/soapbox/normalizers/__tests__/attachment-test.js b/app/soapbox/normalizers/__tests__/attachment-test.js new file mode 100644 index 000000000..ecf3813e7 --- /dev/null +++ b/app/soapbox/normalizers/__tests__/attachment-test.js @@ -0,0 +1,21 @@ +import { Record as ImmutableRecord, fromJS } from 'immutable'; + +import { normalizeAttachment } from '../attachment'; + +describe('normalizeAttachment()', () => { + it('adds base fields', () => { + const attachment = fromJS({}); + const result = normalizeAttachment(attachment); + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.type).toEqual('unknown'); + expect(result.url).toEqual(''); + }); + + it('infers preview_url from url', () => { + const attachment = fromJS({ url: 'https://site.fedi/123.png' }); + const result = normalizeAttachment(attachment); + + expect(result.preview_url).toEqual('https://site.fedi/123.png'); + }); +}); diff --git a/app/soapbox/normalizers/__tests__/card-test.js b/app/soapbox/normalizers/__tests__/card-test.js new file mode 100644 index 000000000..e8ac120b0 --- /dev/null +++ b/app/soapbox/normalizers/__tests__/card-test.js @@ -0,0 +1,14 @@ +import { Record as ImmutableRecord, fromJS } from 'immutable'; + +import { normalizeCard } from '../card'; + +describe('normalizeCard()', () => { + it('adds base fields', () => { + const card = fromJS({}); + const result = normalizeCard(card); + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.type).toEqual('link'); + expect(result.url).toEqual(''); + }); +}); diff --git a/app/soapbox/normalizers/__tests__/mention-test.js b/app/soapbox/normalizers/__tests__/mention-test.js new file mode 100644 index 000000000..e429a03b1 --- /dev/null +++ b/app/soapbox/normalizers/__tests__/mention-test.js @@ -0,0 +1,23 @@ +import { Record as ImmutableRecord, fromJS } from 'immutable'; + +import { normalizeMention } from '../mention'; + +describe('normalizeMention()', () => { + it('adds base fields', () => { + const account = fromJS({}); + const result = normalizeMention(account); + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(result.id).toEqual(''); + expect(result.acct).toEqual(''); + expect(result.username).toEqual(''); + expect(result.url).toEqual(''); + }); + + it('infers username from acct', () => { + const account = fromJS({ acct: 'alex@gleasonator.com' }); + const result = normalizeMention(account); + + expect(result.username).toEqual('alex'); + }); +}); diff --git a/app/soapbox/normalizers/__tests__/poll-test.js b/app/soapbox/normalizers/__tests__/poll-test.js new file mode 100644 index 000000000..4cc9dbbbc --- /dev/null +++ b/app/soapbox/normalizers/__tests__/poll-test.js @@ -0,0 +1,48 @@ +import { Record as ImmutableRecord, fromJS } from 'immutable'; + +import { normalizePoll } from '../poll'; + +describe('normalizePoll()', () => { + it('adds base fields', () => { + const poll = fromJS({ options: [{ title: 'Apples' }] }); + const result = normalizePoll(poll); + + const expected = { + options: [{ title: 'Apples', votes_count: 0 }], + emojis: [], + expired: false, + multiple: false, + voters_count: 0, + votes_count: 0, + own_votes: null, + voted: false, + }; + + expect(ImmutableRecord.isRecord(result)).toBe(true); + expect(ImmutableRecord.isRecord(result.options.get(0))).toBe(true); + expect(result.toJS()).toMatchObject(expected); + expect(result.expires_at instanceof Date).toBe(true); + }); + + it('normalizes a Pleroma logged-out poll', () => { + const poll = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll.json')).get('poll'); + const result = normalizePoll(poll); + + // Adds logged-in fields + expect(result.voted).toBe(false); + expect(result.own_votes).toBe(null); + }); + + it('normalizes poll with emojis', () => { + const poll = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json')).get('poll'); + const result = normalizePoll(poll); + + // Emojifies poll options + expect(result.options.get(1).title_emojified) + .toEqual('Custom emoji :gleason_excited: '); + + // Parses emojis as Immutable.Record's + expect(ImmutableRecord.isRecord(result.emojis.get(0))).toBe(true); + expect(result.emojis.get(1).shortcode).toEqual('soapbox'); + }); +}); diff --git a/app/soapbox/normalizers/__tests__/status-test.js b/app/soapbox/normalizers/__tests__/status-test.js index cd368ea0e..2824615cc 100644 --- a/app/soapbox/normalizers/__tests__/status-test.js +++ b/app/soapbox/normalizers/__tests__/status-test.js @@ -2,7 +2,7 @@ import { Record as ImmutableRecord, fromJS } from 'immutable'; import { normalizeStatus } from '../status'; -describe('normalizeStatus', () => { +describe('normalizeStatus()', () => { it('adds base fields', () => { const status = fromJS({}); const result = normalizeStatus(status); @@ -186,4 +186,13 @@ describe('normalizeStatus', () => { expect(ImmutableRecord.isRecord(result.poll.emojis.get(0))).toBe(true); expect(result.poll.emojis.get(1).shortcode).toEqual('soapbox'); }); + + it('normalizes a card', () => { + const status = fromJS(require('soapbox/__fixtures__/status-with-card.json')); + const result = normalizeStatus(status); + + expect(ImmutableRecord.isRecord(result.card)).toBe(true); + expect(result.card.type).toEqual('link'); + expect(result.card.provider_url).toEqual('https://soapbox.pub'); + }); }); diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index d6e81033b..7faa3282c 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -1,12 +1,22 @@ +/** + * Account normalizer: + * Converts API accounts into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/account/} + */ +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 { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { IAccount } from 'soapbox/types'; -import { mergeDefined } from 'soapbox/utils/normalizers'; +import { unescapeHTML } from 'soapbox/utils/html'; +import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers'; +// https://docs.joinmastodon.org/entities/account/ const AccountRecord = ImmutableRecord({ acct: '', avatar: '', @@ -45,6 +55,18 @@ const AccountRecord = ImmutableRecord({ should_refetch: false, }); +// https://docs.joinmastodon.org/entities/field/ +const FieldRecord = ImmutableRecord({ + name: '', + value: '', + verified_at: null, + + // Internal fields + name_emojified: '', + value_emojified: '', + value_plain: '', +}); + // https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/549 const normalizePleromaLegacyFields = (account: ImmutableMap) => { return account.update('pleroma', ImmutableMap(), (pleroma: ImmutableMap) => { @@ -61,6 +83,29 @@ const normalizePleromaLegacyFields = (account: ImmutableMap) => { }); }; +// Add avatar, if missing +const normalizeAvatar = (account: ImmutableMap) => { + const avatar = account.get('avatar'); + const avatarStatic = account.get('avatar_static'); + const missing = require('images/avatar-missing.png'); + + return account.withMutations(account => { + account.set('avatar', avatar || avatarStatic || missing); + account.set('avatar_static', avatarStatic || avatar || missing); + }); +}; + +// Normalize custom fields +const normalizeFields = (account: ImmutableMap) => { + return account.update('fields', ImmutableList(), fields => fields.map(FieldRecord)); +}; + +// Normalize emojis +const normalizeEmojis = (entity: ImmutableMap) => { + const emojis = entity.get('emojis', ImmutableList()).map(normalizeEmoji); + return entity.set('emojis', emojis); +}; + // Normalize Pleroma/Fedibird birthday const normalizeBirthday = (account: ImmutableMap) => { const birthday = [ @@ -99,19 +144,55 @@ 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] - )); + const acct = account.get('acct') || ''; + const username = account.get('username') || ''; + return account.set('username', username || acct.split('@')[0]); +}; + +// Set display name from username, if applicable +const fixDisplayName = (account: ImmutableMap) => { + const displayName = account.get('display_name') || ''; + return account.set('display_name', displayName.trim().length === 0 ? account.get('username') : displayName); +}; + +// Emojification, etc +const addInternalFields = (account: ImmutableMap) => { + const emojiMap = makeEmojiMap(account.get('emojis')); + + return account.withMutations((account: ImmutableMap) => { + // Emojify account properties + account.merge({ + display_name_html: emojify(escapeTextContentForBrowser(account.get('display_name')), emojiMap), + note_emojified: emojify(account.get('note', ''), emojiMap), + note_plain: unescapeHTML(account.get('note', '')), + }); + + // Emojify fields + account.update('fields', ImmutableList(), fields => { + return fields.map((field: ImmutableMap) => { + return field.merge({ + name_emojified: emojify(escapeTextContentForBrowser(field.get('name')), emojiMap), + value_emojified: emojify(field.get('value'), emojiMap), + value_plain: unescapeHTML(field.get('value')), + }); + }); + }); + }); }; export const normalizeAccount = (account: ImmutableMap): IAccount => { return AccountRecord( account.withMutations(account => { normalizePleromaLegacyFields(account); + normalizeEmojis(account); + normalizeAvatar(account); + normalizeFields(account); normalizeVerified(account); normalizeBirthday(account); normalizeLocation(account); fixUsername(account); + fixDisplayName(account); + addInternalFields(account); }), ); }; diff --git a/app/soapbox/normalizers/attachment.ts b/app/soapbox/normalizers/attachment.ts new file mode 100644 index 000000000..9599fc1ce --- /dev/null +++ b/app/soapbox/normalizers/attachment.ts @@ -0,0 +1,45 @@ +/** + * Attachment normalizer: + * Converts API attachments into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/attachment/} + */ +import { + Map as ImmutableMap, + Record as ImmutableRecord, +} from 'immutable'; + +import { mergeDefined } from 'soapbox/utils/normalizers'; + +// 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: '', + + // Internal fields + // TODO: Remove these? They're set in selectors/index.js + account: null, + status: null, +}); + +// Ensure attachments have required fields +export const normalizeAttachment = (attachment: ImmutableMap) => { + const url = [ + attachment.get('url'), + attachment.get('preview_url'), + attachment.get('remote_url'), + ].find(url => url) || ''; + + const base = ImmutableMap({ + url, + preview_url: url, + }); + + return AttachmentRecord(attachment.mergeWith(mergeDefined, base)); +}; diff --git a/app/soapbox/normalizers/card.ts b/app/soapbox/normalizers/card.ts new file mode 100644 index 000000000..c9ac76adb --- /dev/null +++ b/app/soapbox/normalizers/card.ts @@ -0,0 +1,28 @@ +/** + * Card normalizer: + * Converts API cards into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/card/} + */ +import { Record as ImmutableRecord, Map as ImmutableMap } from 'immutable'; + +// https://docs.joinmastodon.org/entities/card/ +const CardRecord = ImmutableRecord({ + author_name: '', + author_url: '', + blurhash: null, + description: '', + embed_url: '', + height: 0, + html: '', + image: null, + provider_name: '', + provider_url: '', + title: '', + type: 'link', + url: '', + width: 0, +}); + +export const normalizeCard = (card: ImmutableMap) => { + return CardRecord(card); +}; diff --git a/app/soapbox/normalizers/emoji.ts b/app/soapbox/normalizers/emoji.ts new file mode 100644 index 000000000..f450af253 --- /dev/null +++ b/app/soapbox/normalizers/emoji.ts @@ -0,0 +1,19 @@ +/** + * Emoji normalizer: + * Converts API emojis into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/emoji/} + */ +import { Record as ImmutableRecord, Map as ImmutableMap } from 'immutable'; + +// https://docs.joinmastodon.org/entities/emoji/ +const EmojiRecord = ImmutableRecord({ + category: '', + shortcode: '', + static_url: '', + url: '', + visible_in_picker: true, +}); + +export const normalizeEmoji = (emoji: ImmutableMap) => { + return EmojiRecord(emoji); +}; diff --git a/app/soapbox/normalizers/instance.ts b/app/soapbox/normalizers/instance.ts index 1ae2801d3..8e0fe02a6 100644 --- a/app/soapbox/normalizers/instance.ts +++ b/app/soapbox/normalizers/instance.ts @@ -1,3 +1,8 @@ +/** + * Instance normalizer: + * Converts API instances into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/instance/} + */ import { Map as ImmutableMap, List as ImmutableList, @@ -9,6 +14,7 @@ import { mergeDefined } from 'soapbox/utils/normalizers'; import { isNumber } from 'soapbox/utils/numbers'; // Use Mastodon defaults +// https://docs.joinmastodon.org/entities/instance/ const InstanceRecord = ImmutableRecord({ approval_required: false, contact_account: ImmutableMap(), diff --git a/app/soapbox/normalizers/mention.ts b/app/soapbox/normalizers/mention.ts new file mode 100644 index 000000000..998202065 --- /dev/null +++ b/app/soapbox/normalizers/mention.ts @@ -0,0 +1,24 @@ +/** + * Mention normalizer: + * Converts API mentions into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/mention/} + */ +import { + Map as ImmutableMap, + Record as ImmutableRecord, +} from 'immutable'; + +import { normalizeAccount } from 'soapbox/normalizers/account'; + +// https://docs.joinmastodon.org/entities/mention/ +const MentionRecord = ImmutableRecord({ + id: '', + acct: '', + username: '', + url: '', +}); + +export const normalizeMention = (mention: ImmutableMap) => { + // Simply normalize it as an account then cast it as a mention ¯\_(ツ)_/¯ + return MentionRecord(normalizeAccount(mention)); +}; diff --git a/app/soapbox/normalizers/notification.ts b/app/soapbox/normalizers/notification.ts index ab369a52b..e0f466618 100644 --- a/app/soapbox/normalizers/notification.ts +++ b/app/soapbox/normalizers/notification.ts @@ -1,3 +1,8 @@ +/** + * Notification normalizer: + * Converts API notifications into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/notification/} + */ import { Map as ImmutableMap, Record as ImmutableRecord, diff --git a/app/soapbox/normalizers/poll.ts b/app/soapbox/normalizers/poll.ts new file mode 100644 index 000000000..fa127702e --- /dev/null +++ b/app/soapbox/normalizers/poll.ts @@ -0,0 +1,88 @@ +/** + * Poll normalizer: + * Converts API polls into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/poll/} + */ +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 { normalizeEmoji } from 'soapbox/normalizers/emoji'; +import { makeEmojiMap } from 'soapbox/utils/normalizers'; + +// https://docs.joinmastodon.org/entities/poll/ +const PollRecord = ImmutableRecord({ + emojis: ImmutableList(), + expired: false, + expires_at: new Date(), + id: '', + multiple: false, + options: ImmutableList(), + voters_count: 0, + votes_count: 0, + own_votes: null, + voted: false, +}); + +// Sub-entity of Poll +const PollOptionRecord = ImmutableRecord({ + title: '', + votes_count: 0, + + // Internal fields + title_emojified: '', +}); + +// Normalize emojis +const normalizeEmojis = (entity: ImmutableMap) => { + return entity.update('emojis', ImmutableList(), emojis => { + return emojis.map(normalizeEmoji); + }); +}; + +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(option => normalizePollOption(option, emojis)); + }); +}; + +// Normalize own_votes to `null` if empty (like Mastodon) +const normalizePollOwnVotes = (poll: ImmutableMap) => { + return poll.update('own_votes', ownVotes => { + return ownVotes?.size > 0 ? ownVotes : null; + }); +}; + +// Whether the user voted in the poll +const normalizePollVoted = (poll: ImmutableMap) => { + return poll.update('voted', voted => { + return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0; + }); +}; + +export const normalizePoll = (poll: ImmutableMap) => { + return PollRecord( + poll.withMutations((poll: ImmutableMap) => { + normalizeEmojis(poll); + normalizePollOptions(poll); + normalizePollOwnVotes(poll); + normalizePollVoted(poll); + }), + ); +}; diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 1abfe539d..2a1039f45 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -1,15 +1,22 @@ -import escapeTextContentForBrowser from 'escape-html'; +/** + * Status normalizer: + * Converts API statuses into our internal format. + * @see {@link https://docs.joinmastodon.org/entities/status/} + */ 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 { normalizeAttachment } from 'soapbox/normalizers/attachment'; +import { normalizeCard } from 'soapbox/normalizers/card'; +import { normalizeEmoji } from 'soapbox/normalizers/emoji'; +import { normalizeMention } from 'soapbox/normalizers/mention'; +import { normalizePoll } from 'soapbox/normalizers/poll'; import { IStatus } from 'soapbox/types'; -import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers'; +// https://docs.joinmastodon.org/entities/status/ const StatusRecord = ImmutableRecord({ account: null, application: null, @@ -49,91 +56,12 @@ const StatusRecord = ImmutableRecord({ spoilerHtml: '', }); -// 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: '', - - // Internal fields - account: null, - status: null, -}); - -// https://docs.joinmastodon.org/entities/mention/ -const MentionRecord = ImmutableRecord({ - id: '', - acct: '', - username: '', - url: '', -}); - -// https://docs.joinmastodon.org/entities/poll/ -const PollRecord = ImmutableRecord({ - emojis: ImmutableList(), - expired: false, - expires_at: new Date(), - id: '', - multiple: false, - options: ImmutableList(), - voters_count: 0, - votes_count: 0, - own_votes: null, - 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) => { - const url = [ - attachment.get('url'), - attachment.get('preview_url'), - attachment.get('remote_url'), - ].find(url => url) || ''; - - const base = ImmutableMap({ - url, - preview_url: url, - }); - - return AttachmentRecord(attachment.mergeWith(mergeDefined, base)); -}; - const normalizeAttachments = (status: ImmutableMap) => { return status.update('media_attachments', ImmutableList(), attachments => { return attachments.map(normalizeAttachment); }); }; -// Normalize mentions -const normalizeMention = (mention: ImmutableMap) => { - return MentionRecord(normalizeAccount(mention)); -}; - const normalizeMentions = (status: ImmutableMap) => { return status.update('mentions', ImmutableList(), mentions => { return mentions.map(normalizeMention); @@ -143,54 +71,10 @@ const normalizeMentions = (status: ImmutableMap) => { // Normalize emojis const normalizeEmojis = (entity: ImmutableMap) => { return entity.update('emojis', ImmutableList(), emojis => { - return emojis.map(EmojiRecord); + return emojis.map(normalizeEmoji); }); }; -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(option => normalizePollOption(option, emojis)); - }); -}; - -// Normalize own_votes to `null` if empty (like Mastodon) -const normalizePollOwnVotes = (poll: ImmutableMap) => { - return poll.update('own_votes', ownVotes => { - return ownVotes?.size > 0 ? ownVotes : null; - }); -}; - -// Whether the user voted in the poll -const normalizePollVoted = (poll: ImmutableMap) => { - return poll.update('voted', voted => { - return typeof voted === 'boolean' ? voted : poll.get('own_votes')?.size > 0; - }); -}; - -// Normalize the actual poll -const normalizePoll = (poll: ImmutableMap) => { - return PollRecord( - poll.withMutations((poll: ImmutableMap) => { - normalizeEmojis(poll); - normalizePollOptions(poll); - normalizePollOwnVotes(poll); - normalizePollVoted(poll); - }), - ); -}; - // Normalize the poll in the status, if applicable const normalizeStatusPoll = (status: ImmutableMap) => { if (status.hasIn(['poll', 'options'])) { @@ -200,6 +84,15 @@ const normalizeStatusPoll = (status: ImmutableMap) => { } }; +// Normalize card +const normalizeStatusCard = (status: ImmutableMap) => { + if (status.get('card')) { + return status.update('card', ImmutableMap(), normalizeCard); + } else { + return status.set('card', null); + } +}; + // Fix order of mentions const fixMentionsOrder = (status: ImmutableMap) => { const mentions = status.get('mentions', ImmutableList()); @@ -249,6 +142,7 @@ export const normalizeStatus = (status: ImmutableMap): IStatus => { normalizeMentions(status); normalizeEmojis(status); normalizeStatusPoll(status); + normalizeStatusCard(status); fixMentionsOrder(status); addSelfMention(status); fixQuote(status); diff --git a/app/soapbox/reducers/__tests__/accounts-test.js b/app/soapbox/reducers/__tests__/accounts-test.js index 973ac4f1e..4b7915d32 100644 --- a/app/soapbox/reducers/__tests__/accounts-test.js +++ b/app/soapbox/reducers/__tests__/accounts-test.js @@ -1,11 +1,8 @@ -import { Map as ImmutableMap, Record } from 'immutable'; +import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable'; import { ACCOUNT_IMPORT } from 'soapbox/actions/importer'; import reducer from '../accounts'; -// import * as actions from 'soapbox/actions/importer'; -// import { take } from 'lodash'; -// import accounts from 'soapbox/__fixtures__/accounts.json'; describe('accounts reducer', () => { it('should return the initial state', () => { @@ -18,44 +15,15 @@ describe('accounts reducer', () => { const action = { type: ACCOUNT_IMPORT, account }; const result = reducer(undefined, action).get('9v5bmRalQvjOy0ECcC'); - expect(Record.isRecord(result)).toBe(true); + expect(ImmutableRecord.isRecord(result)).toBe(true); + }); + + it('minifies a moved account', () => { + const account = require('soapbox/__fixtures__/account-moved.json'); + const action = { type: ACCOUNT_IMPORT, account }; + const result = reducer(undefined, action).get('106801667066418367'); + + expect(result.moved).toBe('107945464165013501'); }); }); - - // fails to add normalized accounts to state - // it('should handle ACCOUNT_IMPORT', () => { - // const state = ImmutableMap({ }); - // const account = take(accounts, 1); - // const action = { - // type: actions.ACCOUNT_IMPORT, - // account: account, - // }; - // debugger; - // expect(reducer(state, action).toJS()).toMatchObject({ - // }); - // }); - - // fails to add normalized accounts to state - // it('should handle ACCOUNTS_IMPORT', () => { - // const state = ImmutableMap({ }); - // const accounts = take(accounts, 2); - // const action = { - // type: actions.ACCOUNTS_IMPORT, - // accounts: accounts, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // }); - // }); - // - // it('should handle ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP', () => { - // const state = ImmutableMap({ username: 'curtis' }); - // const action = { - // type: actions.ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP, - // username: 'curtis', - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // username: 'curtis', - // }); - // }); - }); diff --git a/app/soapbox/reducers/accounts.js b/app/soapbox/reducers/accounts.js index 3e573244d..c601ed928 100644 --- a/app/soapbox/reducers/accounts.js +++ b/app/soapbox/reducers/accounts.js @@ -28,7 +28,6 @@ import { ADMIN_USERS_UNSUGGEST_FAIL, } from 'soapbox/actions/admin'; import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats'; -import { normalizeAccount as normalizeAccount2 } from 'soapbox/actions/importer/normalizer'; import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming'; import { normalizeAccount } from 'soapbox/normalizers/account'; @@ -40,8 +39,14 @@ import { const initialState = ImmutableMap(); +const minifyAccount = account => { + return account.mergeWith((o, n) => n || o, { + moved: account.getIn(['moved', 'id']), + }); +}; + const fixAccount = (state, account) => { - const normalized = normalizeAccount(fromJS(account)); + const normalized = minifyAccount(normalizeAccount(fromJS(account))); return state.set(account.id, normalized); }; @@ -53,9 +58,7 @@ const normalizeAccounts = (state, accounts) => { return state; }; -const importAccountFromChat = (state, chat) => - // TODO: Fix this monstrosity - fixAccount(state, normalizeAccount2(chat.account)); +const importAccountFromChat = (state, chat) => fixAccount(state, chat.account); const importAccountsFromChats = (state, chats) => state.withMutations(mutable =>