From 3e68a43f2d902522ca31e5a4810c023473776bb3 Mon Sep 17 00:00:00 2001 From: Bruno Lemos Date: Sat, 13 Jan 2018 17:03:58 -0200 Subject: [PATCH] Finished event cards components --- android/app/build.gradle | 1 + android/settings.gradle | 2 + ios/devhub.xcodeproj/project.pbxproj | 59 +++++ package.json | 5 +- src/components/cards/EventCard.tsx | 239 +++++++++++++++++- src/components/cards/EventCards.tsx | 23 +- src/components/cards/NotificationCard.tsx | 10 +- src/components/cards/NotificationCards.tsx | 24 +- src/components/cards/SwipeableEventCard.tsx | 4 +- src/components/cards/partials/CardItemId.tsx | 83 ++++++ .../cards/partials/rows/BranchRow.tsx | 66 +++++ .../cards/partials/rows/CommentRow.tsx | 55 ++++ .../cards/partials/rows/CommitListRow.tsx | 38 +++ .../cards/partials/rows/CommitRow.tsx | 91 +++++++ .../partials/rows/IssueOrPullRequestRow.tsx | 99 ++++++++ .../cards/partials/rows/ReleaseRow.tsx | 112 ++++++++ .../cards/partials/rows/RepositoryListRow.tsx | 48 ++++ .../cards/partials/rows/RepositoryRow.tsx | 67 +++-- .../cards/partials/rows/RowList.tsx | 49 ++++ .../cards/partials/rows/UserListRow.tsx | 35 +++ .../cards/partials/rows/UserRow.tsx | 7 +- .../cards/partials/rows/WikiPageListRow.tsx | 36 +++ .../cards/partials/rows/WikiPageRow.tsx | 44 ++++ src/components/cards/partials/rows/helpers.ts | 39 ++- src/components/cards/partials/rows/styles.ts | 6 +- src/components/cards/styles.ts | 19 +- src/components/common/Avatar.tsx | 15 +- src/components/common/Label.tsx | 6 +- .../common/TransparentTextOverlay.tsx | 121 +++++++++ src/containers/EventCardsContainer.tsx | 156 ++++++------ src/libs/linear-gradient/index.tsx | 4 + src/libs/linear-gradient/index.web.tsx | 30 +++ src/libs/swipeable/AppleSwipeableRow.tsx | 13 +- src/libs/swipeable/GoogleSwipeableRow.tsx | 13 +- src/setup.ts | 12 +- src/styles/styles.ts | 4 + src/styles/themes/dark-blue.ts | 3 +- src/styles/themes/dark.ts | 3 +- src/styles/themes/light.ts | 3 +- src/types/github.ts | 23 +- src/types/index.ts | 43 ++-- src/utils/helpers/github/shared.ts | 4 +- src/utils/helpers/github/url.ts | 132 ++++++++++ src/utils/helpers/shared.ts | 11 + yarn.lock | 6 + 45 files changed, 1677 insertions(+), 186 deletions(-) create mode 100644 src/components/cards/partials/CardItemId.tsx create mode 100644 src/components/cards/partials/rows/BranchRow.tsx create mode 100644 src/components/cards/partials/rows/CommentRow.tsx create mode 100644 src/components/cards/partials/rows/CommitListRow.tsx create mode 100644 src/components/cards/partials/rows/CommitRow.tsx create mode 100644 src/components/cards/partials/rows/IssueOrPullRequestRow.tsx create mode 100644 src/components/cards/partials/rows/ReleaseRow.tsx create mode 100644 src/components/cards/partials/rows/RepositoryListRow.tsx create mode 100644 src/components/cards/partials/rows/RowList.tsx create mode 100644 src/components/cards/partials/rows/UserListRow.tsx create mode 100644 src/components/cards/partials/rows/WikiPageListRow.tsx create mode 100644 src/components/cards/partials/rows/WikiPageRow.tsx create mode 100644 src/components/common/TransparentTextOverlay.tsx create mode 100644 src/libs/linear-gradient/index.tsx create mode 100644 src/libs/linear-gradient/index.web.tsx create mode 100644 src/utils/helpers/github/url.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 43cf8197..cc76a01a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -137,6 +137,7 @@ android { } dependencies { + compile project(':react-native-linear-gradient') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" compile "com.facebook.react:react-native:+" // From node_modules diff --git a/android/settings.gradle b/android/settings.gradle index b6b396d3..6d4c15e6 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'devhub' +include ':react-native-linear-gradient' +project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android') include ':react-native-gesture-handler' project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') include ':react-native-navigation' diff --git a/ios/devhub.xcodeproj/project.pbxproj b/ios/devhub.xcodeproj/project.pbxproj index a266faf3..031ec5bd 100644 --- a/ios/devhub.xcodeproj/project.pbxproj +++ b/ios/devhub.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; 00E356F31AD99517003FC87E /* devhubTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* devhubTests.m */; }; + 072BCFEF97034DD8A762C885 /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D59EE38FE98B402B99250666 /* libBVLinearGradient.a */; }; 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; @@ -117,6 +118,20 @@ remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7; remoteInfo = "devhub-tvOS"; }; + 2E60B5072001851A0076D5AF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 12697B233D884F2EB9A7D7F0 /* BVLinearGradient.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = BVLinearGradient; + }; + 2E60B5092001851A0076D5AF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 12697B233D884F2EB9A7D7F0 /* BVLinearGradient.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 64AA15081EF7F30100718508; + remoteInfo = "BVLinearGradient-tvOS"; + }; 2E9757CD1FE54A870087D2A7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = D1C1CBC9A5564D089F2E18B2 /* ReactNativeNavigation.xcodeproj */; @@ -346,6 +361,7 @@ 00E356EE1AD99517003FC87E /* devhubTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = devhubTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* devhubTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = devhubTests.m; sourceTree = ""; }; + 12697B233D884F2EB9A7D7F0 /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = BVLinearGradient.xcodeproj; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* devhub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = devhub.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -376,6 +392,7 @@ B6BF65C496E44E78BF211275 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = ""; }; C1F790D96FC04CEBA08381F0 /* SafariViewManager.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = SafariViewManager.xcodeproj; path = "../node_modules/react-native-safari-view/SafariViewManager.xcodeproj"; sourceTree = ""; }; D1C1CBC9A5564D089F2E18B2 /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; }; + D59EE38FE98B402B99250666 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = ""; }; D8C408A943574FCE997797CD /* libReactNativeNavigation.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libReactNativeNavigation.a; sourceTree = ""; }; F035D9E85FA74D8CBFBDAE1B /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; }; F1AA246742BC4C2A8C9988F3 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; }; @@ -413,6 +430,7 @@ C2619E3179B6481FA20E5CEB /* libRNGestureHandler.a in Frameworks */, B71E99D6DCBF4278835E3D03 /* libReactNativeNavigation.a in Frameworks */, DC69E7EC47C64276A45E93CF /* libSafariViewManager.a in Frameworks */, + 072BCFEF97034DD8A762C885 /* libBVLinearGradient.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -555,6 +573,15 @@ name = Products; sourceTree = ""; }; + 2E60B5032001851A0076D5AF /* Products */ = { + isa = PBXGroup; + children = ( + 2E60B5082001851A0076D5AF /* libBVLinearGradient.a */, + 2E60B50A2001851A0076D5AF /* libBVLinearGradient.a */, + ); + name = Products; + sourceTree = ""; + }; 2E9757CA1FE54A870087D2A7 /* Products */ = { isa = PBXGroup; children = ( @@ -578,6 +605,7 @@ B5DF78792708477FB2343101 /* libRNGestureHandler.a */, D8C408A943574FCE997797CD /* libReactNativeNavigation.a */, 55B8B3E654F0420CA0ED33D3 /* libSafariViewManager.a */, + D59EE38FE98B402B99250666 /* libBVLinearGradient.a */, ); name = "Recovered References"; sourceTree = ""; @@ -635,6 +663,7 @@ 1FD65CD116F747ACA2D47E1F /* RNGestureHandler.xcodeproj */, D1C1CBC9A5564D089F2E18B2 /* ReactNativeNavigation.xcodeproj */, C1F790D96FC04CEBA08381F0 /* SafariViewManager.xcodeproj */, + 12697B233D884F2EB9A7D7F0 /* BVLinearGradient.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -815,6 +844,10 @@ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; projectReferences = ( + { + ProductGroup = 2E60B5032001851A0076D5AF /* Products */; + ProjectRef = 12697B233D884F2EB9A7D7F0 /* BVLinearGradient.xcodeproj */; + }, { ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; @@ -947,6 +980,20 @@ remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 2E60B5082001851A0076D5AF /* libBVLinearGradient.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libBVLinearGradient.a; + remoteRef = 2E60B5072001851A0076D5AF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 2E60B50A2001851A0076D5AF /* libBVLinearGradient.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libBVLinearGradient.a; + remoteRef = 2E60B5092001851A0076D5AF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 2E9757CE1FE54A870087D2A7 /* libReactNativeNavigation.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1311,6 +1358,7 @@ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient", ); INFOPLIST_FILE = devhubTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1319,6 +1367,7 @@ "$(inherited)", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1340,6 +1389,7 @@ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient", ); INFOPLIST_FILE = devhubTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1348,6 +1398,7 @@ "$(inherited)", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1371,6 +1422,7 @@ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient", ); INFOPLIST_FILE = devhub/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1396,6 +1448,7 @@ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient", ); INFOPLIST_FILE = devhub/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1427,6 +1480,7 @@ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient", ); INFOPLIST_FILE = "devhub-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1434,6 +1488,7 @@ "$(inherited)", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1465,6 +1520,7 @@ "$(SRCROOT)/../node_modules/react-native-gesture-handler/ios/**", "$(SRCROOT)/../node_modules/react-native-navigation/ios/**", "$(SRCROOT)/../node_modules/react-native-safari-view", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/BVLinearGradient", ); INFOPLIST_FILE = "devhub-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1472,6 +1528,7 @@ "$(inherited)", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1502,6 +1559,7 @@ "$(inherited)", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.devhub-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1528,6 +1586,7 @@ "$(inherited)", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.devhub-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/package.json b/package.json index 89a09d67..8c0d1660 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react": "^16.2.0", "react-native": "^0.51.0", "react-native-gesture-handler": "^1.0.0-alpha.37", + "react-native-linear-gradient": "^2.4.0", "react-native-navigation": "^1.1.334", "react-native-safari-view": "^2.1.0", "react-native-vector-icons": "^4.4.3", @@ -50,7 +51,9 @@ }, "jest": { "preset": "react-native", - "setupFiles": ["./jest/setup.js"], + "setupFiles": [ + "./jest/setup.js" + ], "globals": { "ts-jest": { "useBabelrc": true diff --git a/src/components/cards/EventCard.tsx b/src/components/cards/EventCard.tsx index cf8e663c..d17417e1 100644 --- a/src/components/cards/EventCard.tsx +++ b/src/components/cards/EventCard.tsx @@ -3,14 +3,41 @@ import { StyleSheet, View, ViewStyle } from 'react-native' import theme from '../../styles/themes/dark' import { contentPadding } from '../../styles/variables' -import { IGitHubEvent } from '../../types/index' +import { + IForkEvent, + IGitHubCommit, + IGitHubCommitCommentEvent, + IGitHubEvent, + IGitHubPage, + IGitHubRepo, + IGitHubUser, + IGollumEvent, + IIssuesEvent, + IMemberEvent, + IPullRequestEvent, + IPushEvent, + IReleaseEvent, +} from '../../types/index' import { getEventIconAndColor, getEventText, } from '../../utils/helpers/github/events' -import { getOwnerAndRepo } from '../../utils/helpers/github/shared' +import { + getIssueIconAndColor, + getOwnerAndRepo, + getPullRequestIconAndColor, +} from '../../utils/helpers/github/shared' +import { getRepoFullNameFromObject } from '../../utils/helpers/github/url' import EventCardHeader from './partials/EventCardHeader' +import BranchRow from './partials/rows/BranchRow' +import CommentRow from './partials/rows/CommentRow' +import CommitListRow from './partials/rows/CommitListRow' +import IssueOrPullRequestRow from './partials/rows/IssueOrPullRequestRow' +import ReleaseRow from './partials/rows/ReleaseRow' +import RepositoryListRow from './partials/rows/RepositoryListRow' import RepositoryRow from './partials/rows/RepositoryRow' +import UserListRow from './partials/rows/UserListRow' +import WikiPageListRow from './partials/rows/WikiPageListRow' export interface IProps { event: IGitHubEvent @@ -29,9 +56,44 @@ const styles = StyleSheet.create({ export default class EventCard extends PureComponent { render() { const { event } = this.props + const { actor, payload, repo: _repo, type } = event - const repoFullName = event.repo.full_name || event.repo.name || '' - const { owner: orgName, repo: repoName } = getOwnerAndRepo(repoFullName) + const { comment } = payload as IGitHubCommitCommentEvent['payload'] + const { commits: _commits } = payload as IPushEvent['payload'] + const { forkee } = payload as IForkEvent['payload'] + const { member: _member } = payload as IMemberEvent['payload'] + const { release } = payload as IReleaseEvent['payload'] + const { pages: _pages } = payload as IGollumEvent['payload'] + const { + pull_request: pullRequest, + } = payload as IPullRequestEvent['payload'] + const { issue } = payload as IIssuesEvent['payload'] + const { ref: branchName } = payload as IPushEvent['payload'] + + const isRead = false // TODO + const commits: IGitHubCommit[] = (_commits || []).filter(Boolean) + const repos: IGitHubRepo[] = [_repo].filter(Boolean) // TODO + const users: IGitHubUser[] = [_member].filter(Boolean) // TODO + const pages: IGitHubPage[] = (_pages || []).filter(Boolean) + + const repo = repos.length === 1 ? repos[0] : undefined + + const commitIds = commits + .filter(Boolean) + .map((item: IGitHubCommit) => item.sha) + const pageIds = pages.filter(Boolean).map((item: IGitHubPage) => item.sha) + const repoIds = repos.filter(Boolean).map((item: IGitHubRepo) => item.id) + const userIds = users.filter(Boolean).map((item: IGitHubUser) => item.id) + + const repoFullName = repo && getRepoFullNameFromObject(repo) + const { owner: repoOwnerName, repo: repoName } = getOwnerAndRepo( + repoFullName || '', + ) + + const forkRepoFullName = getRepoFullNameFromObject(forkee) + const { owner: forkRepoOwnerName, repo: forkRepoName } = getOwnerAndRepo( + forkRepoFullName, + ) const cardIconDetails = getEventIconAndColor(event, theme) const cardIconName = cardIconDetails.subIcon || cardIconDetails.icon @@ -39,6 +101,32 @@ export default class EventCard extends PureComponent { const actionText = getEventText(event) + const isPush = type === 'PushEvent' + const isForcePush = isPush && (payload as IPushEvent).forced + + const { + icon: pullRequestIconName, + color: pullRequestIconColor, + } = pullRequest + ? getPullRequestIconAndColor(pullRequest) + : { icon: undefined, color: undefined } + + const pullRequestURL = + pullRequest && + (comment && !comment.body && comment.html_url + ? comment.html_url || comment.url + : pullRequest.html_url || pullRequest.url) + + const { icon: issueIconName, color: issueIconColor } = issue + ? getIssueIconAndColor(issue) + : { icon: undefined, color: undefined } + + const issueURL = + issue && + (comment && !comment.body && (comment.html_url || comment.url) + ? comment.html_url || comment.url + : issue.html_url || issue.url) + return ( { cardIconName={cardIconName} username={event.actor.login} /> - {Boolean(event.repo && orgName && repoName) && ( + + {repos.length > 0 && ( + + )} + + {Boolean(repo && repoOwnerName && repoName && branchName) && ( + + )} + + {Boolean(forkee && forkRepoOwnerName && forkRepoName) && ( + )} + + {users.length > 0 && ( + + )} + + {pages.length > 0 && ( + + )} + + {Boolean(pullRequest) && ( + + )} + + {commits.length > 0 && ( + + )} + + {Boolean(issue) && ( + + )} + + {(type === 'IssuesEvent' && + (payload as IIssuesEvent['payload']).action === 'opened' && + Boolean((payload as IIssuesEvent['payload']).issue.body) && ( + + )) || + (type === 'PullRequestEvent' && + (payload as IPullRequestEvent['payload']).action === 'opened' && + Boolean(pullRequest.body) && ( + + )) || + (Boolean(comment && comment.body) && ( + + ))} + + {Boolean(release) && ( + )} diff --git a/src/components/cards/EventCards.tsx b/src/components/cards/EventCards.tsx index 41bf2460..f4fa6b5b 100644 --- a/src/components/cards/EventCards.tsx +++ b/src/components/cards/EventCards.tsx @@ -1,7 +1,10 @@ import React from 'react' import { FlatList } from 'react-native' +import theme from '../../styles/themes/dark' +import { contentPadding } from '../../styles/variables' import { IGitHubEvent } from '../../types' +import TransparentTextOverlay from '../common/TransparentTextOverlay' import CardItemSeparator from './partials/CardItemSeparator' import SwipeableEventCard from './SwipeableEventCard' @@ -11,7 +14,7 @@ export interface IProps { class EventCards extends React.PureComponent { keyExtractor(event: IGitHubEvent) { - return event.id + return `${event.id}` } renderItem({ item: event }: { item: IGitHubEvent }) { @@ -22,12 +25,18 @@ class EventCards extends React.PureComponent { const { events } = this.props return ( - + + + ) } } diff --git a/src/components/cards/NotificationCard.tsx b/src/components/cards/NotificationCard.tsx index 9010eb54..379c18d7 100644 --- a/src/components/cards/NotificationCard.tsx +++ b/src/components/cards/NotificationCard.tsx @@ -32,7 +32,9 @@ export default class NotificationCard extends PureComponent { const repoFullName = notification.repository.full_name || notification.repository.name || '' - const { owner: orgName, repo: repoName } = getOwnerAndRepo(repoFullName) + const { owner: repoOwnerName, repo: repoName } = getOwnerAndRepo( + repoFullName, + ) const cardIconDetails = getNotificationIconAndColor( notification, @@ -54,10 +56,10 @@ export default class NotificationCard extends PureComponent { labelColor={labelColor} labelText={labelText} /> - {Boolean(orgName && repoName) && ( + {Boolean(repoOwnerName && repoName) && ( )} diff --git a/src/components/cards/NotificationCards.tsx b/src/components/cards/NotificationCards.tsx index f41e4021..1da896ac 100644 --- a/src/components/cards/NotificationCards.tsx +++ b/src/components/cards/NotificationCards.tsx @@ -1,7 +1,10 @@ import React from 'react' import { FlatList } from 'react-native' +import theme from '../../styles/themes/dark' +import { contentPadding } from '../../styles/variables' import { IGitHubNotification } from '../../types' +import TransparentTextOverlay from '../common/TransparentTextOverlay' import NotificationCard from './NotificationCard' import CardItemSeparator from './partials/CardItemSeparator' @@ -11,7 +14,7 @@ export interface IProps { class NotificationCards extends React.PureComponent { keyExtractor(notification: IGitHubNotification) { - return notification.id + return `${notification.id}` } renderItem({ item: notification }: { item: IGitHubNotification }) { @@ -26,12 +29,19 @@ class NotificationCards extends React.PureComponent { render() { const { notifications } = this.props return ( - + + + ) } } diff --git a/src/components/cards/SwipeableEventCard.tsx b/src/components/cards/SwipeableEventCard.tsx index 50d87c8b..c0eed9e9 100644 --- a/src/components/cards/SwipeableEventCard.tsx +++ b/src/components/cards/SwipeableEventCard.tsx @@ -21,8 +21,8 @@ export default class SwipeableEventCard extends PureComponent { leftActions={[ { color: theme.blue, - icon: 'read', - key: 'read', + icon: 'isRead', + key: 'isRead', label: 'Read', onPress: this.onMarkAsRead, type: 'FULL', diff --git a/src/components/cards/partials/CardItemId.tsx b/src/components/cards/partials/CardItemId.tsx new file mode 100644 index 00000000..5eed13d7 --- /dev/null +++ b/src/components/cards/partials/CardItemId.tsx @@ -0,0 +1,83 @@ +import React, { SFC } from 'react' +import { + StyleSheet, + Text, + TextStyle, + TouchableOpacity, + ViewStyle, +} from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' + +import { radius } from '../../../styles/variables' +import { IGitHubIcon, ITheme } from '../../../types' +import cardStyles from '../styles' +import { getGithubURLPressHandler } from './rows/helpers' + +export interface IProps { + icon?: IGitHubIcon + id: number | string + isRead?: boolean + style?: ViewStyle + theme: ITheme + url: string +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + borderRadius: radius, + borderWidth: StyleSheet.hairlineWidth, + flexDirection: 'row', + justifyContent: 'space-between', + paddingHorizontal: 4, + } as ViewStyle, + + text: { + backgroundColor: 'transparent', + fontSize: 12, + fontWeight: 'bold', + lineHeight: 20, + opacity: 0.9, + } as TextStyle, +}) + +export const CardItemId: SFC = ({ + icon, + id, + isRead, + style, + theme, + url, +}) => { + if (!id && !icon) return null + + const parsedNumber = parseInt(`${id}`, 10) || id + + return ( + + + {icon ? : ''} + {parsedNumber && icon ? ' ' : ''} + {typeof parsedNumber === 'number' ? '#' : ''} + {parsedNumber} + + + ) +} diff --git a/src/components/cards/partials/rows/BranchRow.tsx b/src/components/cards/partials/rows/BranchRow.tsx new file mode 100644 index 00000000..c1326f13 --- /dev/null +++ b/src/components/cards/partials/rows/BranchRow.tsx @@ -0,0 +1,66 @@ +import React, { SFC } from 'react' +import { Text, TouchableOpacity, View } from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' + +import { IGitHubEventType } from '../../../../types' +import Avatar from '../../../common/Avatar' +import cardStyles from '../../styles' +import { getBranchPressHandler } from './helpers' +import rowStyles from './styles' + +export interface IProps { + branch: string + isRead?: boolean + ownerName: string + repositoryName: string + type: IGitHubEventType +} + +export interface IState {} + +const BranchRow: SFC = ({ + branch: _branch, + isRead, + ownerName, + repositoryName, + type, +}) => { + const branch = (_branch || '').replace('refs/heads/', '') + if (!branch) return null + + const isBranchMainEventAction = + type === 'CreateEvent' || type === 'DeleteEvent' + if (branch === 'master' && !isBranchMainEventAction) return null + + return ( + + + + + + + + + + {' '} + + {branch} + + + + + ) +} + +export default BranchRow diff --git a/src/components/cards/partials/rows/CommentRow.tsx b/src/components/cards/partials/rows/CommentRow.tsx new file mode 100644 index 00000000..7bb01000 --- /dev/null +++ b/src/components/cards/partials/rows/CommentRow.tsx @@ -0,0 +1,55 @@ +import React, { SFC } from 'react' +import { Text, TouchableOpacity, View } from 'react-native' + +import { IGitHubEventType } from '../../../../types' +import { trimNewLinesAndSpaces } from '../../../../utils/helpers/shared' +import Avatar from '../../../common/Avatar' +import cardStyles from '../../styles' +import { getGithubURLPressHandler } from './helpers' +import rowStyles from './styles' + +export interface IProps { + body: string + isRead?: boolean + numberOfLines?: number + username: string + type: IGitHubEventType + url?: string +} + +export interface IState {} + +const CommentRow: SFC = ({ + body: _body, + isRead, + numberOfLines = 4, + url, + username, +}) => { + const body = trimNewLinesAndSpaces(_body, 400) + if (!body) return null + + return ( + + + + + + + + + {body} + + + + + ) +} + +export default CommentRow diff --git a/src/components/cards/partials/rows/CommitListRow.tsx b/src/components/cards/partials/rows/CommitListRow.tsx new file mode 100644 index 00000000..f5101768 --- /dev/null +++ b/src/components/cards/partials/rows/CommitListRow.tsx @@ -0,0 +1,38 @@ +import React from 'react' + +import CommitRow from './CommitRow' +import RowList from './RowList' + +import { IGitHubCommit, ITheme } from '../../../../types' + +export interface IProps { + isRead?: boolean + maxHeight?: number + commits: IGitHubCommit[] + theme: ITheme +} + +export default class CommitListRow extends React.PureComponent { + renderItem({ item: commit }: { item: IGitHubCommit }) { + if (!(commit && commit.sha && commit.message)) return null + + return ( + + ) + } + + render() { + const { commits, ...props } = this.props + + if (!(commits && commits.length > 0)) return null + + return + } +} diff --git a/src/components/cards/partials/rows/CommitRow.tsx b/src/components/cards/partials/rows/CommitRow.tsx new file mode 100644 index 00000000..a4fae5e3 --- /dev/null +++ b/src/components/cards/partials/rows/CommitRow.tsx @@ -0,0 +1,91 @@ +import React, { SFC } from 'react' +import { Text, TouchableOpacity, View } from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' + +import { tryGetUsernameFromGitHubEmail } from '../../../../utils/helpers/github/shared' +import { + getGitHubSearchURL, + getGitHubURLForUser, +} from '../../../../utils/helpers/github/url' +import { trimNewLinesAndSpaces } from '../../../../utils/helpers/shared' +import Avatar from '../../../common/Avatar' +import cardStyles from '../../styles' +import { getGithubURLPressHandler } from './helpers' +import rowStyles from './styles' + +export interface IProps { + authorEmail?: string + authorName?: string + authorUsername?: string + isRead?: boolean + message: string + url: string +} + +export interface IState {} + +const CommitRow: SFC = ({ + authorEmail, + authorName, + authorUsername: _authorUsername, + isRead, + message: _message, + url, +}) => { + const message = trimNewLinesAndSpaces(_message) + if (!message) return null + + const authorUsername = + _authorUsername || tryGetUsernameFromGitHubEmail(authorEmail) + + let byText = authorName + if (authorUsername) byText += ` @${authorUsername}` + else if (authorEmail) + byText += byText ? ` <${authorEmail}>` : ` ${authorEmail}` + byText = trimNewLinesAndSpaces(byText) + + return ( + + + + + + + + + {message} + {Boolean(byText) && ( + + {` by ${byText}`} + + )} + + + + + ) +} + +export default CommitRow diff --git a/src/components/cards/partials/rows/IssueOrPullRequestRow.tsx b/src/components/cards/partials/rows/IssueOrPullRequestRow.tsx new file mode 100644 index 00000000..b3dc50e7 --- /dev/null +++ b/src/components/cards/partials/rows/IssueOrPullRequestRow.tsx @@ -0,0 +1,99 @@ +import React, { SFC } from 'react' +import { + StyleSheet, + Text, + TouchableOpacity, + View, + ViewStyle, +} from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' + +import defaultStyles from '../../../../styles/styles' +import { contentPadding } from '../../../../styles/variables' +import { ITheme } from '../../../../types' +import { trimNewLinesAndSpaces } from '../../../../utils/helpers/shared' +import Avatar from '../../../common/Avatar' +import cardStyles from '../../styles' +import { CardItemId } from '../CardItemId' +import { getGithubURLPressHandler } from './helpers' +import rowStyles from './styles' + +export interface IProps { + iconColor?: string + iconName: string + isRead?: boolean + issueNumber: number + theme: ITheme + title: string + url: string + username: string +} + +export interface IState {} + +const styles = StyleSheet.create({ + cardItemId: { + marginLeft: contentPadding, + } as ViewStyle, +}) + +const IssueOrPullRequestRow: SFC = ({ + iconColor, + iconName, + isRead, + issueNumber, + theme, + title: _title, + url, + username, +}) => { + const title = trimNewLinesAndSpaces(_title) + if (!title) return null + + const byText = username ? `@${username}` : '' + + return ( + + + + + + + + + + {title} + {Boolean(byText) && ( + + {' '} + by {byText} + + )} + + + + + + + + ) +} + +export default IssueOrPullRequestRow diff --git a/src/components/cards/partials/rows/ReleaseRow.tsx b/src/components/cards/partials/rows/ReleaseRow.tsx new file mode 100644 index 00000000..e0df61ca --- /dev/null +++ b/src/components/cards/partials/rows/ReleaseRow.tsx @@ -0,0 +1,112 @@ +import React, { SFC } from 'react' +import { Text, TouchableOpacity, View } from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' + +import { IGitHubEventType } from '../../../../types' +import { getOwnerAndRepo } from '../../../../utils/helpers/github/shared' +import { getRepoFullNameFromUrl } from '../../../../utils/helpers/github/url' +import { trimNewLinesAndSpaces } from '../../../../utils/helpers/shared' +import Avatar from '../../../common/Avatar' +import cardStyles from '../../styles' +import BranchRow from './BranchRow' +import { getGithubURLPressHandler } from './helpers' +import rowStyles from './styles' + +export interface IProps { + branch: string + body: string + isRead?: boolean + name: string + ownerName: string + repositoryName: string + tagName: string + type: IGitHubEventType + url: string +} + +export interface IState {} + +const ReleaseRow: SFC = ({ + body: _body, + branch, + isRead, + name: _name, + tagName: _tagName, + type, + url, +}) => { + const body = trimNewLinesAndSpaces(_body) + const name = trimNewLinesAndSpaces(_name) + const tagName = trimNewLinesAndSpaces(_tagName) + + const { owner: ownerName, repo: repositoryName } = getOwnerAndRepo( + getRepoFullNameFromUrl(url), + ) + + return ( + + {Boolean(branch && ownerName && repositoryName) && ( + + )} + + + + + + + + + + + {' '} + + {name || tagName} + + + + + + + + + + + + + + + {' '} + + {body} + + + + + + ) +} + +export default ReleaseRow diff --git a/src/components/cards/partials/rows/RepositoryListRow.tsx b/src/components/cards/partials/rows/RepositoryListRow.tsx new file mode 100644 index 00000000..52a86e8f --- /dev/null +++ b/src/components/cards/partials/rows/RepositoryListRow.tsx @@ -0,0 +1,48 @@ +import React from 'react' + +import RepositoryRow from './RepositoryRow' +import RowList from './RowList' + +import { IGitHubRepo, ITheme } from '../../../../types' +import { getOwnerAndRepo } from '../../../../utils/helpers/github/shared' +import { getRepoFullNameFromObject } from '../../../../utils/helpers/github/url' + +export interface IProps { + isForcePush?: boolean + isFork?: boolean + isPush?: boolean + isRead?: boolean + maxHeight?: number + repos: IGitHubRepo[] + theme: ITheme +} + +export default class RepositoryListRow extends React.PureComponent { + renderItem({ item: repo }: { item: IGitHubRepo }) { + if (!(repo && repo.id)) return null + + const repoFullName = getRepoFullNameFromObject(repo) + const { owner: repoOwnerName, repo: repoName } = getOwnerAndRepo( + repoFullName, + ) + + if (!(repoOwnerName && repoName)) return null + + return ( + + ) + } + + render() { + const { repos, ...props } = this.props + + if (!(repos && repos.length > 0)) return null + + return + } +} diff --git a/src/components/cards/partials/rows/RepositoryRow.tsx b/src/components/cards/partials/rows/RepositoryRow.tsx index 1e14a80c..5daf49f1 100644 --- a/src/components/cards/partials/rows/RepositoryRow.tsx +++ b/src/components/cards/partials/rows/RepositoryRow.tsx @@ -1,5 +1,6 @@ import React, { SFC } from 'react' import { Text, TouchableOpacity, View } from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' import Avatar from '../../../common/Avatar' import cardStyles from '../../styles' @@ -7,28 +8,60 @@ import { getRepositoryPressHandler } from './helpers' import rowStyles from './styles' export interface IProps { - repository: string - owner: string + isForcePush?: boolean + isFork?: boolean + isPush?: boolean + isRead?: boolean + ownerName: string + repositoryName: string } export interface IState {} -const RepositoryRow: SFC = ({ owner, repository }) => ( - - - - +const RepositoryRow: SFC = ({ + isForcePush, + isFork, + isPush, + isRead, + ownerName, + repositoryName, +}) => { + const repoIcon = + (isForcePush && 'repo-force-push') || + (isPush && 'repo-push') || + (isFork && 'repo-forked') || + 'repo' - - - {repository} -  {owner} - + return ( + + + + + + + + + {' '} + {repositoryName} + + {` ${ownerName}`} + + + + - -) + ) +} export default RepositoryRow diff --git a/src/components/cards/partials/rows/RowList.tsx b/src/components/cards/partials/rows/RowList.tsx new file mode 100644 index 00000000..238db9d5 --- /dev/null +++ b/src/components/cards/partials/rows/RowList.tsx @@ -0,0 +1,49 @@ +import React, { ReactNode, SFC } from 'react' +import { ScrollView } from 'react-native' + +import { contentPadding } from '../../../../styles/variables' +import { ITheme } from '../../../../types' +import TransparentTextOverlay from '../../../common/TransparentTextOverlay' + +export interface IProps { + data: any[] + maxHeight?: number + narrow?: boolean + renderItem: ({ item, index }: { item: any; index: number }) => ReactNode + theme: ITheme +} + +const RowList: SFC = ({ + data, + maxHeight = 200, + narrow, + renderItem, + theme, + ...props +}) => { + if (!(data && data.length > 0)) return null + + if (data.length === 1) return renderItem({ item: data[0], index: 0 }) + + return ( + + + {data.map((item, index) => renderItem({ item, index }))} + + + ) +} + +export default RowList diff --git a/src/components/cards/partials/rows/UserListRow.tsx b/src/components/cards/partials/rows/UserListRow.tsx new file mode 100644 index 00000000..444d56ce --- /dev/null +++ b/src/components/cards/partials/rows/UserListRow.tsx @@ -0,0 +1,35 @@ +import React from 'react' + +import RowList from './RowList' +import UserRow from './UserRow' + +import { IGitHubUser, ITheme } from '../../../../types' + +export interface IProps { + isRead?: boolean + maxHeight?: number + users: IGitHubUser[] + theme: ITheme +} + +export default class UserListRow extends React.PureComponent { + renderItem = ({ item: user }: { item: IGitHubUser }) => { + if (!(user && user.id && user.login)) return null + + return ( + + ) + } + + render() { + const { users, ...props } = this.props + + if (!(users && users.length > 0)) return null + + return + } +} diff --git a/src/components/cards/partials/rows/UserRow.tsx b/src/components/cards/partials/rows/UserRow.tsx index 741f1792..e22e991d 100644 --- a/src/components/cards/partials/rows/UserRow.tsx +++ b/src/components/cards/partials/rows/UserRow.tsx @@ -7,12 +7,13 @@ import { getUserPressHandler } from './helpers' import rowStyles from './styles' export interface IProps { + isRead?: boolean username: string } export interface IState {} -const UserRow: SFC = ({ username }) => ( +const UserRow: SFC = ({ isRead, username }) => ( @@ -23,7 +24,9 @@ const UserRow: SFC = ({ username }) => ( onPress={getUserPressHandler(username)} style={rowStyles.mainContentContainer} > - {username} + + {username} + diff --git a/src/components/cards/partials/rows/WikiPageListRow.tsx b/src/components/cards/partials/rows/WikiPageListRow.tsx new file mode 100644 index 00000000..087866d7 --- /dev/null +++ b/src/components/cards/partials/rows/WikiPageListRow.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +import RowList from './RowList' +import WikiPageRow from './WikiPageRow' + +import { IGitHubPage, ITheme } from '../../../../types' + +export interface IProps { + isRead?: boolean + maxHeight?: number + pages: IGitHubPage[] + theme: ITheme +} + +export default class WikiPageListRow extends React.PureComponent { + renderItem = ({ item: page }: { item: IGitHubPage }) => { + if (!(page && page.sha && page.title)) return null + + return ( + + ) + } + + render() { + const { pages, ...props } = this.props + + if (!(pages && pages.length > 0)) return null + + return + } +} diff --git a/src/components/cards/partials/rows/WikiPageRow.tsx b/src/components/cards/partials/rows/WikiPageRow.tsx new file mode 100644 index 00000000..d1f4e73a --- /dev/null +++ b/src/components/cards/partials/rows/WikiPageRow.tsx @@ -0,0 +1,44 @@ +import React, { SFC } from 'react' +import { Text, TouchableOpacity, View } from 'react-native' +import Icon from 'react-native-vector-icons/Octicons' + +import { trimNewLinesAndSpaces } from '../../../../utils/helpers/shared' +import cardStyles from '../../styles' +import { getGithubURLPressHandler } from './helpers' +import rowStyles from './styles' + +export interface IProps { + isRead?: boolean + name?: string + title: string + url: string +} + +export interface IState {} + +const WikiPageRow: SFC = ({ isRead, name, title: _title, url }) => { + const title = trimNewLinesAndSpaces(_title || name) + if (!title) return null + + return ( + + + + + + + {title} + + + + + ) +} + +export default WikiPageRow diff --git a/src/components/cards/partials/rows/helpers.ts b/src/components/cards/partials/rows/helpers.ts index a6166abb..16aeff43 100644 --- a/src/components/cards/partials/rows/helpers.ts +++ b/src/components/cards/partials/rows/helpers.ts @@ -1,17 +1,36 @@ import R from 'ramda' import SafariView from 'react-native-safari-view' -export const getUserPressHandler = R.memoize( - (username?: string) => - username - ? () => SafariView.show({ url: `https://github.com/${username}` }) +import { fixURL } from '../../../../utils/helpers/github/url' + +const baseURL = 'https://github.com' + +export const getGithubURLPressHandler = R.memoize((url?: string) => { + const fixedURL = fixURL(url) + return fixedURL ? () => SafariView.show({ url: fixedURL }) : undefined +}) as (url?: string) => () => void | undefined + +export const getBranchPressHandler = R.memoize( + (ownerName?: string, repositoryName?: string, branch?: string) => + ownerName && repositoryName && branch + ? getGithubURLPressHandler( + `${baseURL}/${ownerName}/${repositoryName}/tree/${branch}`, + ) : undefined, -) as (username?: string) => () => void | undefined +) as ( + ownerName?: string, + repositoryName?: string, + branch?: string, +) => () => void | undefined export const getRepositoryPressHandler = R.memoize( - (owner?: string, repository?: string) => - owner && repository - ? () => - SafariView.show({ url: `https://github.com/${owner}/${repository}` }) + (ownerName?: string, repositoryName?: string) => + ownerName && repositoryName + ? getGithubURLPressHandler(`${baseURL}/${ownerName}/${repositoryName}`) : undefined, -) as (owner?: string, repository?: string) => () => void | undefined +) as (owner?: string, repositoryName?: string) => () => void | undefined + +export const getUserPressHandler = R.memoize( + (username?: string) => + username ? getGithubURLPressHandler(`${baseURL}/${username}`) : undefined, +) as (username?: string) => () => void | undefined diff --git a/src/components/cards/partials/rows/styles.ts b/src/components/cards/partials/rows/styles.ts index 97323b4a..ad45e299 100644 --- a/src/components/cards/partials/rows/styles.ts +++ b/src/components/cards/partials/rows/styles.ts @@ -3,7 +3,7 @@ import { StyleSheet, TextStyle, ViewStyle } from 'react-native' import theme from '../../../../styles/themes/dark' import { contentPadding, - radius, + // radius, smallTextSize, } from '../../../../styles/variables' @@ -17,12 +17,12 @@ export default StyleSheet.create({ mainContentContainer: { alignItems: 'center', borderColor: theme.base01, - borderRadius: radius, + // borderRadius: radius, // borderWidth: 1, flexDirection: 'row', flexGrow: 1, // paddingHorizontal: contentPadding, - paddingVertical: contentPadding / 2, + // paddingVertical: contentPadding / 2, } as ViewStyle, repositoryText: { diff --git a/src/components/cards/styles.ts b/src/components/cards/styles.ts index e5b8c55f..df95cf5b 100644 --- a/src/components/cards/styles.ts +++ b/src/components/cards/styles.ts @@ -18,14 +18,19 @@ export default StyleSheet.create({ width: avatarSize, } as ViewStyle, - avatar: { - alignSelf: 'flex-end', - } as ImageStyle, + leftColumnAlignTop: { + alignSelf: 'flex-start', + } as ViewStyle, rightColumn: { flex: 1, + flexDirection: 'row', } as ViewStyle, + avatar: { + alignSelf: 'flex-end', + } as ImageStyle, + usernameText: { alignSelf: 'center', color: theme.base04, @@ -43,6 +48,14 @@ export default StyleSheet.create({ color: theme.base05, } as TextStyle, + normalText: { + color: theme.base04, + } as TextStyle, + + smallText: { + fontSize: smallTextSize, + } as TextStyle, + descriptionText: { color: theme.base05, lineHeight: 20, diff --git a/src/components/common/Avatar.tsx b/src/components/common/Avatar.tsx index d0e48fc7..719dabe6 100644 --- a/src/components/common/Avatar.tsx +++ b/src/components/common/Avatar.tsx @@ -12,11 +12,15 @@ import { getUserAvatarByEmail, getUserAvatarByUsername, } from '../../utils/helpers/github/shared' -import { getUserPressHandler } from '../cards/partials/rows/helpers' +import { + getGithubURLPressHandler, + getUserPressHandler, +} from '../cards/partials/rows/helpers' export interface IProps { avatarURL?: string email?: string + linkURL?: string size?: number small?: boolean style?: ImageStyle @@ -34,6 +38,7 @@ const styles = StyleSheet.create({ const Avatar: SFC = ({ avatarURL, email, + linkURL, size: _size, small, style, @@ -48,7 +53,13 @@ const Avatar: SFC = ({ if (!uri) return null return ( - + = ({ {...containerProps} > = ({ > {Boolean(isPrivate) && ( -  , + {' '} )} {children} diff --git a/src/components/common/TransparentTextOverlay.tsx b/src/components/common/TransparentTextOverlay.tsx new file mode 100644 index 00000000..32b8b9a2 --- /dev/null +++ b/src/components/common/TransparentTextOverlay.tsx @@ -0,0 +1,121 @@ +import React, { ReactNode, SFC } from 'react' +import { View, ViewStyle } from 'react-native' + +import LinearGradient from '../../libs/linear-gradient' +import { fade } from '../../utils/helpers/color' + +type From = 'top' | 'bottom' | 'left' | 'right' +type FromWithVH = 'vertical' | 'horizontal' | From + +export interface IProps { + children?: ReactNode + color: string + containerStyle?: ViewStyle + from: FromWithVH + radius?: number + size: number + style?: ViewStyle +} + +function getStyle(from: From, size: number): ViewStyle { + switch (from) { + case 'top': + return { + height: size, + left: 0, + position: 'absolute', + right: 0, + top: 0, + } + case 'bottom': + return { + bottom: 0, + height: size, + left: 0, + position: 'absolute', + right: 0, + } + case 'left': + return { + bottom: 0, + left: 0, + position: 'absolute', + top: 0, + width: size, + } + case 'right': + return { + bottom: 0, + position: 'absolute', + right: 0, + top: 0, + width: size, + } + default: + return {} + } +} + +function getProps(from: From, size: number) { + switch (from) { + case 'top': + return { start: { x: 0, y: 1 }, end: { x: 0, y: 0 }, height: size } + case 'bottom': + return { start: { x: 0, y: 0 }, end: { x: 0, y: 1 }, height: size } + case 'left': + return { start: { x: 1, y: 0 }, end: { x: 0, y: 0 }, width: size } + default: + return { start: { x: 0, y: 0 }, end: { x: 1, y: 0 }, width: size } + } +} + +const GradientLayerOverlay: SFC = ({ + color, + from, + radius, + size, + style, + ...props +}) => ( + +) + +const TransparentTextOverlay: SFC = ({ + children, + containerStyle, + from, + ...props +}) => { + return ( + + {children} + + {(from === 'vertical' || from === 'top') && ( + + )} + + {(from === 'vertical' || from === 'bottom') && ( + + )} + + {(from === 'horizontal' || from === 'left') && ( + + )} + + {(from === 'horizontal' || from === 'right') && ( + + )} + + ) +} + +export default TransparentTextOverlay diff --git a/src/containers/EventCardsContainer.tsx b/src/containers/EventCardsContainer.tsx index 6b8548cf..601478ef 100644 --- a/src/containers/EventCardsContainer.tsx +++ b/src/containers/EventCardsContainer.tsx @@ -96,7 +96,7 @@ const _data: IGitHubEvent[] = [ closed_at: null, author_association: 'CONTRIBUTOR', body: - '### Is this a bug report?\r\n\r\nYes. In the example `index.js` we have a line `let language = this.props.language...`. However, in `generate.js` and particularly `metadata.js` we are actually setting the permalink on the assumption that the language isn\'t set.\r\n\r\n```\r\nif (languages.length === 1 && !siteConfig.useEnglishUrl) {\r\n metadata.permalink = \'docs/\' + metadata.id + \'.html\';\r\n } else {\r\n metadata.permalink = \'docs/\' + language + \'/\' + metadata.id + \'.html\';\r\n }\r\n```\r\n\r\nBut in the code before this, we are actually setting this.props.language to `en` by default, I think. So we are conflicting.\r\n\r\nThere are a few ways I can fix this, but I need to come up with the best way.\r\n\r\n### Have you read the [Contributing Guidelines]\r\n\r\nYes, of course. I helped right them :)\r\n\r\n### Environment\r\n\r\nN/A\r\n\r\n### Steps to Reproduce\r\n\r\n1. `yarn global add docusaurus-init`\r\n2. `docusaurus-init`\r\n3. `mv docs-examples-from-docusaurus docs` && `mv website/blog-examples-from-docusaurus website/blog`\r\n4. `cd website`\r\n5. `yarn run start`\r\n6. Go to http://localhost:3000\r\n7. Click on the `Example Link` Button\r\n8. See 404.\r\n\r\n### Expected Behavior\r\n\r\nThe button links should go to an actual docs page.\r\n\r\n### Actual Behavior\r\n\r\nThe button links go to a 404-ish page.\r\n\r\nscreenshot 2017-12-17 15 53 41\r\n\r\nscreenshot 2017-12-17 15 54 00\r\n\r\n### Reproducible Demo\r\n\r\nRun the steps above.\r\n', + '### Is this a bug report?\r\n\r\nYes. In the example `index.js` we have a line `let language = this.props.language...`. However, in `generate.js` and particularly `metadata.js` we are actually setting the permalink on the assumption that the language isn\'t set.\r\n\r\n```\r\nif (languages.length === 1 && !siteConfig.useEnglishUrl) {\r\n metadata.permalink = \'docs/\' + metadata.id + \'.html\';\r\n } else {\r\n metadata.permalink = \'docs/\' + language + \'/\' + metadata.id + \'.html\';\r\n }\r\n```\r\n\r\nBut in the code before this, we are actually setting this.props.language to `en` by default, I think. So we are conflicting.\r\n\r\nThere are a few ways I can fix this, but I need to come up with the best way.\r\n\r\n### Have you isRead the [Contributing Guidelines]\r\n\r\nYes, of course. I helped right them :)\r\n\r\n### Environment\r\n\r\nN/A\r\n\r\n### Steps to Reproduce\r\n\r\n1. `yarn global add docusaurus-init`\r\n2. `docusaurus-init`\r\n3. `mv docs-examples-from-docusaurus docs` && `mv website/blog-examples-from-docusaurus website/blog`\r\n4. `cd website`\r\n5. `yarn run start`\r\n6. Go to http://localhost:3000\r\n7. Click on the `Example Link` Button\r\n8. See 404.\r\n\r\n### Expected Behavior\r\n\r\nThe button links should go to an actual docs page.\r\n\r\n### Actual Behavior\r\n\r\nThe button links go to a 404-ish page.\r\n\r\nscreenshot 2017-12-17 15 53 41\r\n\r\nscreenshot 2017-12-17 15 54 00\r\n\r\n### Reproducible Demo\r\n\r\nRun the steps above.\r\n', }, comment: { url: @@ -148,83 +148,83 @@ const _data: IGitHubEvent[] = [ avatar_url: 'https://avatars.githubusercontent.com/u/69631?', }, }, - { - id: '6999953726', - type: 'PushEvent', - actor: { - id: 3757713, - login: 'JoelMarcey', - display_login: 'JoelMarcey', - gravatar_id: '', - url: 'https://api.github.com/users/JoelMarcey', - avatar_url: 'https://avatars.githubusercontent.com/u/3757713?', - }, - repo: { - id: 94911145, - name: 'facebook/Docusaurus', - url: 'https://api.github.com/repos/facebook/Docusaurus', - }, - payload: { - push_id: 2201568216, - size: 1, - distinct_size: 1, - ref: 'refs/heads/gh-pages', - head: '043929ac2164d24dbe0b87fed506c67cfa0a5846', - before: '6b3d635cc6cdde1a84e4ce9bfc2b3fe95f866f43', - commits: [ - { - sha: '043929ac2164d24dbe0b87fed506c67cfa0a5846', - author: { - email: 'docusaurus@users.noreply.github.com', - name: 'Website Deployment Script', - }, - message: - 'Deploy website\n\nDeploy website version based on 6b3d635cc6cdde1a84e4ce9bfc2b3fe95f866f43', - distinct: true, - url: - 'https://api.github.com/repos/facebook/Docusaurus/commits/043929ac2164d24dbe0b87fed506c67cfa0a5846', - }, - ], - }, - public: true, - created_at: '2017-12-18T00:36:40Z', - org: { - id: 69631, - login: 'facebook', - gravatar_id: '', - url: 'https://api.github.com/orgs/facebook', - avatar_url: 'https://avatars.githubusercontent.com/u/69631?', - }, - }, - { - id: '6999952659', - type: 'WatchEvent', - actor: { - id: 16931088, - login: 'wagaman', - display_login: 'wagaman', - gravatar_id: '', - url: 'https://api.github.com/users/wagaman', - avatar_url: 'https://avatars.githubusercontent.com/u/16931088?', - }, - repo: { - id: 29028775, - name: 'facebook/react-native', - url: 'https://api.github.com/repos/facebook/react-native', - }, - payload: { - action: 'started', - }, - public: true, - created_at: '2017-12-18T00:35:54Z', - org: { - id: 69631, - login: 'facebook', - gravatar_id: '', - url: 'https://api.github.com/orgs/facebook', - avatar_url: 'https://avatars.githubusercontent.com/u/69631?', - }, - }, + // { + // id: '6999953726', + // type: 'PushEvent', + // actor: { + // id: 3757713, + // login: 'JoelMarcey', + // display_login: 'JoelMarcey', + // gravatar_id: '', + // url: 'https://api.github.com/users/JoelMarcey', + // avatar_url: 'https://avatars.githubusercontent.com/u/3757713?', + // }, + // repo: { + // id: 94911145, + // name: 'facebook/Docusaurus', + // url: 'https://api.github.com/repos/facebook/Docusaurus', + // }, + // payload: { + // push_id: 2201568216, + // size: 1, + // distinct_size: 1, + // ref: 'refs/heads/gh-pages', + // head: '043929ac2164d24dbe0b87fed506c67cfa0a5846', + // before: '6b3d635cc6cdde1a84e4ce9bfc2b3fe95f866f43', + // commits: [ + // { + // sha: '043929ac2164d24dbe0b87fed506c67cfa0a5846', + // author: { + // email: 'docusaurus@users.noreply.github.com', + // name: 'Website Deployment Script', + // }, + // message: + // 'Deploy website\n\nDeploy website version based on 6b3d635cc6cdde1a84e4ce9bfc2b3fe95f866f43', + // distinct: true, + // url: + // 'https://api.github.com/repos/facebook/Docusaurus/commits/043929ac2164d24dbe0b87fed506c67cfa0a5846', + // }, + // ], + // }, + // public: true, + // created_at: '2017-12-18T00:36:40Z', + // org: { + // id: 69631, + // login: 'facebook', + // gravatar_id: '', + // url: 'https://api.github.com/orgs/facebook', + // avatar_url: 'https://avatars.githubusercontent.com/u/69631?', + // }, + // }, + // { + // id: '6999952659', + // type: 'WatchEvent', + // actor: { + // id: 16931088, + // login: 'wagaman', + // display_login: 'wagaman', + // gravatar_id: '', + // url: 'https://api.github.com/users/wagaman', + // avatar_url: 'https://avatars.githubusercontent.com/u/16931088?', + // }, + // repo: { + // id: 29028775, + // name: 'facebook/react-native', + // url: 'https://api.github.com/repos/facebook/react-native', + // }, + // payload: { + // action: 'started', + // }, + // public: true, + // created_at: '2017-12-18T00:35:54Z', + // org: { + // id: 69631, + // login: 'facebook', + // gravatar_id: '', + // url: 'https://api.github.com/orgs/facebook', + // avatar_url: 'https://avatars.githubusercontent.com/u/69631?', + // }, + // }, { id: '6999952360', type: 'PushEvent', diff --git a/src/libs/linear-gradient/index.tsx b/src/libs/linear-gradient/index.tsx new file mode 100644 index 00000000..5c2dab99 --- /dev/null +++ b/src/libs/linear-gradient/index.tsx @@ -0,0 +1,4 @@ +import LinearGradient from 'react-native-linear-gradient' + +export * from 'react-native-linear-gradient' +export default LinearGradient diff --git a/src/libs/linear-gradient/index.web.tsx b/src/libs/linear-gradient/index.web.tsx new file mode 100644 index 00000000..56e5193b --- /dev/null +++ b/src/libs/linear-gradient/index.web.tsx @@ -0,0 +1,30 @@ +import React, { SFC } from 'react' +import { PointProperties, View, ViewStyle } from 'react-native' + +export interface IProps { + colors: string[] + end: PointProperties + height: number + locations: string[] + start: PointProperties + style?: ViewStyle + width: number +} + +const radToDeg = (angle: number) => angle * (180 / Math.PI) +const pointsToAngle = (p1: PointProperties, p2: PointProperties) => + radToDeg(Math.atan2(p2.y - p1.y, p2.x - p1.x)) + 90 + +const propsToDeg = ({ start, end }: IProps) => `${pointsToAngle(start, end)}deg` + +const propsToLinearGradient = (props: IProps) => + `linear-gradient(${propsToDeg(props)}, ${props.colors.join(', ')})` + +const LinearGradient: SFC = props => ( + +) + +export default LinearGradient diff --git a/src/libs/swipeable/AppleSwipeableRow.tsx b/src/libs/swipeable/AppleSwipeableRow.tsx index b2a3f158..22147cf0 100644 --- a/src/libs/swipeable/AppleSwipeableRow.tsx +++ b/src/libs/swipeable/AppleSwipeableRow.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Animated, StyleSheet, Text } from 'react-native' +import { Animated, StyleSheet, Text, TextStyle, ViewStyle } from 'react-native' import { RectButton, Swipeable } from 'react-native-gesture-handler' import BaseSwipeableRow, { @@ -137,13 +137,14 @@ export default class AppleSwipeableRow extends BaseSwipeableRow { } const styles = StyleSheet.create({ + baseActionContainer: { + flex: 1, + justifyContent: 'center', + } as ViewStyle, + actionText: { backgroundColor: 'transparent', fontSize: 16, padding: 10, - }, - baseActionContainer: { - flex: 1, - justifyContent: 'center', - }, + } as TextStyle, }) diff --git a/src/libs/swipeable/GoogleSwipeableRow.tsx b/src/libs/swipeable/GoogleSwipeableRow.tsx index e800ea65..b9ffe4b0 100644 --- a/src/libs/swipeable/GoogleSwipeableRow.tsx +++ b/src/libs/swipeable/GoogleSwipeableRow.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Animated, StyleSheet } from 'react-native' +import { Animated, StyleSheet, TextStyle, ViewStyle } from 'react-native' import { RectButton, Swipeable } from 'react-native-gesture-handler' import Icon from 'react-native-vector-icons/MaterialIcons' @@ -136,13 +136,14 @@ export default class GoogleSwipeableRow extends BaseSwipeableRow { } const styles = StyleSheet.create({ + baseActionContainer: { + flex: 1, + justifyContent: 'center', + } as TextStyle, + actionIcon: { backgroundColor: 'transparent', marginHorizontal: 10, width: 30, - }, - baseActionContainer: { - flex: 1, - justifyContent: 'center', - }, + } as ViewStyle, }) diff --git a/src/setup.ts b/src/setup.ts index ffbb1d39..851b0644 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -3,12 +3,12 @@ import React from 'react' import { init, startMainApp } from './screens' export async function setup() { - if (__DEV__) { - const { whyDidYouUpdate } = require('why-did-you-update') - whyDidYouUpdate(React, { - exclude: /[^a-zA-Z0-9]|CellRenderer|Icon|Swipeable/, - }) - } + // if (__DEV__) { + // const { whyDidYouUpdate } = require('why-did-you-update') + // whyDidYouUpdate(React, { + // exclude: /[^a-zA-Z0-9]|CellRenderer|Icon|Swipeable/, + // }) + // } await init() diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 9f9f28f9..2e53a52e 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -1,6 +1,10 @@ import { StyleSheet, ViewStyle } from 'react-native' export default StyleSheet.create({ + full: { + flex: 1, + } as ViewStyle, + horizontal: { flexDirection: 'row', } as ViewStyle, diff --git a/src/styles/themes/dark-blue.ts b/src/styles/themes/dark-blue.ts index a842b8ba..a5ad5da5 100755 --- a/src/styles/themes/dark-blue.ts +++ b/src/styles/themes/dark-blue.ts @@ -1,5 +1,6 @@ import Platform from '../../libs/platform' +import { ITheme } from '../../types' import * as base from './base' export const base00 = '#141c26' // page background @@ -57,4 +58,4 @@ export default { invert: () => require('./light').default, // tslint:disable-line isDark: true, name: 'dark-blue', -} +} as ITheme diff --git a/src/styles/themes/dark.ts b/src/styles/themes/dark.ts index 28507fb2..8583f74a 100755 --- a/src/styles/themes/dark.ts +++ b/src/styles/themes/dark.ts @@ -1,5 +1,6 @@ import { Platform } from 'react-native' +import { ITheme } from '../../types' import { fade } from '../../utils/helpers/color' import { mutedOpacity } from '../variables' import * as base from './base' @@ -59,4 +60,4 @@ export default { invert: () => require('./light').default, // tslint:disable-line isDark: true, name: 'dark', -} +} as ITheme diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index bfc9a38c..4e792164 100755 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,5 +1,6 @@ import { Platform } from 'react-native' +import { ITheme } from '../../types' import { lighten } from '../../utils/helpers/color' import { mutedOpacity } from '../variables' import * as base from './base' @@ -56,4 +57,4 @@ export default { invert: () => require('./dark').default, // tslint:disable-line isDark: false, name: 'light', -} +} as ITheme diff --git a/src/types/github.ts b/src/types/github.ts index 3aac30ef..156d66f4 100644 --- a/src/types/github.ts +++ b/src/types/github.ts @@ -137,6 +137,7 @@ export interface IGitHubRepo { name: string full_name?: string url: string // https://api.github.com/repos/facebook/react + html_url: string // https://github.com/facebook/react } export interface IGitHubPage { @@ -145,6 +146,23 @@ export interface IGitHubPage { sha: string title: string html_url: string + url: string +} + +export interface IGitHubRelease { + id: number // 1 + tag_name: string // "v1.0.0" + target_commitish: string // "master" + name: string // "v1.0.0" + body: string // "Description of the release" + draft: boolean + prerelease: boolean + created_at: string // "2013-02-27T19:35:32Z" + published_at: string // "2013-02-27T19:35:32Z" + author: IGitHubUser + assets: any[] // see https://developer.github.com/v3/repos/releases/#get-a-single-release + url: string // "https://api.github.com/repos/octocat/Hello-World/releases/1" + html_url: string // "https://github.com/octocat/Hello-World/releases/v1.0.0" } /** @@ -283,6 +301,8 @@ export interface IIssuesEvent { assignee?: IGitHubUser | null // The optional user who was assigned or unassigned from the issue. label?: IGitHubLabel // The optional label that was added or removed from the issue. } + url: string + html_url: string } /** @@ -401,6 +421,7 @@ export interface IPushEvent { commits: IGitHubCommit[] } public?: boolean + forced?: boolean } /** @@ -414,7 +435,7 @@ export interface IReleaseEvent { repo: IGitHubRepo payload: { action: 'published' - release: object // https://developer.github.com/v3/repos/releases/#get-a-single-release + release: IGitHubRelease // https://developer.github.com/v3/repos/releases/#get-a-single-release } } diff --git a/src/types/index.ts b/src/types/index.ts index 2644a32d..fff162c4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,6 +2,25 @@ export * from './github' export type Theme = '' | 'auto' | 'light' | 'dark' | 'dark-blue' +export interface IBase16 { + base00: string + base01: string + base02: string + base03: string + base04: string + base05: string + base06: string | undefined + base07: string + base08: string + base09: string | undefined + base0A: string | undefined + base0B: string | undefined + base0C: string | undefined + base0D: string | undefined + base0E: string | undefined + base0F: string | undefined +} + export interface IBaseTheme { blue: string blueGray: string @@ -23,21 +42,11 @@ export interface IBaseTheme { yellow: string } -export interface ITheme extends IBaseTheme { - base00: string - base01: string - base02: string - base03: string - base04: string - base05: string - base06: string | undefined - base07: string - base08: string - base09: string | undefined - base0A: string | undefined - base0B: string | undefined - base0C: string | undefined - base0D: string | undefined - base0E: string | undefined - base0F: string | undefined +export interface ITheme extends IBaseTheme, IBase16 { + cardBackground: string + invert: () => string + isDark: boolean + name: Theme + statusBarBackground: string + tabBarBackground: string } diff --git a/src/utils/helpers/github/shared.ts b/src/utils/helpers/github/shared.ts index 63f5c849..6c38f203 100644 --- a/src/utils/helpers/github/shared.ts +++ b/src/utils/helpers/github/shared.ts @@ -20,7 +20,7 @@ export function getUserAvatarByUsername( : '' } -export function tryGetUsernameFromGitHubEmail(email: string) { +export function tryGetUsernameFromGitHubEmail(email?: string) { if (!email) return '' const emailSplit = email.split('@') @@ -54,6 +54,8 @@ export function isPullRequest(issue: IGitHubIssue | IGitHubPullRequest) { export function getOwnerAndRepo( repoFullName: string, ): { owner: string | undefined; repo: string | undefined } { + if (!repoFullName) return { owner: '', repo: '' } + const repoSplitedNames = (repoFullName || '') .trim() .split('/') diff --git a/src/utils/helpers/github/url.ts b/src/utils/helpers/github/url.ts new file mode 100644 index 00000000..73613d16 --- /dev/null +++ b/src/utils/helpers/github/url.ts @@ -0,0 +1,132 @@ +import { IGitHubRepo } from '../../../types' + +export const baseURL = 'https://github.com' + +export function getCommentIdFromUrl(url: string) { + if (!url) return null + + const matches = url.match(/\/comments\/([0-9]+)([?].+)?$/) + return (matches && matches[1]) || null +} + +export function getCommitShaFromUrl(url: string) { + if (!url) return null + + const matches = url.match(/\/commits\/([a-zA-Z0-9]+)([?].+)?$/) + return (matches && matches[1]) || null +} + +export function getIssueOrPullRequestNumberFromUrl(url: string) { + if (!url) return null + + const matches = url.match(/\/(issues|pulls)\/([0-9]+)([?].+)?$/) + const n = matches && matches[2] + + return (n && parseInt(n, 10)) || null +} + +export function getReleaseIdFromUrl(url: string) { + if (!url) return null + + const matches = url.match(/\/releases\/([0-9]+)([?].+)?$/) + return (matches && matches[1]) || null +} + +/* eslint-disable-next-line no-useless-escape */ +export const getRepoFullNameFromUrl = (url: string): string => + url + ? (url.match( + /(github.com\/(repos\/)?)([a-zA-Z0-9\-._]+\/[a-zA-Z0-9\-._]+[^/#$]?)/i, + ) || [])[3] || '' + : '' + +export const getRepoFullNameFromObject = (repo: IGitHubRepo): string => + (repo && + (repo.full_name || + repo.name || + getRepoFullNameFromUrl(repo.html_url || repo.url))) || + '' + +export const getGitHubURLForUser = (user: string) => + user ? `${baseURL}/${user}` : '' + +const objToQueryParams = (obj: { [key: string]: string | number }) => + Object.keys(obj) + .map(key => `${key}=${obj[key]}`) + .join('&') + +export const getGitHubSearchURL = (queryParams: { + [key: string]: string | number +}) => (queryParams ? `${baseURL}/search?${objToQueryParams(queryParams)}` : '') + +export const getGitHubURLForBranch = (repoFullName: string, branch: string) => + repoFullName && branch ? `${baseURL}/${repoFullName}/tree/${branch}` : '' + +export function githubHTMLUrlFromAPIUrl( + apiURL: string, + { n }: { n?: number } = {}, +): string { + if (!apiURL) return '' + + const [, type, restOfURL] = apiURL.match( + 'api.github.com/([a-zA-Z]+)/(.*)', + ) as string[] + if (!(type && restOfURL)) return '' + + if (type === 'repos') { + const repoFullName = getRepoFullNameFromUrl(apiURL) + const [type2, ...restOfURL2] = ( + apiURL.split(`/repos/${repoFullName}/`)[1] || '' + ).split('/') + + if (restOfURL2[0]) { + switch (type2) { + case 'commits': + return `${baseURL}/${repoFullName}/commit/${restOfURL2.join('/')}` + + case 'issues': + if (restOfURL2[0] === 'comments' && restOfURL2[1]) { + return n + ? `${baseURL}/${repoFullName}/pull/${n}/comments#issuecomment-${ + restOfURL2[1] + }` + : '' + } + + return `${baseURL}/${repoFullName}/issues/${restOfURL2.join('/')}` + + case 'pulls': + if (restOfURL2[0] === 'comments' && restOfURL2[1]) { + return n + ? `${baseURL}/${repoFullName}/pull/${n}/comments#discussion_r${ + restOfURL2[1] + }` + : '' + } + + return `${baseURL}/${repoFullName}/pull/${restOfURL2.join('/')}` + + case 'releases': + // it wont go directly to the release, but to the generic releases page. + // we would need to have the tag name to do that. + return `${baseURL}/${repoFullName}/releases/?${restOfURL2.join('/')}` + + default: + return `${baseURL}/${restOfURL}` + } + } + } + + return `${baseURL}/${restOfURL}` +} + +export function fixURL(url?: string) { + if (!url) return + + // sometimes the url come like this: '/facebook/react', so we add https://github.com + let uri = + url[0] === '/' && url.indexOf('github.com') < 0 ? `${baseURL}${url}` : url + uri = uri.indexOf('api.github.com') >= 0 ? githubHTMLUrlFromAPIUrl(uri) : uri + + return uri +} diff --git a/src/utils/helpers/shared.ts b/src/utils/helpers/shared.ts index 2deb165c..be631df2 100644 --- a/src/utils/helpers/shared.ts +++ b/src/utils/helpers/shared.ts @@ -18,3 +18,14 @@ export function getSteppedSize(size?: number, sizeSteps = 50) { export function randomBetween(minNumber: number, maxNumber: number) { return Math.floor(Math.random() * maxNumber) + minNumber } + +export function trimNewLinesAndSpaces(text?: string, maxLength: number = 100) { + if (!text || typeof text !== 'string') return '' + + let newText = text.replace(/\s+/g, ' ').trim() + if (maxLength > 0 && newText.length > maxLength) { + newText = `${newText.substr(0, maxLength).trim()}...` + } + + return newText +} diff --git a/yarn.lock b/yarn.lock index 6fbe8e57..60515705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4153,6 +4153,12 @@ react-native-gesture-handler@^1.0.0-alpha.37: invariant "^2.2.2" prop-types "^15.5.10" +react-native-linear-gradient@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.4.0.tgz#51d8ea12bb72a59bede9edc87b694b16b64cf435" + dependencies: + prop-types "^15.5.10" + react-native-navigation@^1.1.334: version "1.1.334" resolved "https://registry.yarnpkg.com/react-native-navigation/-/react-native-navigation-1.1.334.tgz#255cfe35ea5d7e6edd9d3583dd7a65b9d87736bd"