From 8503ad5550cef4ef7dcb8dbcc22332c6590c6620 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Wed, 24 Oct 2018 12:44:36 -0700 Subject: [PATCH 01/14] Perf improvements for daily and weekly digests --- .../20181024173616-indexes-for-digests.js | 31 ++++++++++++ chronos/models/community.js | 38 ++------------- chronos/models/usersSettings.js | 48 ++++--------------- chronos/queues/digests/index.js | 7 ++- 4 files changed, 51 insertions(+), 73 deletions(-) create mode 100644 api/migrations/20181024173616-indexes-for-digests.js diff --git a/api/migrations/20181024173616-indexes-for-digests.js b/api/migrations/20181024173616-indexes-for-digests.js new file mode 100644 index 0000000000..bfe6ea3367 --- /dev/null +++ b/api/migrations/20181024173616-indexes-for-digests.js @@ -0,0 +1,31 @@ +exports.up = function(r, conn) { + return Promise.all([ + r + .table('usersSettings') + .indexCreate( + 'weeklyDigestEmail', + r.row('notifications')('types')('weeklyDigest')('email') + ) + .run(conn), + r + .table('usersSettings') + .indexCreate( + 'dailyDigestEmail', + r.row('notifications')('types')('dailyDigest')('email') + ) + .run(conn), + ]); +}; + +exports.down = function(r, conn) { + return Promise.all([ + r + .table('usersSettings') + .indexDrop('weeklyDigestEmail') + .run(conn), + r + .table('usersSettings') + .indexDrop('dailyDigestEmail') + .run(conn), + ]); +}; diff --git a/chronos/models/community.js b/chronos/models/community.js index 5356f5632a..ec679fa9f0 100644 --- a/chronos/models/community.js +++ b/chronos/models/community.js @@ -21,40 +21,10 @@ export const getCommunities = ( export const getTopCommunities = (amount: number): Array => { return db .table('communities') - .pluck('id') - .run() - .then(communities => communities.map(community => community.id)) - .then(communityIds => { - return Promise.all( - communityIds.map(community => { - return db - .table('usersCommunities') - .getAll([community, true], { index: 'communityIdAndIsMember' }) - .count() - .run() - .then(count => { - return { - id: community, - count, - }; - }); - }) - ); - }) - .then(data => { - let sortedCommunities = data - .sort((x, y) => { - return y.count - x.count; - }) - .map(community => community.id) - .slice(0, amount); - - return db - .table('communities') - .getAll(...sortedCommunities) - .filter(community => db.not(community.hasFields('deletedAt'))) - .run(); - }); + .orderBy('memberCount') + .filter(community => community.hasFields('deletedAt').not()) + .limit(amount) + .run(); }; export const getCommunitiesWithMinimumMembers = ( diff --git a/chronos/models/usersSettings.js b/chronos/models/usersSettings.js index b6d99e3b30..734cc94eea 100644 --- a/chronos/models/usersSettings.js +++ b/chronos/models/usersSettings.js @@ -1,42 +1,14 @@ // @flow const { db } = require('./db'); -export const getUsersForDigest = ( - timeframe: string -): Promise> => { - let range; - switch (timeframe) { - case 'daily': { - range = 'dailyDigest'; - break; - } - case 'weekly': { - range = 'weeklyDigest'; - break; - } - } - - return ( - db - .table('usersSettings') - .filter(row => row('notifications')('types')(range)('email').eq(true)) - .eqJoin('userId', db.table('users')) - .zip() - .pluck(['userId', 'email', 'firstName', 'name', 'username', 'lastSeen']) - // save some processing time by making sure the user has an email address - .filter(row => row('email').ne(null)) - // save some processing time by making sure the user has a username - .filter(row => row.hasFields('username').and(row('username').ne(null))) - // save some processing time by making sure the user was active in the last month - .filter(row => - row - .hasFields('lastSeen') - .and( - row('lastSeen').during(db.now().sub(60 * 60 * 24 * 30), db.now()) - ) - ) - .pluck(['userId', 'email', 'firstName', 'name', 'username']) - .distinct() - .run() - ); +// prettier-ignore +export const getUsersForDigest = (timeframe: string): Promise> => { + let range = timeframe === 'daily' ? 'dailyDigest' : 'weeklyDigest'; + return db + .table('usersSettings') + .getAll(true, { index: `${range}Email` }) + .eqJoin('userId', db.table('users')) + .zip() + .pluck(['userId', 'email', 'firstName', 'name', 'username']) + .run() }; diff --git a/chronos/queues/digests/index.js b/chronos/queues/digests/index.js index 42872d8864..b1e53140a7 100644 --- a/chronos/queues/digests/index.js +++ b/chronos/queues/digests/index.js @@ -37,6 +37,7 @@ export default async (job: DigestJob) => { debug('\n ❌ No threads found for this digest'); return; } + debug('\n ⚙️ Fetched threads for digest'); const threadsWithData = await attachDataToThreads(threads); @@ -44,6 +45,7 @@ export default async (job: DigestJob) => { debug('\n ❌ No threads with data eligible for this digest'); return; } + debug('\n ⚙️ Processed threads with data'); // 2 @@ -60,6 +62,8 @@ export default async (job: DigestJob) => { // 4 const usersPromises = users.map(user => { + if (!user.email || !user.username) return null; + try { return addQueue( PROCESS_INDIVIDUAL_DIGEST, @@ -72,12 +76,13 @@ export default async (job: DigestJob) => { } catch (err) { debug(err); Raven.captureException(err); + return null; } }); debug('\n ⚙️ Created individual jobs for each users digest'); try { - return Promise.all(usersPromises); + return Promise.all(usersPromises.filter(Boolean)); } catch (err) { debug('❌ Error in job:\n'); debug(err); From aa8101abf834c3f0a170f97b276f054920accbb2 Mon Sep 17 00:00:00 2001 From: uberbryn Date: Wed, 24 Oct 2018 14:16:06 -0700 Subject: [PATCH 02/14] addresses a regression in firefox 62 that breaks scrolling on pages with overflow set to auto on their main element --- src/views/pages/style.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/views/pages/style.js b/src/views/pages/style.js index 7017a166f0..f90dbb8186 100644 --- a/src/views/pages/style.js +++ b/src/views/pages/style.js @@ -21,8 +21,6 @@ export const Page = styled.main` grid-template-rows: 1fr; grid-template-columns: 1fr; grid-template-areas: 'content'; - overflow: auto; - overflow-x: hidden; background-color: ${theme.bg.default}; `; From bb987b5a670b08eeef4c7454bd9fe997d18c15ba Mon Sep 17 00:00:00 2001 From: "depfu[bot]" Date: Thu, 25 Oct 2018 00:57:15 +0000 Subject: [PATCH 03/14] Update react-test-renderer to version 16.6.0 --- mobile/package.json | 2 +- mobile/yarn.lock | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/mobile/package.json b/mobile/package.json index ec117aa6a7..c9dcb47b8c 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -40,7 +40,7 @@ "exp": "^52.0.0", "jest": "^22.4.4", "jest-expo": "^28.0.0", - "react-test-renderer": "^16.5.2" + "react-test-renderer": "^16.6.0" }, "scripts": { "dev": "exp start .", diff --git a/mobile/yarn.lock b/mobile/yarn.lock index d24c1de3be..5cd45fc4d4 100644 --- a/mobile/yarn.lock +++ b/mobile/yarn.lock @@ -6332,7 +6332,7 @@ react-is@^16.3.1: version "16.4.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.0.tgz#cc9fdc855ac34d2e7d9d2eb7059bbc240d35ffcf" -react-is@^16.3.2, react-is@^16.5.2: +react-is@^16.3.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3" @@ -6340,6 +6340,10 @@ react-is@^16.4.1: version "16.4.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e" +react-is@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.0.tgz#456645144581a6e99f6816ae2bd24ee94bdd0c01" + react-lifecycles-compat@^3, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -6601,14 +6605,14 @@ react-test-renderer@^16.3.1: prop-types "^15.6.0" react-is "^16.4.1" -react-test-renderer@^16.5.2: - version "16.5.2" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.5.2.tgz#92e9d2c6f763b9821b2e0b22f994ee675068b5ae" +react-test-renderer@^16.6.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.6.0.tgz#fe490096bed55c3f4e92c023da3b89f9d03fceb3" dependencies: object-assign "^4.1.1" prop-types "^15.6.2" - react-is "^16.5.2" - schedule "^0.5.0" + react-is "^16.6.0" + scheduler "^0.10.0" react-timer-mixin@^0.13.2: version "0.13.3" @@ -7051,10 +7055,11 @@ sax@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240" -schedule@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/schedule/-/schedule-0.5.0.tgz#c128fffa0b402488b08b55ae74bb9df55cc29cc8" +scheduler@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.10.0.tgz#7988de90fe7edccc774ea175a783e69c40c521e1" dependencies: + loose-envify "^1.1.0" object-assign "^4.1.1" "semver@2 || 3 || 4 || 5", semver@5.x, semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: From 54e0374a9521cd474d323f3a4970243e1c800ae9 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" Date: Thu, 25 Oct 2018 03:05:55 +0000 Subject: [PATCH 04/14] Update react-navigation to version 2.18.1 --- mobile/package.json | 2 +- mobile/yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/package.json b/mobile/package.json index ec117aa6a7..dd0d8743b9 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -22,7 +22,7 @@ "react-native-picker-select": "^3.1.3", "react-native-tab-view": "^0.0.78", "react-native-typography": "^1.4.0", - "react-navigation": "^2.18.0", + "react-navigation": "^2.18.1", "react-navigation-props-mapper": "^0.1.3", "recompose": "^0.26.0", "redraft": "^0.10.2", diff --git a/mobile/yarn.lock b/mobile/yarn.lock index d24c1de3be..e84c15607a 100644 --- a/mobile/yarn.lock +++ b/mobile/yarn.lock @@ -6557,9 +6557,9 @@ react-navigation-tabs@0.8.4: react-lifecycles-compat "^3.0.4" react-native-tab-view "^1.0.0" -react-navigation@^2.18.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-2.18.0.tgz#c14e632da308cb60c3f60bbfba4b0c037ea502c3" +react-navigation@^2.18.1: + version "2.18.1" + resolved "https://registry.yarnpkg.com/react-navigation/-/react-navigation-2.18.1.tgz#0aadd405ba1f04135701e75aadd29814678c3ba4" dependencies: clamp "^1.0.1" create-react-context "0.2.2" From 549316f1590e71fc588f6c679ce811bc0d26e6d3 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 09:00:41 -0700 Subject: [PATCH 05/14] More perf improvements and faster queries --- chronos/models/community.js | 14 ++++---------- chronos/models/coreMetrics.js | 1 - chronos/queues/coreMetrics/index.js | 10 ---------- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/chronos/models/community.js b/chronos/models/community.js index ec679fa9f0..28b639e35d 100644 --- a/chronos/models/community.js +++ b/chronos/models/community.js @@ -32,15 +32,9 @@ export const getCommunitiesWithMinimumMembers = ( communityIds: Array ) => { return db - .table('usersCommunities') - .getAll(...communityIds, { index: 'communityId' }) - .group('communityId') - .ungroup() - .filter(row => - row('reduction') - .count() - .gt(min) - ) - .map(row => row('group')) + .table('communities') + .filter(row => row('memberCount').ge(min)) + .filter(community => community.hasFields('deletedAt').not()) + .map(row => row('id')) .run(); }; diff --git a/chronos/models/coreMetrics.js b/chronos/models/coreMetrics.js index a3af12b31e..2aa5c9b343 100644 --- a/chronos/models/coreMetrics.js +++ b/chronos/models/coreMetrics.js @@ -77,7 +77,6 @@ export const getCount = (table: string, filter: mixed) => { if (filter) { return db .table(table) - .filter(filter) .filter(row => db.not(row.hasFields('deletedAt'))) .count() .run(); diff --git a/chronos/queues/coreMetrics/index.js b/chronos/queues/coreMetrics/index.js index c0073cb905..8d625d24c3 100644 --- a/chronos/queues/coreMetrics/index.js +++ b/chronos/queues/coreMetrics/index.js @@ -72,14 +72,6 @@ export default async () => { // 13 const dmThreads = await getCount('directMessageThreads'); - // 14 - const threadMessages = await getCount('messages', { threadType: 'story' }); - - // 15 - const dmMessages = await getCount('messages', { - threadType: 'directMessageThread', - }); - const coreMetrics = { dau, wau, @@ -97,8 +89,6 @@ export default async () => { communities, threads, dmThreads, - threadMessages, - dmMessages, }; try { From c1f58b375a1f3c9fe3e1754f03d9e7b9fdddae9b Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:05:32 -0700 Subject: [PATCH 06/14] Static analysis fixes from deepscan --- .../threadComposer/components/composer.js | 2 +- .../components/createCommunityForm/index.js | 2 +- src/views/newCommunity/index.js | 10 +++--- .../components/newThreadNotification.js | 11 ++++--- .../sortAndGroupNotificationMessages.js | 2 +- src/views/notifications/index.js | 2 +- src/views/notifications/utils.js | 14 ++++---- src/views/thread/components/messages.js | 5 +-- src/views/thread/components/threadDetail.js | 2 -- src/views/thread/container.js | 5 ++- src/views/user/index.js | 32 +++++++++---------- src/views/userSettings/components/editForm.js | 2 -- src/views/userSettings/index.js | 4 +-- 13 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/components/threadComposer/components/composer.js b/src/components/threadComposer/components/composer.js index b02784ef55..b00f6c1099 100644 --- a/src/components/threadComposer/components/composer.js +++ b/src/components/threadComposer/components/composer.js @@ -450,7 +450,7 @@ class ThreadComposerWithData extends React.Component { ); const newActiveChannel = activeCommunityChannels.find(channel => { - if (channel) return null; + if (!channel) return null; // If there is an active channel and we're switching back to the currently open community // select that channel if ( diff --git a/src/views/newCommunity/components/createCommunityForm/index.js b/src/views/newCommunity/components/createCommunityForm/index.js index 9668d33f05..09ee156066 100644 --- a/src/views/newCommunity/components/createCommunityForm/index.js +++ b/src/views/newCommunity/components/createCommunityForm/index.js @@ -490,7 +490,7 @@ class CreateCommunityForm extends React.Component { diff --git a/src/views/newCommunity/index.js b/src/views/newCommunity/index.js index 3e5db022f7..d17c05b663 100644 --- a/src/views/newCommunity/index.js +++ b/src/views/newCommunity/index.js @@ -168,7 +168,10 @@ class NewCommunity extends React.Component { }; render() { - const { isLoading, data: { user } } = this.props; + const { + isLoading, + data: { user }, + } = this.props; const { activeStep, community, existingId, hasInvitedPeople } = this.state; const title = this.title(); const description = this.description(); @@ -211,10 +214,7 @@ class NewCommunity extends React.Component { community.id && ( - + diff --git a/src/views/notifications/components/newThreadNotification.js b/src/views/notifications/components/newThreadNotification.js index c69c701770..5d1a6c6373 100644 --- a/src/views/notifications/components/newThreadNotification.js +++ b/src/views/notifications/components/newThreadNotification.js @@ -56,9 +56,10 @@ const ThreadCreatedComponent = ({ return ; }; -const ThreadCreated = compose(getThreadById, viewNetworkHandler)( - ThreadCreatedComponent -); +const ThreadCreated = compose( + getThreadById, + viewNetworkHandler +)(ThreadCreatedComponent); /* NOTE: @brianlovin @@ -95,7 +96,7 @@ export class NewThreadNotification extends React.Component { ); const newThreadCount = - threads.length > 1 ? 'New threads were' : 'A new thread was'; + threads && threads.length > 1 ? 'New threads were' : 'A new thread was'; if (threads && threads.length > 0) { return ( @@ -172,7 +173,7 @@ class MiniNewThreadNotificationWithMutation extends React.Component< ); const newThreadCount = - threads.length > 1 ? 'New threads were' : 'A new thread was'; + threads && threads.length > 1 ? 'New threads were' : 'A new thread was'; if (threads && threads.length > 0) { return ( diff --git a/src/views/notifications/components/sortAndGroupNotificationMessages.js b/src/views/notifications/components/sortAndGroupNotificationMessages.js index a96749b8b1..19dc5614bf 100644 --- a/src/views/notifications/components/sortAndGroupNotificationMessages.js +++ b/src/views/notifications/components/sortAndGroupNotificationMessages.js @@ -1,7 +1,7 @@ import { sortByDate } from '../../../helpers/utils'; export const sortAndGroupNotificationMessages = messagesToSort => { - if (!messagesToSort.length > 0) return []; + if (!(messagesToSort.length > 0)) return []; let messages = messagesToSort; messages = sortByDate(messages, 'timestamp', 'asc'); let masterArray = []; diff --git a/src/views/notifications/index.js b/src/views/notifications/index.js index 5755cd67b8..efe4888168 100644 --- a/src/views/notifications/index.js +++ b/src/views/notifications/index.js @@ -391,7 +391,7 @@ class NotificationsPure extends React.Component { ); } - if (!data || data.error) { + if (!data || (data && data.error)) { return ( diff --git a/src/views/notifications/utils.js b/src/views/notifications/utils.js index 423d543fe1..85718e03b2 100644 --- a/src/views/notifications/utils.js +++ b/src/views/notifications/utils.js @@ -38,30 +38,28 @@ const actorsToString = actors => { const data = actors && actors.length > 0 && actors.map(actor => actor.payload).reverse(); - if (actors.length === 1) { + if (actors && actors.length === 1) { return ( {`${names[0]}`} ); - } else if (actors.length === 2) { + } else if (actors && actors.length === 2) { return ( {`${names[0]}`} and{' '} {`${names[1]}`} ); - } else if (actors.length === 3) { + } else if (actors && actors.length === 3) { return ( - {`${names[0]}`} - , - {`${names[1]}`} - and{' '} + {`${names[0]}`},{' '} + {`${names[1]}`} and{' '} {`${names[2]}`} ); - } else if (actors.length >= 4) { + } else if (actors && actors.length >= 4) { return ( {`${names[0]}`} and{' '} diff --git a/src/views/thread/components/messages.js b/src/views/thread/components/messages.js index 35a3689067..7d24b5bda7 100644 --- a/src/views/thread/components/messages.js +++ b/src/views/thread/components/messages.js @@ -247,7 +247,8 @@ class MessagesWithData extends React.Component { if (messagesExist) { const { edges, pageInfo } = data.thread.messageConnection; - const unsortedMessages = edges.map(message => message && message.node); + const unsortedMessages = + edges && edges.map(message => message && message.node); const uniqueMessages = deduplicateChildren(unsortedMessages, 'id'); const sortedMessages = sortAndGroupMessages(uniqueMessages); @@ -323,7 +324,7 @@ class MessagesWithData extends React.Component { ); } - if (!messagesExist) { + if (!isLoading && !messagesExist) { if (isLocked || !this.props.data.thread) return null; return this.getIsAuthor() diff --git a/src/views/thread/components/threadDetail.js b/src/views/thread/components/threadDetail.js index c5c8a9739b..c1152dc278 100644 --- a/src/views/thread/components/threadDetail.js +++ b/src/views/thread/components/threadDetail.js @@ -192,8 +192,6 @@ class ThreadDetailPure extends React.Component { message = `You are about to delete another person's thread. As the owner of the ${ thread.channel.name } channel, you have permission to do this. The thread author will be notified that this thread was deleted.`; - } else if (thread.isAuthor) { - message = 'Are you sure you want to delete this thread?'; } else { message = 'Are you sure you want to delete this thread?'; } diff --git a/src/views/thread/container.js b/src/views/thread/container.js index 29bbc5b400..be36a53acb 100644 --- a/src/views/thread/container.js +++ b/src/views/thread/container.js @@ -160,9 +160,12 @@ class ThreadContainer extends React.Component { } handleScroll = e => { - e.persist(); if (!e || !e.target) return; + if (e && e.persist) { + e.persist(); + } + // whenever the user scrolls in the thread we determine if they've scrolled // past the thread content section - once they've scroll passed it, we // enable the `bannerIsVisible` state to slide the thread context banner diff --git a/src/views/user/index.js b/src/views/user/index.js index 713dbf9658..eeeba28f48 100644 --- a/src/views/user/index.js +++ b/src/views/user/index.js @@ -312,23 +312,6 @@ class UserView extends React.Component { return ; } - if (hasError) { - return ( - - - - - ); - } - if (!user) { return ( @@ -351,6 +334,21 @@ class UserView extends React.Component { ); } + + return ( + + + + + ); } } diff --git a/src/views/userSettings/components/editForm.js b/src/views/userSettings/components/editForm.js index b72cf04aad..5285c1b686 100644 --- a/src/views/userSettings/components/editForm.js +++ b/src/views/userSettings/components/editForm.js @@ -133,8 +133,6 @@ class UserWithData extends React.Component { isLoading: true, }); - if (!file) return; - if (file && file.size > PRO_USER_MAX_IMAGE_SIZE_BYTES) { return this.setState({ photoSizeError: `Try uploading a file less than ${PRO_USER_MAX_IMAGE_SIZE_STRING}.`, diff --git a/src/views/userSettings/index.js b/src/views/userSettings/index.js index e4ca13b5ce..fb221dedac 100644 --- a/src/views/userSettings/index.js +++ b/src/views/userSettings/index.js @@ -53,7 +53,7 @@ class UserSettings extends React.Component { } // the user is logged in but somehow a user wasnt fetched from the server prompt a refresh to reauth the user - if ((currentUser && !user) || (currentUser && !user && !user.id)) { + if ((currentUser && !user) || (currentUser && user && !user.id)) { return ( { } // if the user isn't logged in, or for some reason the user settings that were returned don't match the user id in the store, we show a warning error state - if (!currentUser || user.id !== currentUser.id) { + if (!currentUser || (user && user.id !== currentUser.id)) { return ( > From aea99649364b77b76545e04f395a31aad3184ce2 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:06:18 -0700 Subject: [PATCH 07/14] Eslint --- src/views/newCommunity/index.js | 2 +- src/views/user/index.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/views/newCommunity/index.js b/src/views/newCommunity/index.js index d17c05b663..b8fe136e38 100644 --- a/src/views/newCommunity/index.js +++ b/src/views/newCommunity/index.js @@ -172,7 +172,7 @@ class NewCommunity extends React.Component { isLoading, data: { user }, } = this.props; - const { activeStep, community, existingId, hasInvitedPeople } = this.state; + const { activeStep, community, hasInvitedPeople } = this.state; const title = this.title(); const description = this.description(); if (user && user.email) { diff --git a/src/views/user/index.js b/src/views/user/index.js index eeeba28f48..dd5581cea8 100644 --- a/src/views/user/index.js +++ b/src/views/user/index.js @@ -59,7 +59,6 @@ type Props = { user: GetUserType, }, isLoading: boolean, - hasError: boolean, queryVarIsChanging: boolean, dispatch: Dispatch, history: History, @@ -125,7 +124,6 @@ class UserView extends React.Component { const { data: { user }, isLoading, - hasError, queryVarIsChanging, match: { params: { username }, From 4d01369da0c842c6c8e082f8936a6869671ad75e Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:12:56 -0700 Subject: [PATCH 08/14] Hide notifs button on action bar signed out --- src/views/thread/components/actionBar.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/views/thread/components/actionBar.js b/src/views/thread/components/actionBar.js index 91fcb56173..9e4f891ed4 100644 --- a/src/views/thread/components/actionBar.js +++ b/src/views/thread/components/actionBar.js @@ -387,7 +387,7 @@ class ActionBar extends React.Component {
- {currentUser ? ( + {currentUser && ( { > {thread.receiveNotifications ? 'Subscribed' : 'Notify me'} - ) : ( - - this.props.dispatch(openModal('CHAT_INPUT_LOGIN_MODAL', {})) - } - > - Notify me - )} {shouldRenderActionsDropdown && ( From fac8eedd11f458a03a6421aeae165bb59857d896 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:14:23 -0700 Subject: [PATCH 09/14] Add fix to watercooler, remove test condition --- cypress/integration/thread/action_bar_spec.js | 42 ++++++++----------- .../thread/components/watercoolerActionBar.js | 15 +------ 2 files changed, 18 insertions(+), 39 deletions(-) diff --git a/cypress/integration/thread/action_bar_spec.js b/cypress/integration/thread/action_bar_spec.js index 1bb1c2deb1..508e43b951 100644 --- a/cypress/integration/thread/action_bar_spec.js +++ b/cypress/integration/thread/action_bar_spec.js @@ -50,8 +50,7 @@ const pinThread = () => { const triggerThreadDelete = () => { cy.get('[data-cy="thread-dropdown-delete"]').click(); cy.get('[data-cy="delete-button"]').should('be.visible'); - cy - .get('div.ReactModal__Overlay') + cy.get('div.ReactModal__Overlay') .should('be.visible') .click('topLeft'); }; @@ -59,15 +58,13 @@ const triggerThreadDelete = () => { const triggerMovingThread = () => { cy.get('[data-cy="thread-dropdown-move"]').click(); cy.get('[data-cy="move-thread-modal"]').should('be.visible'); - cy - .get('div.ReactModal__Overlay') + cy.get('div.ReactModal__Overlay') .should('be.visible') .click('topLeft'); }; const openSettingsDropdown = () => { - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') + cy.get('[data-cy="thread-actions-dropdown-trigger"]') .should('be.visible') .click(); }; @@ -80,15 +77,12 @@ describe('action bar renders', () => { it('should render', () => { cy.get('[data-cy="thread-view"]').should('be.visible'); - cy - .get('[data-cy="thread-notifications-login-capture"]') - .should('be.visible'); cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); @@ -104,9 +98,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); @@ -122,9 +116,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); @@ -140,9 +134,9 @@ describe('action bar renders', () => { cy.get('[data-cy="thread-facebook-button"]').should('not.be.visible'); cy.get('[data-cy="thread-tweet-button"]').should('not.be.visible'); cy.get('[data-cy="thread-copy-link-button"]').should('be.visible'); - cy - .get('[data-cy="thread-actions-dropdown-trigger"]') - .should('not.be.visible'); + cy.get('[data-cy="thread-actions-dropdown-trigger"]').should( + 'not.be.visible' + ); }); }); @@ -192,8 +186,7 @@ describe('action bar renders', () => { cy.get('[data-cy="save-thread-edit-button"]').should('be.visible'); const title = 'Some new thread'; cy.get('[data-cy="rich-text-editor"]').should('be.visible'); - cy - .get('[data-cy="thread-editor-title-input"]') + cy.get('[data-cy="thread-editor-title-input"]') .clear() .type(title); cy.get('[data-cy="save-thread-edit-button"]').click(); @@ -205,8 +198,7 @@ describe('action bar renders', () => { cy.get('[data-cy="save-thread-edit-button"]').should('be.visible'); const originalTitle = 'The first thread! 🎉'; cy.get('[data-cy="rich-text-editor"]').should('be.visible'); - cy - .get('[data-cy="thread-editor-title-input"]') + cy.get('[data-cy="thread-editor-title-input"]') .clear() .type(originalTitle); cy.get('[data-cy="save-thread-edit-button"]').click(); diff --git a/src/views/thread/components/watercoolerActionBar.js b/src/views/thread/components/watercoolerActionBar.js index d5b5cb63c2..a6be7e0312 100644 --- a/src/views/thread/components/watercoolerActionBar.js +++ b/src/views/thread/components/watercoolerActionBar.js @@ -137,7 +137,7 @@ class WatercoolerActionBar extends React.Component { )}
- {currentUser ? ( + {currentUser && ( { > {thread.receiveNotifications ? 'Subscribed' : 'Get notifications'} - ) : ( - - this.props.dispatch(openModal('CHAT_INPUT_LOGIN_MODAL', {})) - } - > - Notify me - )} ); From fa49cbafdedb3a7f81cfa1e001752c7597c4e26c Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:22:30 -0700 Subject: [PATCH 10/14] Allow blog image uploads for thread composer --- shared/middlewares/security.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/middlewares/security.js b/shared/middlewares/security.js index a6f7be8b24..79c0d36c5d 100644 --- a/shared/middlewares/security.js +++ b/shared/middlewares/security.js @@ -78,7 +78,7 @@ function securityMiddleware( // Defines the origins from which images can be loaded. // @note: Leave open to all images, too much image coming from different servers. - imgSrc: ['https:', 'http:', "'self'", 'data:'], + imgSrc: ['https:', 'http:', "'self'", 'data:', 'blob:'], // Defines valid sources of stylesheets. styleSrc: ["'self'", "'unsafe-inline'"], From 111c29e9364288bdd643144443bf71d069391daa Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:38:25 -0700 Subject: [PATCH 11/14] Remove link previews from threads --- src/components/composer/index.js | 108 +--------- src/components/linkPreview/index.js | 76 ------- src/components/linkPreview/style.js | 189 ------------------ src/components/rich-text-editor/container.js | 39 ---- .../threadComposer/components/composer.js | 106 +--------- src/components/threadFeedCard/index.js | 24 +-- src/helpers/utils.js | 11 +- src/views/thread/components/threadDetail.js | 122 +---------- 8 files changed, 9 insertions(+), 666 deletions(-) delete mode 100644 src/components/linkPreview/index.js delete mode 100644 src/components/linkPreview/style.js diff --git a/src/components/composer/index.js b/src/components/composer/index.js index eff7296f3c..fc1cd6ded9 100644 --- a/src/components/composer/index.js +++ b/src/components/composer/index.js @@ -4,11 +4,9 @@ import compose from 'recompose/compose'; import Textarea from 'react-textarea-autosize'; import { withRouter } from 'react-router'; import { connect } from 'react-redux'; -import isURL from 'validator/lib/isURL'; import debounce from 'debounce'; import queryString from 'query-string'; import { KeyBindingUtil } from 'draft-js'; -import { URLS } from '../../helpers/regexps'; import { closeComposer } from '../../actions/composer'; import { changeActiveThread } from '../../actions/dashboardFeed'; import { addToastWithTimeout } from '../../actions/toasts'; @@ -23,7 +21,6 @@ import { import getComposerCommunitiesAndChannels from 'shared/graphql/queries/composer/getComposerCommunitiesAndChannels'; import type { GetComposerType } from 'shared/graphql/queries/composer/getComposerCommunitiesAndChannels'; import publishThread from 'shared/graphql/mutations/thread/publishThread'; -import { getLinkPreviewFromUrl } from '../../helpers/utils'; import { TextButton, Button } from '../buttons'; import { FlexRow } from '../../components/globals'; import { LoadingSelect } from '../loading'; @@ -46,8 +43,6 @@ import { } from './utils'; import { events, track } from 'src/helpers/analytics'; -const ENDS_IN_WHITESPACE = /(\s|\n)$/; - type State = { title: string, body: Object, @@ -56,10 +51,6 @@ type State = { activeCommunity: ?string, activeChannel: ?string, isPublishing: boolean, - linkPreview: ?Object, - linkPreviewTrueUrl: ?string, - linkPreviewLength: number, - fetchingLinkPreview: boolean, postWasPublished: boolean, }; @@ -109,10 +100,6 @@ class ComposerWithData extends Component { activeCommunity: '', activeChannel: '', isPublishing: false, - linkPreview: null, - linkPreviewTrueUrl: '', - linkPreviewLength: 0, - fetchingLinkPreview: false, postWasPublished: false, }; @@ -293,7 +280,6 @@ class ComposerWithData extends Component { }; changeBody = body => { - this.listenForUrl(body); this.persistBodyToLocalStorageWithDebounce(body); this.setState({ body, @@ -443,14 +429,7 @@ class ComposerWithData extends Component { // define new constants in order to construct the proper shape of the // input for the publishThread mutation - const { - activeChannel, - activeCommunity, - title, - body, - linkPreview, - linkPreviewTrueUrl, - } = this.state; + const { activeChannel, activeCommunity, title, body } = this.state; const channelId = activeChannel; const communityId = activeCommunity; const jsonBody = toJSON(body); @@ -460,18 +439,6 @@ class ComposerWithData extends Component { body: isAndroid() ? toPlainText(body) : JSON.stringify(jsonBody), }; - const attachments = []; - if (linkPreview) { - const attachmentData = JSON.stringify({ - ...linkPreview, - trueUrl: linkPreviewTrueUrl, - }); - attachments.push({ - attachmentType: 'linkPreview', - data: attachmentData, - }); - } - // Get the images const filesToUpload = Object.keys(jsonBody.entityMap) .filter( @@ -491,7 +458,6 @@ class ComposerWithData extends Component { // which is parsed as markdown to draftjs on the server type: isAndroid() ? 'TEXT' : 'DRAFTJS', content, - attachments, filesToUpload, }; @@ -539,68 +505,6 @@ class ComposerWithData extends Component { }); }; - listenForUrl = state => { - const { linkPreview, linkPreviewLength } = this.state; - if (linkPreview !== null) return; - - const lastChangeType = state.getLastChangeType(); - if ( - lastChangeType !== 'backspace-character' && - lastChangeType !== 'insert-characters' - ) { - return; - } - - const text = toPlainText(state); - - if (!ENDS_IN_WHITESPACE.test(text)) return; - - const toCheck = text.match(URLS); - - if (toCheck) { - const len = toCheck.length; - if (linkPreviewLength === len) return; // no new links, don't recheck - - let urlToCheck = toCheck[len - 1].trim(); - - if (!/^https?:\/\//i.test(urlToCheck)) { - urlToCheck = 'https://' + urlToCheck; - } - - if (!isURL(urlToCheck)) return; - - this.setState({ fetchingLinkPreview: true }); - - getLinkPreviewFromUrl(urlToCheck) - .then(data => - this.setState(prevState => ({ - linkPreview: { ...data, trueUrl: urlToCheck }, - linkPreviewTrueUrl: urlToCheck, - linkPreviewLength: prevState.linkPreviewLength + 1, - fetchingLinkPreview: false, - })) - ) - .catch(() => { - this.setState({ - fetchingLinkPreview: false, - }); - this.props.dispatch( - addToastWithTimeout( - 'error', - `Oops, we couldn't fetch a preview for ${urlToCheck}. You can publish your story anyways though! 👍` - ) - ); - }); - } - }; - - removeLinkPreview = () => { - this.setState({ - linkPreview: null, - linkPreviewTrueUrl: null, - }); - }; - render() { const { title, @@ -609,9 +513,6 @@ class ComposerWithData extends Component { activeCommunity, activeChannel, isPublishing, - linkPreview, - linkPreviewTrueUrl, - fetchingLinkPreview, } = this.state; const { @@ -689,13 +590,6 @@ class ComposerWithData extends Component { editorKey="thread-composer" placeholder={'Write more thoughts here...'} className={'threadComposer'} - showLinkPreview={true} - linkPreview={{ - loading: fetchingLinkPreview, - remove: this.removeLinkPreview, - trueUrl: linkPreviewTrueUrl, - data: linkPreview, - }} /> diff --git a/src/components/linkPreview/index.js b/src/components/linkPreview/index.js deleted file mode 100644 index b74135bf65..0000000000 --- a/src/components/linkPreview/index.js +++ /dev/null @@ -1,76 +0,0 @@ -import React, { Component } from 'react'; -import Icon from '../icons'; -import { FlexCol } from '../globals'; -import { truncate } from '../../helpers/utils'; -import { - LinkPreviewContainer, - LinkPreviewImage, - LinkPreviewTextContainer, - MetaTitle, - MetaUrl, - Close, - LinkPreviewSkeleton, - AnimatedBackground, - CoverTop, - CoverMiddle, - CoverMiddleMiddle, - CoverMiddleTopRight, - CoverLeft, - CoverMiddleBottomRight, - CoverBottom, - CoverMiddleMiddleBottomRight, -} from './style'; - -export class LinkPreview extends Component { - remove = e => { - e.preventDefault(); - this.props.remove(); - }; - - render() { - let { data: { image, title, url, trueUrl }, editable, margin } = this.props; - title = title ? truncate(title, 72) : ''; - if (!title) return null; - if (!url && !trueUrl) return null; - return ( - - {editable && ( - - - - )} - - {image && } - - - {title && {title}} - - {(url || trueUrl) && {url || trueUrl}} - - - ); - } -} - -export const LinkPreviewLoading = () => { - return ( - - - - - - - - - - - - ); -}; diff --git a/src/components/linkPreview/style.js b/src/components/linkPreview/style.js deleted file mode 100644 index 34c9fa71db..0000000000 --- a/src/components/linkPreview/style.js +++ /dev/null @@ -1,189 +0,0 @@ -import styled, { keyframes } from 'styled-components'; -import theme from 'shared/theme'; -import { Transition, Shadow, hexa, zIndex, Truncate } from '../globals'; - -export const LinkPreviewContainer = styled.a` - display: flex; - flex-direction: ${props => (props.size === 'large' ? 'row' : 'row')}; - align-items: ${props => (props.size === 'large' ? 'flex-start' : 'center')}; - border-radius: 4px; - background: ${theme.bg.default}; - border: 1px solid ${theme.bg.border}; - ${'' /* box-shadow: ${Shadow.low} ${props => hexa(props.theme.bg.reverse, 0.1)}; */} overflow: hidden; - position: relative; - margin: ${props => (props.margin ? props.margin : '0')}; - padding: 0; - transition: ${Transition.reaction.off}; - flex: auto; - pointer-events: auto; - - &:hover { - transition: ${Transition.reaction.on}; - box-shadow: ${Shadow.high} ${props => hexa(props.theme.bg.reverse, 0.1)}; - border: 1px solid ${theme.bg.border}; - text-decoration: none !important; - } -`; - -export const Close = styled.span` - position: absolute; - right: 8px; - top: 8px; -`; - -export const LinkPreviewImage = styled.div` - overflow: hidden; - min-width: ${props => (props.size === 'large' ? '100%' : '64px')}; - min-height: 64px; - background: ${props => - `${props.theme.bg.wash} url("${props.image}") no-repeat center center`}; - background-size: cover; -`; - -export const LinkPreviewTextContainer = styled.div` - display: flex; - flex: auto; - overflow: hidden; - flex-direction: column; - justify-content: space-between; - padding: 8px 12px; - align-self: stretch; -`; - -export const BaseMeta = styled.span` - display: flex; - flex: none; - line-height: 1.2; - margin: 2px 0; - display: inline-block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - align-self: stretch; -`; - -export const MetaTitle = styled(BaseMeta)` - font-size: 16px; - font-weight: 800; - white-space: normal; - color: ${theme.text.default}; - ${Truncate}; -`; - -export const MetaDescription = styled(BaseMeta)` - font-size: 14px; - font-weight: 500; - line-height: 1.4; - color: ${theme.text.alt}; -`; - -export const MetaUrl = styled(BaseMeta)` - color: ${theme.text.placeholder}; - font-weight: 500; - margin-top: 8px; - font-size: 12px; -`; - -export const LinkPreviewSkeleton = styled.div` - display: flex; - flex-direction: row; - align-items: center; - border-radius: 4px; - background: #fff; - border: 1px solid ${theme.bg.border}; - box-shadow: ${Shadow.low} ${props => hexa(props.theme.bg.reverse, 0.1)}; - overflow: hidden; - position: relative; - height: 140px; - z-index: ${zIndex.loading}; - margin: ${props => (props.margin ? props.margin : '16px')}; -`; - -const placeHolderShimmer = keyframes` - 0%{ - background-position: -600px 0 - } - 100%{ - background-position: 600px 0 - } -`; - -export const AnimatedBackground = styled.div` - width: 100%; - height: 100%; - position: absolute; - z-index: ${zIndex.loading + 2}; - animation-duration: 2.5s; - animation-fill-mode: forwards; - animation-iteration-count: infinite; - animation-timing-function: ease-in-out; - background: linear-gradient( - to right, - ${theme.bg.wash} 10%, - ${({ theme }) => hexa(theme.generic.default, 0.65)} 20%, - ${theme.bg.wash} 30% - ); - animation-name: ${placeHolderShimmer}; - z-index: ${zIndex.loading + 1}; -`; - -const Cover = styled.div` - position: absolute; - background: ${theme.bg.default}; - z-index: ${zIndex.loading + 2}; -`; - -export const CoverLeft = styled(Cover)` - left: 140px; - width: 24px; - height: 100%; -`; - -export const CoverTop = styled(Cover)` - right: 0; - top: 0; - height: 40px; - width: calc(100% - 152px); -`; - -export const CoverMiddle = styled(Cover)` - right: 0; - top: 52px; - height: 16px; - width: calc(100% - 152px); -`; - -export const CoverMiddleMiddle = styled(Cover)` - right: 0; - top: 80px; - height: 20px; - width: calc(100% - 152px); -`; - -export const CoverMiddleTopRight = styled(Cover)` - right: 0; - top: 34px; - height: 20px; - width: calc(100% - 482px); -`; - -export const CoverMiddleBottomRight = styled(Cover)` - right: 0; - top: 64px; - height: 20px; - width: calc(100% - 342px); -`; - -export const CoverMiddleMiddleBottomRight = styled(Cover)` - right: 0; - top: 88px; - height: 20px; - width: calc(100% - 382px); -`; - -export const CoverBottom = styled(Cover)` - right: 0; - bottom: 0; - height: 30px; - width: calc(100% - 152px); -`; diff --git a/src/components/rich-text-editor/container.js b/src/components/rich-text-editor/container.js index 5e81230c04..aa70dc45a7 100644 --- a/src/components/rich-text-editor/container.js +++ b/src/components/rich-text-editor/container.js @@ -28,7 +28,6 @@ import { IconButton } from 'src/components/buttons'; import mentionsDecorator from 'shared/clients/draft-js/mentions-decorator/index.web.js'; import { isAndroid } from 'shared/draft-utils'; import MediaInput from 'src/components/mediaInput'; -import { LinkPreview, LinkPreviewLoading } from 'src/components/linkPreview'; import Image from './Image'; import Embed, { addEmbed, parseEmbedUrl } from './Embed'; import { renderLanguageSelect } from './LanguageSelect'; @@ -46,8 +45,6 @@ import { type Props = { state: Object, onChange: Function, - showLinkPreview?: boolean, - linkPreview?: Object, focus?: boolean, readOnly?: boolean, editorRef?: any => void, @@ -200,8 +197,6 @@ class Editor extends React.Component { className, style, editorRef, - showLinkPreview, - linkPreview, focus, version, placeholder, @@ -277,23 +272,6 @@ class Editor extends React.Component { )} - {showLinkPreview && - linkPreview && - linkPreview.loading && ( - - )} - {showLinkPreview && - linkPreview && - linkPreview.data && ( - - )} ); } else { @@ -325,23 +303,6 @@ class Editor extends React.Component { {...rest} /> - {showLinkPreview && - linkPreview && - linkPreview.loading && ( - - )} - {showLinkPreview && - linkPreview && - linkPreview.data && ( - - )} {!readOnly && ( diff --git a/src/components/threadComposer/components/composer.js b/src/components/threadComposer/components/composer.js index b02784ef55..bf404ba812 100644 --- a/src/components/threadComposer/components/composer.js +++ b/src/components/threadComposer/components/composer.js @@ -18,9 +18,6 @@ import { import getComposerCommunitiesAndChannels from 'shared/graphql/queries/composer/getComposerCommunitiesAndChannels'; import type { GetComposerType } from 'shared/graphql/queries/composer/getComposerCommunitiesAndChannels'; import publishThread from 'shared/graphql/mutations/thread/publishThread'; -import { getLinkPreviewFromUrl } from '../../../helpers/utils'; -import isURL from 'validator/lib/isURL'; -import { URLS, ENDS_IN_WHITESPACE } from '../../../helpers/regexps'; import { TextButton, Button } from '../../buttons'; import { FlexRow } from '../../../components/globals'; import { LoadingComposer } from '../../loading'; @@ -67,10 +64,6 @@ type State = { activeCommunity: ?string, activeChannel: ?string, isPublishing: boolean, - linkPreview: ?Object, - linkPreviewTrueUrl: ?string, - linkPreviewLength: number, - fetchingLinkPreview: boolean, postWasPublished: boolean, }; @@ -133,10 +126,6 @@ class ThreadComposerWithData extends React.Component { activeCommunity: '', activeChannel: '', isPublishing: false, - linkPreview: null, - linkPreviewTrueUrl: '', - linkPreviewLength: 0, - fetchingLinkPreview: false, postWasPublished: false, }; } @@ -281,10 +270,6 @@ class ThreadComposerWithData extends React.Component { activeCommunity, activeChannel, isPublishing: false, - linkPreview: null, - linkPreviewTrueUrl: '', - linkPreviewLength: 0, - fetchingLinkPreview: false, }); }; @@ -362,7 +347,6 @@ class ThreadComposerWithData extends React.Component { }; changeBody = body => { - this.listenForUrl(body); persistBody(body); this.setState({ body, @@ -514,14 +498,7 @@ class ThreadComposerWithData extends React.Component { // define new constants in order to construct the proper shape of the // input for the publishThread mutation - const { - activeChannel, - activeCommunity, - title, - body, - linkPreview, - linkPreviewTrueUrl, - } = this.state; + const { activeChannel, activeCommunity, title, body } = this.state; const channelId = activeChannel; const communityId = activeCommunity; const jsonBody = toJSON(body); @@ -533,18 +510,6 @@ class ThreadComposerWithData extends React.Component { body: isAndroid() ? toPlainText(body) : JSON.stringify(jsonBody), }; - const attachments = []; - if (linkPreview) { - const attachmentData = JSON.stringify({ - ...linkPreview, - trueUrl: linkPreviewTrueUrl, - }); - attachments.push({ - attachmentType: 'linkPreview', - data: attachmentData, - }); - } - // Get the images const filesToUpload = Object.keys(jsonBody.entityMap) .filter( @@ -562,7 +527,6 @@ class ThreadComposerWithData extends React.Component { communityId, type: isAndroid() ? 'TEXT' : 'DRAFTJS', content, - attachments, filesToUpload, }; @@ -604,64 +568,6 @@ class ThreadComposerWithData extends React.Component { }); }; - listenForUrl = state => { - const { linkPreview, linkPreviewLength } = this.state; - if (linkPreview !== null) return; - - const lastChangeType = state.getLastChangeType(); - if ( - lastChangeType !== 'backspace-character' && - lastChangeType !== 'insert-characters' - ) { - return; - } - - const text = toPlainText(state); - - if (!ENDS_IN_WHITESPACE.test(text)) return; - - const toCheck = text.match(URLS); - - if (toCheck) { - const len = toCheck.length; - if (linkPreviewLength === len) return; // no new links, don't recheck - - let urlToCheck = toCheck[len - 1].trim(); - - if (!/^https?:\/\//i.test(urlToCheck)) { - urlToCheck = 'https://' + urlToCheck; - } - - if (!isURL(urlToCheck)) return; - this.setState({ fetchingLinkPreview: true }); - - getLinkPreviewFromUrl(urlToCheck) - .then(data => - this.setState(prevState => ({ - linkPreview: { ...data, trueUrl: urlToCheck }, - linkPreviewTrueUrl: urlToCheck, - linkPreviewLength: prevState.linkPreviewLength + 1, - fetchingLinkPreview: false, - error: null, - })) - ) - .catch(() => { - this.setState({ - error: - "Oops, that URL didn't seem to want to work. You can still publish your story anyways 👍", - fetchingLinkPreview: false, - }); - }); - } - }; - - removeLinkPreview = () => { - this.setState({ - linkPreview: null, - linkPreviewTrueUrl: null, - }); - }; - render() { const { title, @@ -670,9 +576,6 @@ class ThreadComposerWithData extends React.Component { activeCommunity, activeChannel, isPublishing, - linkPreview, - linkPreviewTrueUrl, - fetchingLinkPreview, } = this.state; const { @@ -722,13 +625,6 @@ class ThreadComposerWithData extends React.Component { editorKey="thread-composer" placeholder="Put your text, photos, code, or embeds here..." className={'threadComposer'} - showLinkPreview={true} - linkPreview={{ - loading: fetchingLinkPreview, - remove: this.removeLinkPreview, - trueUrl: linkPreviewTrueUrl, - data: linkPreview, - }} /> diff --git a/src/components/threadFeedCard/index.js b/src/components/threadFeedCard/index.js index b6cf7cdc8b..a050f02537 100644 --- a/src/components/threadFeedCard/index.js +++ b/src/components/threadFeedCard/index.js @@ -5,7 +5,6 @@ import compose from 'recompose/compose'; import { withRouter } from 'react-router-dom'; import { connect } from 'react-redux'; import Link from 'src/components/link'; -import { LinkPreview } from '../../components/linkPreview'; import Icon from '../../components/icons'; import FacePile from './facePile'; import FormattedThreadLocation from './formattedThreadLocation'; @@ -24,7 +23,10 @@ import { } from './style'; const ThreadFeedCardPure = (props: Object): React$Element => { - const { location: { pathname }, data: { attachments, participants } } = props; + const { + location: { pathname }, + data: { attachments, participants }, + } = props; const attachmentsExist = attachments && attachments.length > 0; const participantsExist = participants && participants.length > 0; @@ -54,24 +56,6 @@ const ThreadFeedCardPure = (props: Object): React$Element => { )} - {attachmentsExist && - attachments.map((attachment, i) => { - if (attachment.attachmentType === 'linkPreview') { - return ( - - - - ); - } else { - return null; - } - })} {participantsExist && } {props.data.messageCount > 0 ? ( diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 676403beba..06e90c50af 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -60,15 +60,6 @@ export const throttle = (func: Function, threshhold: number, scope: any) => { }; }; -export const getLinkPreviewFromUrl = (url: string) => - fetch(`https://links.spectrum.chat/?url=${url}`) - .then(response => { - return response.json(); - }) - .catch(err => { - console.error('Error getting link preview: ', err); - }); - // Truncate a string nicely to a certain length export const truncate = (str: string, length: number) => { if (str.length <= length) { @@ -120,7 +111,7 @@ export const renderMarkdownLinks = (text: string) => { const MARKDOWN_LINK = /(?:\[(.*?)\]\((.*?)\))/g; return replace(text, MARKDOWN_LINK, (fullLink, text, url) => ( - + {text} )); diff --git a/src/views/thread/components/threadDetail.js b/src/views/thread/components/threadDetail.js index c5c8a9739b..7222416316 100644 --- a/src/views/thread/components/threadDetail.js +++ b/src/views/thread/components/threadDetail.js @@ -4,11 +4,8 @@ import compose from 'recompose/compose'; import Link from 'src/components/link'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; -import { getLinkPreviewFromUrl } from 'src/helpers/utils'; import { timeDifference } from 'shared/time-difference'; import { convertTimestampToDate } from 'shared/time-formatting'; -import isURL from 'validator/lib/isURL'; -import { URLS } from 'src/helpers/regexps'; import { openModal } from 'src/actions/modals'; import { addToastWithTimeout } from 'src/actions/toasts'; import setThreadLockMutation from 'shared/graphql/mutations/thread/lockThread'; @@ -18,7 +15,7 @@ import editThreadMutation from 'shared/graphql/mutations/thread/editThread'; import pinThreadMutation from 'shared/graphql/mutations/community/pinCommunityThread'; import type { GetThreadType } from 'shared/graphql/queries/thread/getThread'; import Editor from 'src/components/rich-text-editor'; -import { toJSON, toPlainText, toState } from 'shared/draft-utils'; +import { toJSON, toState } from 'shared/draft-utils'; import Textarea from 'react-textarea-autosize'; import ActionBar from './actionBar'; import ConditionalWrap from 'src/components/conditionalWrap'; @@ -38,16 +35,10 @@ import { track, events, transformations } from 'src/helpers/analytics'; import type { Dispatch } from 'redux'; import { ErrorBoundary } from 'src/components/error'; -const ENDS_IN_WHITESPACE = /(\s|\n)$/; - type State = { isEditing?: boolean, body?: any, title?: string, - linkPreview?: ?Object, - linkPreviewTrueUrl?: string, - linkPreviewLength?: number, - fetchingLinkPreview?: boolean, receiveNotifications?: boolean, isSavingEdit?: boolean, flyoutOpen?: ?boolean, @@ -73,14 +64,10 @@ class ThreadDetailPure extends React.Component { isEditing: false, body: null, title: '', - linkPreview: null, - linkPreviewTrueUrl: '', - fetchingLinkPreview: false, receiveNotifications: false, isSavingEdit: false, flyoutOpen: false, error: '', - linkPreviewLength: 0, }; bodyEditor: any; @@ -99,34 +86,10 @@ class ThreadDetailPure extends React.Component { community: transformations.analyticsCommunity(thread.community), }); - let rawLinkPreview = - thread.attachments && thread.attachments.length > 0 - ? thread.attachments.filter( - attachment => - attachment && attachment.attachmentType === 'linkPreview' - )[0] - : null; - - let cleanLinkPreview = rawLinkPreview && { - attachmentType: rawLinkPreview.attachmentType, - data: JSON.parse(rawLinkPreview.data), - }; - return this.setState({ isEditing: false, body: toState(JSON.parse(thread.content.body)), title: thread.content.title, - // $FlowFixMe - linkPreview: rawLinkPreview ? cleanLinkPreview.data : null, - linkPreviewTrueUrl: - thread.attachments && - thread.attachments.length > 0 && - thread.attachments[0] - ? thread.attachments[0].trueUrl - : '', - linkPreviewLength: - thread.attachments && thread.attachments.length > 0 ? 1 : 0, - fetchingLinkPreview: false, flyoutOpen: false, receiveNotifications: thread.receiveNotifications, isSavingEdit: false, @@ -237,7 +200,7 @@ class ThreadDetailPure extends React.Component { saveEdit = () => { const { dispatch, editThread, thread } = this.props; - const { linkPreview, linkPreviewTrueUrl, title, body } = this.state; + const { title, body } = this.state; const threadId = thread.id; if (!title || title.trim().length === 0) { @@ -258,18 +221,6 @@ class ThreadDetailPure extends React.Component { body: JSON.stringify(jsonBody), }; - const attachments = []; - if (linkPreview) { - const attachmentData = JSON.stringify({ - ...linkPreview, - trueUrl: linkPreviewTrueUrl, - }); - attachments.push({ - attachmentType: 'linkPreview', - data: attachmentData, - }); - } - // Get the images const filesToUpload = Object.keys(jsonBody.entityMap) .filter( @@ -283,7 +234,6 @@ class ThreadDetailPure extends React.Component { const input = { threadId, content, - attachments, filesToUpload, }; @@ -325,70 +275,11 @@ class ThreadDetailPure extends React.Component { }; changeBody = state => { - this.listenForUrl(state); this.setState({ body: state, }); }; - listenForUrl = state => { - const { linkPreview, linkPreviewLength } = this.state; - if (linkPreview !== null) return; - - const lastChangeType = state.getLastChangeType(); - if ( - lastChangeType !== 'backspace-character' && - lastChangeType !== 'insert-characters' - ) { - return; - } - - const text = toPlainText(state); - - if (!ENDS_IN_WHITESPACE.test(text)) return; - - const toCheck = text.match(URLS); - - if (toCheck) { - const len = toCheck.length; - if (linkPreviewLength === len) return; // no new links, don't recheck - - let urlToCheck = toCheck[len - 1].trim(); - - if (!/^https?:\/\//i.test(urlToCheck)) { - urlToCheck = 'https://' + urlToCheck; - } - - if (!isURL(urlToCheck)) return; - this.setState({ fetchingLinkPreview: true }); - - getLinkPreviewFromUrl(urlToCheck) - .then(data => { - return this.setState(prevState => ({ - linkPreview: { ...data, trueUrl: urlToCheck }, - linkPreviewTrueUrl: urlToCheck, - linkPreviewLength: prevState.linkPreviewLength + 1, - fetchingLinkPreview: false, - error: null, - })); - }) - .catch(() => { - this.setState({ - error: - "Oops, that URL didn't seem to want to work. You can still publish your story anyways 👍", - fetchingLinkPreview: false, - }); - }); - } - }; - - removeLinkPreview = () => { - this.setState({ - linkPreview: null, - linkPreviewTrueUrl: '', - }); - }; - togglePinThread = () => { const { pinThread, thread, dispatch } = this.props; const isPinned = thread.community.pinnedThreadId === thread.id; @@ -430,9 +321,7 @@ class ThreadDetailPure extends React.Component { const { isEditing, - linkPreview, body, - fetchingLinkPreview, isSavingEdit, isLockingThread, isPinningThread, @@ -515,15 +404,8 @@ class ThreadDetailPure extends React.Component { onChange={this.changeBody} editorKey="thread-detail" placeholder="Write more thoughts here..." - showLinkPreview={true} editorRef={editor => (this.bodyEditor = editor)} version={2} - linkPreview={{ - loading: fetchingLinkPreview, - remove: this.removeLinkPreview, - trueUrl: linkPreview && linkPreview.url, - data: linkPreview, - }} /> From efcf069f5a4ccc42e24efdfcce2967893510b502 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 10:41:39 -0700 Subject: [PATCH 12/14] Remove clientside references to attachments --- src/components/threadFeedCard/index.js | 4 +--- src/components/threadFeedCard/style.js | 8 -------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/threadFeedCard/index.js b/src/components/threadFeedCard/index.js index a050f02537..30f8dd2635 100644 --- a/src/components/threadFeedCard/index.js +++ b/src/components/threadFeedCard/index.js @@ -14,7 +14,6 @@ import { CardLink, Title, MessageCount, - Attachments, Pinned, PinnedBanner, PinnedIconWrapper, @@ -25,9 +24,8 @@ import { const ThreadFeedCardPure = (props: Object): React$Element => { const { location: { pathname }, - data: { attachments, participants }, + data: { participants }, } = props; - const attachmentsExist = attachments && attachments.length > 0; const participantsExist = participants && participants.length > 0; return ( diff --git a/src/components/threadFeedCard/style.js b/src/components/threadFeedCard/style.js index 6f4d86c3bf..ac5eb99ccf 100644 --- a/src/components/threadFeedCard/style.js +++ b/src/components/threadFeedCard/style.js @@ -62,14 +62,6 @@ export const MessageCount = styled(FlexRow)` } `; -export const Attachments = styled(FlexRow)` - align-self: stretch; - align-items: center; - justify-content: space-between; - margin: 0; - margin-top: 8px; -`; - export const AuthorName = styled.span` font-weight: 500; font-size: 13px; From 47fd755beb488ced7a57375d3d2c39b54c7e7340 Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 11:01:33 -0700 Subject: [PATCH 13/14] 2.4.51 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63e0377422..d1f1c0143a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Spectrum", - "version": "2.4.50", + "version": "2.4.51", "license": "BSD-3-Clause", "devDependencies": { "babel-cli": "^6.24.1", From b60c84511d4b7278add851c11b79ee2cbeea3c2a Mon Sep 17 00:00:00 2001 From: Brian Lovin Date: Thu, 25 Oct 2018 11:34:04 -0700 Subject: [PATCH 14/14] Eslint --- src/views/thread/components/watercoolerActionBar.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/thread/components/watercoolerActionBar.js b/src/views/thread/components/watercoolerActionBar.js index a6be7e0312..bd064c55bf 100644 --- a/src/views/thread/components/watercoolerActionBar.js +++ b/src/views/thread/components/watercoolerActionBar.js @@ -3,7 +3,6 @@ import * as React from 'react'; import { connect } from 'react-redux'; import Clipboard from 'react-clipboard.js'; import { addToastWithTimeout } from '../../../actions/toasts'; -import { openModal } from '../../../actions/modals'; import Icon from '../../../components/icons'; import compose from 'recompose/compose'; import type { GetThreadType } from 'shared/graphql/queries/thread/getThread';