Skip to content

Commit 87316c6

Browse files
Merge pull request #66 from ekonstantinidis/mark-repo-as-read
Mark Repository as Read
2 parents efff9de + 009afa8 commit 87316c6

File tree

6 files changed

+191
-27
lines changed

6 files changed

+191
-27
lines changed

src/js/__tests__/components/notification.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ describe('Test for Notification Component', function () {
6868
notification={notification}
6969
key={notification.id} />);
7070

71-
expect(instance.state.readClass).toBe('row notification');
72-
expect(instance.state.read).toBeFalsy();
71+
expect(instance.state.isRead).toBeFalsy();
7372
expect(instance.openBrowser).toBeDefined();
7473
expect(instance.markAsRead).toBeDefined();
7574

@@ -99,8 +98,7 @@ describe('Test for Notification Component', function () {
9998
notification={notification}
10099
key={notification.id} />);
101100

102-
expect(instance.state.readClass).toBe('row notification');
103-
expect(instance.state.read).toBeFalsy();
101+
expect(instance.state.isRead).toBeFalsy();
104102
expect(instance.openBrowser).toBeDefined();
105103
expect(instance.markAsRead).toBeDefined();
106104

@@ -127,8 +125,7 @@ describe('Test for Notification Component', function () {
127125
notification={notification}
128126
key={notification.id} />);
129127

130-
expect(instance.state.readClass).toBe('row notification');
131-
expect(instance.state.read).toBeFalsy();
128+
expect(instance.state.isRead).toBeFalsy();
132129
expect(instance.openBrowser).toBeDefined();
133130
expect(instance.markAsRead).toBeDefined();
134131

src/js/__tests__/components/repository.js

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var TestUtils = React.addons.TestUtils;
1212

1313
describe('Test for Repository Component', function () {
1414

15-
var Actions, Repository;
15+
var apiRequests, Actions, Repository;
1616

1717
beforeEach(function () {
1818
// Mock Electron's window.require
@@ -37,6 +37,7 @@ describe('Test for Repository Component', function () {
3737
}
3838
};
3939

40+
apiRequests = require('../../utils/api-requests.js');
4041
Actions = require('../../actions/actions.js');
4142
Repository = require('../../components/repository.js');
4243
});
@@ -63,8 +64,10 @@ describe('Test for Repository Component', function () {
6364
);
6465

6566
expect(instance.props.repo[0].repository.full_name).toBe('ekonstantinidis/gitify');
67+
expect(instance.isRead).toBeFalsy();
6668
expect(instance.getAvatar).toBeDefined();
6769
expect(instance.openBrowser).toBeDefined();
70+
expect(instance.markRepoAsRead).toBeDefined();
6871

6972
// Get Avatar
7073
var avatar = instance.getAvatar();
@@ -75,4 +78,80 @@ describe('Test for Repository Component', function () {
7578

7679
});
7780

81+
it('Should mark a repo as read - successfully', function () {
82+
83+
var repoDetails = [{
84+
'repository': {
85+
'name': 'gitify',
86+
'full_name': 'ekonstantinidis/gitify',
87+
'owner': {
88+
'login': 'ekonstantinidis',
89+
'avatar_url': 'http://avatar.url'
90+
}
91+
},
92+
'subject': {
93+
'type': 'Issue'
94+
}
95+
}];
96+
97+
var instance = TestUtils.renderIntoDocument(
98+
<Repository
99+
repo={repoDetails}
100+
repoName='ekonstantinidis/gitify'
101+
key='ekonstantinidis/gitify' />
102+
);
103+
104+
expect(instance.state.isRead).toBeFalsy();
105+
expect(instance.markRepoAsRead).toBeDefined();
106+
107+
var superagent = require('superagent');
108+
superagent.__setResponse(200, 'ok', {}, false);
109+
110+
// Mark Repo as Read
111+
instance.markRepoAsRead();
112+
jest.runAllTimers();
113+
114+
expect(instance.state.isRead).toBeTruthy();
115+
116+
});
117+
118+
it('Should mark a repo as read - fail', function () {
119+
120+
var repoDetails = [{
121+
'repository': {
122+
'name': 'gitify',
123+
'full_name': 'ekonstantinidis/gitify',
124+
'owner': {
125+
'login': 'ekonstantinidis',
126+
'avatar_url': 'http://avatar.url'
127+
}
128+
},
129+
'subject': {
130+
'type': 'Issue'
131+
}
132+
}];
133+
134+
var instance = TestUtils.renderIntoDocument(
135+
<Repository
136+
repo={repoDetails}
137+
repoName='ekonstantinidis/gitify'
138+
key='ekonstantinidis/gitify' />
139+
);
140+
141+
expect(instance.state.isRead).toBeFalsy();
142+
expect(instance.state.errors).toBeFalsy();
143+
expect(instance.markRepoAsRead).toBeDefined();
144+
145+
var superagent = require('superagent');
146+
superagent.__setResponse(400, false);
147+
148+
// Mark Repo as Read
149+
instance.markRepoAsRead();
150+
jest.runAllTimers();
151+
152+
expect(instance.state.isRead).toBeFalsy();
153+
expect(instance.state.errors).toBeTruthy();
154+
155+
});
156+
78157
});

src/js/components/notification.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@ var NotificationItem = React.createClass({
88

99
getInitialState: function () {
1010
return {
11-
readClass: 'row notification',
12-
read: false
11+
isRead: this.props.isRead
1312
};
1413
},
1514

15+
componentWillReceiveProps: function (nextProps) {
16+
this.setState({
17+
isRead: nextProps.isRead
18+
});
19+
},
20+
1621
openBrowser: function () {
1722
var url = this.props.notification.subject.url.replace('api.github.com/repos', 'www.github.com');
1823
if (url.indexOf('/pulls/') != -1) {
@@ -30,8 +35,7 @@ var NotificationItem = React.createClass({
3035
if (response && response.ok) {
3136
// Notification Read
3237
self.setState({
33-
readClass: self.state.readClass + ' read',
34-
read: true
38+
isRead: true
3539
});
3640
} else {
3741
// Error - Show messages.
@@ -54,7 +58,7 @@ var NotificationItem = React.createClass({
5458
}
5559

5660
return (
57-
<div className={this.state.readClass}>
61+
<div className={this.state.isRead ? 'row notification read' : 'row notification'}>
5862
<div className='col-xs-1'><span className={typeIconClass} /></div>
5963
<div className='col-xs-10 subject' onClick={this.openBrowser}>
6064
{this.props.notification.subject.title}

src/js/components/repository.js

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@ var remote = window.require('remote');
33
var shell = remote.require('shell');
44

55
var SingleNotification = require('../components/notification');
6+
var apiRequests = require('../utils/api-requests');
67

78
var Repository = React.createClass({
89

10+
getInitialState: function () {
11+
return {
12+
isRead: false,
13+
errors: false
14+
};
15+
},
16+
917
getAvatar: function () {
1018
return this.props.repo[0].repository.owner.avatar_url;
1119
},
@@ -15,7 +23,32 @@ var Repository = React.createClass({
1523
shell.openExternal(url);
1624
},
1725

26+
markRepoAsRead: function () {
27+
var self = this;
28+
var loginId = this.props.repo[0].repository.owner.login;
29+
var repoId = this.props.repo[0].repository.name;
30+
31+
apiRequests
32+
.putAuth('https://api.github.com/repos/' + loginId + '/' + repoId + '/notifications', {})
33+
.end(function (err, response) {
34+
if (response && response.ok) {
35+
// Notification Read
36+
self.setState({
37+
isRead: true,
38+
errors: false
39+
});
40+
} else {
41+
self.setState({
42+
isRead: false,
43+
errors: true
44+
});
45+
}
46+
});
47+
48+
},
49+
1850
render: function () {
51+
var self = this;
1952
var organisationName, repositoryName;
2053

2154
if (typeof this.props.repoName === 'string') {
@@ -26,16 +59,25 @@ var Repository = React.createClass({
2659

2760
return (
2861
<div>
29-
<div className='row repository'>
62+
<div className={this.state.isRead ? 'row repository read' : 'row repository'}>
3063
<div className='col-xs-2'><img className='avatar' src={this.getAvatar()} /></div>
31-
<div className='col-xs-10 name' onClick={this.openBrowser}>
64+
<div className='col-xs-9 name' onClick={this.openBrowser}>
3265
<span>{'/' + repositoryName}</span>
3366
<span>{organisationName}</span>
3467
</div>
68+
<div className='col-xs-1 check-wrapper'>
69+
<span className='octicon octicon-check' onClick={this.markRepoAsRead} />
70+
</div>
3571
</div>
3672

73+
{this.state.errors ?
74+
<div className="alert alert-danger">
75+
<strong>Oops!</strong> We couldn't mark this repo as read.
76+
</div> : null
77+
}
78+
3779
{this.props.repo.map(function (obj) {
38-
return <SingleNotification notification={obj} key={obj.id} />;
80+
return <SingleNotification isRead={self.state.isRead} notification={obj} key={obj.id} />;
3981
})}
4082

4183
</div>

src/js/utils/api-requests.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ var apiRequests = {
2525
.set('User-Agent', 'Gitify');
2626
},
2727

28+
putAuth: function (url, params) {
29+
return request
30+
.put(url)
31+
.send(params)
32+
.set('Accept', 'application/vnd.github.v3+json')
33+
.set('Authorization', 'token ' + AuthStore.authStatus())
34+
.set('Cache-Control', 'no-cache')
35+
.set('User-Agent', 'Gitify');
36+
},
37+
2838
patchAuth: function (url, params) {
2939
return request
3040
.patch(url)

src/less/style.less

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
@White: #ffffff;
1818
@LightGray: #f5f5f5;
1919

20+
@ErrorColor: #DB423C;
21+
2022
/* @end Colors */
2123

2224
/* @group Typography */
@@ -98,12 +100,32 @@
98100
opacity: @opacity;
99101
}
100102

103+
.CheckOcticon() {
104+
&.octicon-check {
105+
-webkit-transition: all .4s;
106+
-moz-transition: all .4s;
107+
transition: all .4s;
108+
color: darken(@LightGray, 20%);
109+
110+
&:hover {
111+
color: @ThemeGreen;
112+
cursor: pointer;
113+
}
114+
}
115+
}
116+
101117
/* @end Mixins */
102118

103119

104120
/* @group Bootstrap Overrides */
105121

106122
@navbar-height: 35px;
123+
124+
@alert-padding: 8px;
125+
@alert-border-radius: 0;
126+
@alert-danger-bg: @ErrorColor;
127+
@alert-danger-text: @White;
128+
107129
@octicons-font-path: "../../build/fonts";
108130

109131
/* @end Bootstrap Overrides */
@@ -155,6 +177,12 @@ input {
155177
outline: none;
156178
}
157179

180+
.alert {
181+
border: 0;
182+
text-align: center;
183+
margin-bottom: 0;
184+
}
185+
158186
/* @end Misc */
159187

160188

@@ -287,6 +315,10 @@ input {
287315
margin: 0;
288316
background-color: @LightGray;
289317

318+
&.read {
319+
.Opacity(0.4);
320+
}
321+
290322
.col-xs-2,
291323
.col-xs-10 {
292324
padding: 0;
@@ -299,9 +331,10 @@ input {
299331
}
300332

301333
.name {
302-
font-size: 18px;
303334
.FontOpenSansSemibold();
335+
font-size: 18px;
304336
text-align: right;
337+
padding: 0 5px;
305338

306339
span {
307340
display: inline-block;
@@ -312,6 +345,15 @@ input {
312345
overflow: hidden;
313346
}
314347
}
348+
349+
.check-wrapper {
350+
padding: 0 5px;
351+
352+
.octicon {
353+
font-size: 22px;
354+
.CheckOcticon();
355+
}
356+
}
315357
}
316358

317359
/* @end Component / Repository */
@@ -376,17 +418,7 @@ input {
376418
.octicon {
377419
margin-right: 10px;
378420
font-size: 20px;
379-
380-
&.octicon-check {
381-
-webkit-transition: all .4s;
382-
-moz-transition: all .4s;
383-
transition: all .4s;
384-
385-
&:hover {
386-
color: @ThemeGreen;
387-
cursor: pointer;
388-
}
389-
}
421+
.CheckOcticon();
390422
}
391423

392424
&.read {

0 commit comments

Comments
 (0)