Skip to content

Commit

Permalink
Gmail column. closes #4
Browse files Browse the repository at this point in the history
  • Loading branch information
jariz committed Feb 26, 2015
1 parent 78b9027 commit 8d47a6f
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 5 deletions.
13 changes: 13 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Author: Jari Zwarts (@JariZwarts)
4 changes: 3 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"packery": "~1.3.2",
"draggabilly": "~1.1.1",
"fetch": "~0.7.0",
"momentjs": "~2.9.0"
"momentjs": "~2.9.0",
"google-signin": "~0.2.1",
"pleasejs": "~0.4.2"
}
}
5 changes: 4 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ gulp.task('libs', function() {
'bower_components/color-thief/src/color-thief.js',
'bower_components/draggabilly/dist/draggabilly.pkgd.js',
'bower_components/fetch/fetch.js',
'bower_components/momentjs/moment.js'
'bower_components/momentjs/moment.js',
'bower_components/pleasejs/src/Please.js'
])
.pipe(concat('libs.js'))
.pipe(gulp.dest('dist/js'))
Expand Down Expand Up @@ -129,4 +130,6 @@ gulp.task('watch', ['serve'], function () {
gulp.watch('src/**/*.html', ['html', 'reload']);

gulp.watch('src/**/*.coffee', ['coffee', 'reload']);

gulp.watch('src/manifest.json', ['copy']);
});
3 changes: 1 addition & 2 deletions src/coffee/FeedColumn.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,4 @@ class Columns.FeedColumn extends Columns.Column

if Object.keys(@cache).length
@draw @cache, holderElement
@refresh columnElement, holderElement
else @refresh columnElement, holderElement
@refresh columnElement, holderElement
135 changes: 135 additions & 0 deletions src/columns/gmail/Gmail.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
`
//util functions kindly stolen from https://github.com/ebidel/polymer-gmail/

function getValueForHeaderField(headers, field) {
for (var i = 0, header; header = headers[i]; ++i) {
if (header.name == field || header.name == field.toLowerCase()) {
return header.value;
}
}
return null;
}

function fixUpMessages(resp) {
var messages = resp.result.messages;

for (var j = 0, m; m = messages[j]; ++j) {
var headers = m.payload.headers;

// Example: Thu Sep 25 2014 14:43:18 GMT-0700 (PDT) -> Sept 25.
var date = new Date(getValueForHeaderField(headers, 'Date'));
m.date = date.toDateString().split(' ').slice(1, 3).join(' ');
m.to = getValueForHeaderField(headers, 'To');
m.subject = getValueForHeaderField(headers, 'Subject');

var fromHeaders = getValueForHeaderField(headers, 'From');
var fromHeaderMatches = fromHeaders.match(new RegExp(/"?(.*?)"?\s?<(.*)>/));

m.from = {};

// Use name if one was found. Otherwise, use email address.
if (fromHeaderMatches) {
// If no a name, use email address for displayName.
m.from.name = fromHeaderMatches[1].length ? fromHeaderMatches[1] :
fromHeaderMatches[2];
m.from.email = fromHeaderMatches[2];
} else {
m.from.name = fromHeaders.split('@')[0];
m.from.email = fromHeaders;
}
m.from.name = m.from.name.split('@')[0]; // Ensure email is split.

m.unread = m.labelIds.indexOf("UNREAD") != -1;
m.starred = m.labelIds.indexOf("STARRED") != -1;
}

return messages;
}
`

class Columns.Gmail extends Columns.Column
name: "Gmail"
thumb: "column-gmail.png"
element: "hn-item"

draw: (data, holderElement) =>
@loading = false
@refreshing = false

holderElement.innerHTML = ""

for item in data
child = document.createElement "gmail-item"
child.item = item
holderElement.appendChild child

render: (columnElement, holderElement) ->
super columnElement, holderElement

if Object.keys(@cache).length
@draw @cache, holderElement
@refresh columnElement, holderElement

refresh: (columnElement, holderElement) ->
if not @config.colors then @config.colors = {}

@refreshing = true
gapiEl = document.createElement "google-client-api"
columnElement.appendChild gapiEl

gapiEl.addEventListener "api-load", =>
console.info 'gapi loaded'
chrome.identity.getAuthToken
interactive: false
, (token) =>
if !chrome.runtime.lastError
gapi.auth.setToken
access_token: token,
duration: "52000",
state: "profile email https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/gmail.modify"

console.log "Auth token data", gapi.auth.getToken()
gapi.client.load "gmail", "v1", =>
console.info "gmail loaded"
gmail = gapi.client.gmail.users
gmail.threads.list
userId: 'me',
q: 'in:inbox'
.then (resp) =>
batch = gapi.client.newBatch()
resp.result.threads.forEach (thread) =>
req = gmail.threads.get
userId: 'me',
id: thread.id
batch.add req

batch.then (resp) =>
messages = []
for key, item of resp.result
fixed = fixUpMessages item
#grab first, add amount of messages in thread to message
message = fixed[0]
message.amount = fixed.length
#check if we already generated a color for this recipient, if not, generate it & save it
if not @config.colors[message.from.email] then message.color = @config.colors[message.from.email] = Please.make_color()[0]
else message.color = @config.colors[message.from.email]
messages.push message
#batch returns threads in a random order, so resort them based on their hex id's
messages = messages.sort (a, b) ->
ax = parseInt a.id, 16
bx = parseInt b.id, 16

if ax > bx then return -1
else return 1
@cache = messages
@draw messages, holderElement
else
@loading = false
@refreshing = false
auth = document.createElement "gmail-auth"
auth.addEventListener "sign-in", =>
@render columnElement, holderElement
holderElement.innerHTML = ""
holderElement.appendChild auth

tabbie.register "Gmail"
42 changes: 42 additions & 0 deletions src/columns/gmail/gmail-auth.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
<link rel="import" href="../../../bower_components/core-icon/core-icon.html">
<link rel="import" href="../../../bower_components/google-signin/google-icons.html">
<link rel="import" href="../../../bower_components/google-apis/google-apis.html">

<polymer-element name="gmail-auth" attributes="item">
<template>
<style>
div {
display:flex;
width:100%;
height:100%;
}

div paper-button {
margin:auto;
background:#DC4434;
color:#fff;
}

div paper-button core-icon {
margin-right:10px;
}
</style>
<div>
<paper-button on-click="{{signIn}}" raised><core-icon icon="google:google"></core-icon> Sign in with Google</paper-button>
</div>
</template>
<script>
Polymer({
signIn: function() {
var self = this;
chrome.identity.getAuthToken({
interactive:true
}, function(token) {
self.fire("sign-in", token)
console.log(self)
});
}
})
</script>
</polymer-element>
90 changes: 90 additions & 0 deletions src/columns/gmail/gmail-item.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/paper-ripple/paper-ripple.html">
<link rel="import" href="../../time-ago.html">

<polymer-element name="gmail-item" attributes="item" noscript>
<template>
<style>
:host {
position: relative;
width:100%;
}
.avatar {
border-radius: 50%;
background: gray;
color: #fff;
width: 30px;
height: 30px;
display: inline-block;
text-align: center;
padding-top: 4px;
position: absolute;
top:12px;
left:10px;
text-transform: uppercase;
}
* {
box-sizing: border-box;
text-decoration: none !important;
}
paper-ripple {
position: absolute;
top:0;
left:0;
width:100%;
height:100%;
}
a {
display: block;
padding: 10px 10px 10px 55px;
border-bottom: 1px solid #E0E0E0;
color:#000;
}
a.unread h1, a.unread h2 {
font-weight: bold;
}
h1 {
font-size:16px;
margin:0;
font-weight:normal;
}
h1 .amount {
color:#616161;
font-weight: normal;
}
h2 {
font-size:12px;
margin:0;
font-weight:normal;
}
p {
font-size:12px;
color:#616161;
margin: 6px 0 0;
}

p.date {
margin:0 0 0;
float:right;
}
</style>

<div class="avatar" style="background-color: {{item.color}}">
{{item.from.name[0]}}
</div>
<a target="_blank" href="https://mail.google.com/mail/u/0/#inbox/{{item.threadId}}" class="{{item.unread ? 'unread' : ''}}">
<paper-ripple></paper-ripple>
<p class="date">{{item.date}}</p>
<h1>
{{item.from.name}}
<template if="{{item.amount > 1}}">
<span class="amount">({{item.amount}})</span>
</template>
</h1>
<h2>{{item.subject}}</h2>
<p>
{{item.snippet}}
</p>
</a>
</template>
</polymer-element>
Binary file added src/img/column-gmail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@
"newtab": "tab.html"
},
"permissions": [
"identity",
"https://lobste.rs/*",
"https://api.behance.net/*"
],
"oauth2": {
"client_id": "2445668283-ovnfq4m0m03tmvkhh4rmj4qtu02l5tic.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/gmail.modify"
]
},
"manifest_version": 2,
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"content_security_policy": "script-src 'self' https://apis.google.com/ https://accounts.google.com https://oauth.googleusercontent.com https://ssl.gstatic.com 'unsafe-eval'; object-src 'self'",
"icons": {
"48": "img/icon_48.png",
"64": "img/icon_64.png",
Expand Down

0 comments on commit 8d47a6f

Please sign in to comment.