├── widget ├── .gitignore ├── GithubPulse │ ├── Resources │ │ ├── Images │ │ │ ├── icon.png │ │ │ ├── icon@2x.png │ │ │ ├── icon_dark.png │ │ │ ├── icon_dark@2x.png │ │ │ ├── icon_notification.png │ │ │ ├── icon_notification@2x.png │ │ │ ├── icon_notification_dark.png │ │ │ └── icon_notification_dark@2x.png │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── icon16.png │ │ │ │ ├── icon32.png │ │ │ │ ├── icon64.png │ │ │ │ ├── icon1024.png │ │ │ │ ├── icon128.png │ │ │ │ ├── icon256.png │ │ │ │ ├── icon32_2.png │ │ │ │ ├── icon512.png │ │ │ │ ├── icon256_2.png │ │ │ │ ├── icon512_2.png │ │ │ │ └── Contents.json │ │ └── Info.plist │ ├── Other Sources │ │ ├── BridgeHeader.h │ │ ├── GithubUpdate.swift │ │ └── AppDelegate.swift │ ├── Components │ │ └── CustomButton.swift │ ├── Views │ │ ├── ContentViewController.xib │ │ └── MainMenu.xib │ ├── Models │ │ └── Contributions.swift │ └── Controllers │ │ └── ContentViewController.swift ├── GithubPulse.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── Podfile ├── Makefile └── Podfile.lock ├── chrome_extension ├── front ├── images │ ├── blue │ │ ├── icon19.png │ │ └── icon38.png │ ├── red │ │ ├── icon19.png │ │ └── icon38.png │ └── icons │ │ ├── icon128.png │ │ ├── icon16.png │ │ ├── icon32.png │ │ └── icon48.png ├── manifest.json └── js │ ├── update.js │ └── utils.js ├── .jshintrc ├── resources ├── .gitignore ├── icon.png ├── Tile440x280.png ├── github_pulse.ai ├── screenshot1.png ├── screenshot2.png └── screenshot3.png ├── .gitignore ├── dist ├── GithubPulse.crx └── GithubPulse.zip ├── front ├── images │ ├── icon.png │ ├── sync.svg │ └── gear.svg ├── octicons │ ├── octicons.woff │ └── octicons.css ├── index.html ├── Makefile └── webpack.config.js ├── javascript ├── styles │ ├── GithubPulse.styl │ ├── ActivityGraph.styl │ ├── Stats.styl │ ├── Login.styl │ ├── Following.styl │ ├── main.styl │ ├── ProfileInfo.styl │ ├── Config.styl │ ├── Profile.styl │ └── UserLine.styl ├── fonts │ ├── open_sans │ │ ├── OpenSans-Bold.ttf │ │ └── OpenSans-Regular.ttf │ └── montserrat │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-Regular.ttf │ │ └── OFL.txt ├── main.jsx ├── components │ ├── GithubPulse.react.jsx │ ├── Routes.react.jsx │ ├── Stats.react.jsx │ ├── UserLine.react.jsx │ ├── ProfileInfo.react.jsx │ ├── ActivityGraph.react.jsx │ ├── Login.react.jsx │ ├── Config.react.jsx │ ├── Following.react.jsx │ └── Profile.react.jsx ├── github-api.js └── utils.js ├── .yabsrc ├── Makefile ├── package.json ├── LICENSE └── README.md /widget/.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | -------------------------------------------------------------------------------- /chrome_extension/front: -------------------------------------------------------------------------------- 1 | ../front -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /resources/.gitignore: -------------------------------------------------------------------------------- 1 | GithubPulse.pem 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | public 4 | xcuserdata 5 | *.xcworkspace 6 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/resources/icon.png -------------------------------------------------------------------------------- /dist/GithubPulse.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/dist/GithubPulse.crx -------------------------------------------------------------------------------- /dist/GithubPulse.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/dist/GithubPulse.zip -------------------------------------------------------------------------------- /front/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/front/images/icon.png -------------------------------------------------------------------------------- /resources/Tile440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/resources/Tile440x280.png -------------------------------------------------------------------------------- /resources/github_pulse.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/resources/github_pulse.ai -------------------------------------------------------------------------------- /resources/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/resources/screenshot1.png -------------------------------------------------------------------------------- /resources/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/resources/screenshot2.png -------------------------------------------------------------------------------- /resources/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/resources/screenshot3.png -------------------------------------------------------------------------------- /front/octicons/octicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/front/octicons/octicons.woff -------------------------------------------------------------------------------- /javascript/styles/GithubPulse.styl: -------------------------------------------------------------------------------- 1 | .github-pulse { 2 | position: relative; 3 | 4 | width: 400px; 5 | height: 384px; 6 | } 7 | -------------------------------------------------------------------------------- /chrome_extension/images/blue/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/blue/icon19.png -------------------------------------------------------------------------------- /chrome_extension/images/blue/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/blue/icon38.png -------------------------------------------------------------------------------- /chrome_extension/images/red/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/red/icon19.png -------------------------------------------------------------------------------- /chrome_extension/images/red/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/red/icon38.png -------------------------------------------------------------------------------- /chrome_extension/images/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/icons/icon128.png -------------------------------------------------------------------------------- /chrome_extension/images/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/icons/icon16.png -------------------------------------------------------------------------------- /chrome_extension/images/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/icons/icon32.png -------------------------------------------------------------------------------- /chrome_extension/images/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/chrome_extension/images/icons/icon48.png -------------------------------------------------------------------------------- /javascript/fonts/open_sans/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/javascript/fonts/open_sans/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon.png -------------------------------------------------------------------------------- /javascript/fonts/montserrat/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/javascript/fonts/montserrat/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /javascript/fonts/open_sans/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/javascript/fonts/open_sans/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon@2x.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon_dark.png -------------------------------------------------------------------------------- /javascript/fonts/montserrat/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/javascript/fonts/montserrat/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon_dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon_dark@2x.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon_notification.png -------------------------------------------------------------------------------- /javascript/main.jsx: -------------------------------------------------------------------------------- 1 | require('./styles/main'); 2 | require('./components/Routes.react'); 3 | 4 | document.body.addEventListener('contextmenu', e => e.preventDefault(), false); 5 | -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon_notification@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon_notification@2x.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon_notification_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon_notification_dark.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images/icon_notification_dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images/icon_notification_dark@2x.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon16.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon32.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon64.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon1024.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon128.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon256.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon32_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon32_2.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon512.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon256_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon256_2.png -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon512_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tadeuzagallo/GithubPulse/HEAD/widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/icon512_2.png -------------------------------------------------------------------------------- /.yabsrc: -------------------------------------------------------------------------------- 1 | { 2 | "package.json": "version", 3 | "chrome_extension/manifest.json": "version", 4 | "widget/GithubPulse/Resources/Info.plist": "CFBundleShortVersionString", 5 | "widget/GithubPulse/Resources/Info.plist": "CFBundleVersion" 6 | } 7 | -------------------------------------------------------------------------------- /javascript/styles/ActivityGraph.styl: -------------------------------------------------------------------------------- 1 | .activity-graph { 2 | position: relative; 3 | } 4 | 5 | .activity-graph__canvas { 6 | display: block; 7 | position: absolute; 8 | 9 | top: 18px; 10 | width: 390px; 11 | height: 145px; 12 | } 13 | -------------------------------------------------------------------------------- /widget/GithubPulse.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /front/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: osx chrome 2 | 3 | osx: 4 | $(MAKE) -C ./front -B osx 5 | $(MAKE) -C ./widget -B release 6 | 7 | chrome: 8 | $(MAKE) -C ./front -B chrome 9 | crx pack ./chrome_extension -o dist/GithubPulse.crx -p ./resources/GithubPulse.pem 10 | 11 | .PHONY: osx chrome all 12 | -------------------------------------------------------------------------------- /widget/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '6.0' 3 | 4 | target 'GithubPulse' do 5 | 6 | pod 'IYLoginItem' 7 | pod 'INPopoverController', git: 'https://github.com/indragiek/INPopoverController.git' 8 | pod 'SSZipArchive' 9 | pod 'EDSemver' 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /javascript/components/GithubPulse.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | require('../styles/GithubPulse'); 4 | 5 | var GithubPulse = React.createClass({ 6 | render() { 7 | return ( 8 |
9 | { this.props.children } 10 |
11 | ); 12 | } 13 | }); 14 | 15 | module.exports = GithubPulse; 16 | -------------------------------------------------------------------------------- /widget/GithubPulse/Other Sources/BridgeHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgeHeader.h 3 | // GithubPulse 4 | // 5 | // Created by Tadeu Zagallo on 12/31/14. 6 | // Copyright (c) 2014 Tadeu Zagallo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import -------------------------------------------------------------------------------- /front/Makefile: -------------------------------------------------------------------------------- 1 | WEBPACK := node ../node_modules/webpack/bin/webpack.js 2 | DEV_SERVER := node ../node_modules/webpack-dev-server/bin/webpack-dev-server.js 3 | 4 | chrome: 5 | TARGET=chrome $(WEBPACK) 6 | 7 | osx: 8 | TARGET=osx $(WEBPACK) 9 | 10 | chrome.watch: 11 | TARGET=chrome $(WEBPACK) --watch 12 | 13 | osx.watch: 14 | TARGET=osx $(DEV_SERVER) 15 | 16 | .PHONY: chrome osx chrome.watch osx.watch 17 | -------------------------------------------------------------------------------- /widget/Makefile: -------------------------------------------------------------------------------- 1 | release: archive export zip clean 2 | mv GithubPulse.zip ../dist 3 | 4 | clean: 5 | rm -rf GithubPulse.app GithubPulse.xcarchive 6 | 7 | zip: 8 | zip -r GithubPulse.zip GithubPulse.app 9 | 10 | export: 11 | xcodebuild -exportArchive -archivePath GithubPulse.xcarchive -exportPath GithubPulse -exportFormat app 12 | 13 | archive: 14 | xcodebuild archive -workspace GithubPulse.xcworkspace -scheme GithubPulse -archivePath GithubPulse 15 | -------------------------------------------------------------------------------- /widget/GithubPulse/Components/CustomButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomButton.swift 3 | // GithubPulse 4 | // 5 | // Created by Tadeu Zagallo on 12/30/14. 6 | // Copyright (c) 2014 Tadeu Zagallo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class CustomButton : NSButton { 12 | var rightAction:((NSEvent) -> Void)? 13 | 14 | override init(frame frameRect: NSRect) { 15 | super.init(frame: frameRect) 16 | } 17 | 18 | required init?(coder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func rightMouseDown(theEvent: NSEvent) { 23 | if self.rightAction != nil { 24 | self.rightAction?(theEvent) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /front/webpack.config.js: -------------------------------------------------------------------------------- 1 | var utils = { 2 | osx: '../javascript/utils.js', 3 | chrome: '../chrome_extension/js/utils.js' 4 | }; 5 | 6 | module.exports = { 7 | entry: [utils[process.env.TARGET || 'osx'], '../javascript/main.jsx'], 8 | devtool: 'source-map', 9 | output: { 10 | publicPath: 'public/', 11 | path: __dirname + '/public', 12 | filename: 'bundle.js' 13 | }, 14 | module: { 15 | loaders: [ 16 | {test: /\.jsx$/, loader: 'babel-loader'}, 17 | {test: /\.styl$/, loader: 'style-loader!css-loader!stylus-loader'}, 18 | {test: /\.json$/, loader: 'json-loader'}, 19 | {test: /\.ttf$/, loader: 'file-loader' } 20 | ] 21 | }, 22 | resolve: { 23 | extensions: ['', '.js', '.jsx', '.styl' ] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /javascript/styles/Stats.styl: -------------------------------------------------------------------------------- 1 | .stats { 2 | position: absolute; 3 | bottom: 0; 4 | width: 100%; 5 | height: 70px; 6 | 7 | border-top: 1px solid #e9e9e9; 8 | font-family: montserrat; 9 | } 10 | 11 | .stat { 12 | float: left; 13 | 14 | width: 25%; 15 | padding-bottom: 10px; 16 | 17 | border-left: 1px solid #e9e9e9; 18 | text-align: center; 19 | 20 | &:first-child { 21 | border-left: none; 22 | } 23 | } 24 | 25 | .stat__count { 26 | margin: 15px 0 5px 0; 27 | 28 | color: #4183c4; 29 | font-size: 18pt; 30 | line-height: 18pt; 31 | } 32 | 33 | .stat__name { 34 | color: #a4a4a4; 35 | font-size: 8pt; 36 | } 37 | 38 | .notification { 39 | position: relative; 40 | top: -10px; 41 | color: #c44141; 42 | font-size: 11pt; 43 | } 44 | -------------------------------------------------------------------------------- /javascript/styles/Login.styl: -------------------------------------------------------------------------------- 1 | .login { 2 | position: relative; 3 | 4 | top: 24px; 5 | width: 400px; 6 | height: 360px; 7 | padding-top: 20px; 8 | 9 | background: white; 10 | color: #808080; 11 | font-family: 'Open Sans'; 12 | text-align: center; 13 | 14 | h1 { 15 | color: #000; 16 | } 17 | } 18 | 19 | .login__blue { 20 | color: #4183c4; 21 | } 22 | 23 | .login__input { 24 | width: 70%; 25 | padding: 10px; 26 | margin-top: 10px; 27 | 28 | border: 1px solid #c3c3c3; 29 | border-radius: 5px; 30 | color: #606060; 31 | font-size: 1.1em; 32 | text-align: center; 33 | 34 | &:focus { 35 | outline: none; 36 | } 37 | } 38 | 39 | .login__zen { 40 | position: absolute; 41 | 42 | bottom: 20px; 43 | width: 100%; 44 | 45 | font-size: .8em; 46 | } 47 | -------------------------------------------------------------------------------- /javascript/components/Routes.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var ReactDOM = require('react-dom'); 4 | 5 | var GithubPulse = require('./GithubPulse.react'); 6 | var Login = require('./Login.react'); 7 | var Profile = require('./Profile.react'); 8 | var Following = require('./Following.react'); 9 | 10 | var Route = Router.Route; 11 | var IndexRoute = Router.IndexRoute; 12 | 13 | var routes = ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | ReactDOM.render(routes, document.getElementById('github-pulse')); 24 | -------------------------------------------------------------------------------- /javascript/github-api.js: -------------------------------------------------------------------------------- 1 | var GithubApi = {}; 2 | GithubApi.host = 'https://api.github.com'; 3 | 4 | var request = function (method, path, callback) { 5 | var request = new XMLHttpRequest(); 6 | request.onload = function () { 7 | var result = request.responseText; 8 | if (~request.getResponseHeader('content-type').indexOf('json')) { 9 | result = JSON.parse(result); 10 | } 11 | callback(null, result); 12 | }; 13 | request.onerror = function (err) { 14 | callback(err, null); 15 | }; 16 | request.open(method, GithubApi.host + '/' + path, true); 17 | request.send(); 18 | }; 19 | 20 | GithubApi.get = function () { 21 | var args = [].slice.call(arguments); 22 | var callback = args.pop(); 23 | var path = args.join('/'); 24 | request('GET', path, callback); 25 | }; 26 | 27 | module.exports = GithubApi; 28 | -------------------------------------------------------------------------------- /chrome_extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "GithubPulse", 4 | "description": "Chrome Extension to help you remember to contribute every day on Github", 5 | "version": "0.3.10", 6 | "background": { 7 | "scripts": [ 8 | "js/update.js" 9 | ] 10 | }, 11 | "permissions": [ 12 | "management", 13 | "notifications", 14 | "storage", 15 | "alarms", 16 | "webRequest", 17 | "webRequestBlocking", 18 | "https://*/" 19 | ], 20 | "icons": { 21 | "16": "images/icons/icon16.png", 22 | "32": "images/icons/icon32.png", 23 | "48": "images/icons/icon48.png", 24 | "128": "images/icons/icon128.png" 25 | }, 26 | "browser_action": { 27 | "default_icon": "images/blue/icon38.png", 28 | "default_popup": "front/index.html" 29 | }, 30 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 31 | } -------------------------------------------------------------------------------- /widget/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - EDSemver (0.3.0) 3 | - INPopoverController (0.0.1) 4 | - IYLoginItem (0.1) 5 | - SSZipArchive (0.3.2) 6 | 7 | DEPENDENCIES: 8 | - EDSemver 9 | - INPopoverController (from `https://github.com/indragiek/INPopoverController.git`) 10 | - IYLoginItem 11 | - SSZipArchive 12 | 13 | EXTERNAL SOURCES: 14 | INPopoverController: 15 | :git: https://github.com/indragiek/INPopoverController.git 16 | 17 | CHECKOUT OPTIONS: 18 | INPopoverController: 19 | :commit: 4c7c674b25e3859bd3627df35538fbb470c938ab 20 | :git: https://github.com/indragiek/INPopoverController.git 21 | 22 | SPEC CHECKSUMS: 23 | EDSemver: 124a90685873628ec30f42504b461a1a61757568 24 | INPopoverController: e4c315804b6d227c84a6dd64adf6732600b659fc 25 | IYLoginItem: e57a86b39d3b9ab0e2fc033adedae01efafd9adf 26 | SSZipArchive: 11801759495fa4efb7512ef652eb0042d68305d7 27 | 28 | COCOAPODS: 0.39.0 29 | -------------------------------------------------------------------------------- /javascript/styles/Following.styl: -------------------------------------------------------------------------------- 1 | $blue = #4183c4; 2 | 3 | .following { 4 | position: relative; 5 | width: 400px; 6 | height: 351px; 7 | background: white; 8 | overflow-y: scroll; 9 | } 10 | 11 | .following-title { 12 | height: 34px; 13 | padding-top: 4px; 14 | border-bottom: 1px solid #c3c3c3; 15 | background: linear-gradient(#fff, #e0e0e0); 16 | color: #696969; 17 | font-family: montserrat; 18 | font-size: 10.5pt; 19 | line-height: 26px; 20 | text-align: center; 21 | } 22 | 23 | .following-profile { 24 | position: absolute; 25 | z-index: 100; 26 | top: 4px; 27 | left: 8px; 28 | color: #4183c4; 29 | font-family: montserrat; 30 | font-size: 10.5pt; 31 | line-height: 26px; 32 | cursor: pointer; 33 | } 34 | 35 | .following-container__progress-bar { 36 | position: absolute; 37 | z-index: 101; 38 | top: 32px; 39 | left: 0; 40 | right: 0; 41 | height: 2px; 42 | } 43 | 44 | .following-container__progress-bar-complete { 45 | background-color: $blue; 46 | height: 100%; 47 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GithubPulse", 3 | "version": "0.3.10", 4 | "description": "Application to help you remember to contribute every day on Github", 5 | "main": "", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/tadeuzagallo/GithubPulse" 12 | }, 13 | "author": "Tadeu Zagallo ", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "babel-core": "^5.8.29", 17 | "babel-loader": "^5.3.2", 18 | "chart.js": "^1.0.2", 19 | "classnames": "^2.2.0", 20 | "crx": "^3.0.2", 21 | "css-loader": "^0.21.0", 22 | "file-loader": "^0.8.1", 23 | "json-loader": "^0.5.3", 24 | "object-assign": "^4.0.1", 25 | "react": "^0.14.0", 26 | "react-addons-css-transition-group": "^0.14.0", 27 | "react-dom": "^0.14.0", 28 | "react-router": "^1.0.0-rc3", 29 | "style-loader": "^0.13.0", 30 | "stylus": "^0.52.4", 31 | "stylus-loader": "^1.4.0", 32 | "webpack": "^1.12.2", 33 | "webpack-dev-server": "^1.12.1", 34 | "yabs": "^0.0.3" 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Tadeu Zagallo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /javascript/styles/main.styl: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | overflow: hidden; 4 | } 5 | 6 | @font-face { 7 | font-family: 'Montserrat'; 8 | font-weight: normal; 9 | src: url('../fonts/montserrat/Montserrat-Regular.ttf') format('truetype'); 10 | } 11 | 12 | @font-face { 13 | font-family: 'Montserrat'; 14 | font-weight: bold; 15 | src: url('../fonts/montserrat/Montserrat-Bold.ttf') format('truetype'); 16 | } 17 | 18 | @font-face { 19 | font-family: 'Open Sans'; 20 | src: url('../fonts/open_sans/OpenSans-Regular.ttf') format('truetype'); 21 | } 22 | 23 | @font-face { 24 | font-family: 'Open Sans'; 25 | font-weight: bold; 26 | src: url('../fonts/open_sans/OpenSans-Bold.ttf') format('truetype'); 27 | } 28 | 29 | .chrome { 30 | .profile-title { 31 | padding-top: 4px; 32 | } 33 | 34 | .config__gear { 35 | top: 8px; 36 | } 37 | 38 | .update { 39 | margin-top: 7px; 40 | } 41 | 42 | .config__panel { 43 | padding-top: 30px; 44 | } 45 | 46 | .config__item:nth-of-type(1) 47 | .config__separator:nth-of-type(2) { 48 | display: none; 49 | } 50 | 51 | .panel-enter, 52 | .panel-leave.panel-leave-active { 53 | -webkit-transform: scale(0.11, 0.25); 54 | } 55 | } 56 | 57 | * { 58 | box-sizing: border-box; 59 | } 60 | -------------------------------------------------------------------------------- /javascript/styles/ProfileInfo.styl: -------------------------------------------------------------------------------- 1 | $width = 400px; 2 | 3 | .profile-info { 4 | position: relative; 5 | 6 | height: 90px; 7 | 8 | border-bottom: 1px solid #e9e9e9; 9 | } 10 | 11 | .profile-info__picture { 12 | position: absolute; 13 | 14 | left: 20px; 15 | top: 20px; 16 | width: 50px; 17 | 18 | border-radius: 50%; 19 | cursor: pointer; 20 | } 21 | 22 | .profile-info__data { 23 | position: absolute; 24 | 25 | left: 90px; 26 | top: 0px; 27 | width: 290px; 28 | height: 90px; 29 | 30 | line-height: 86px; 31 | } 32 | 33 | .profile-info__data__content { 34 | display: inline-block; 35 | vertical-align: middle; 36 | } 37 | 38 | .profile-info__name { 39 | margin-bottom: 4px; 40 | 41 | color: #4183c4; 42 | font-family: 'Open Sans'; 43 | font-size: 16pt; 44 | font-weight: bold; 45 | line-height: 16pt; 46 | cursor: pointer; 47 | } 48 | 49 | .profile-info__username { 50 | margin: 0; 51 | 52 | color: #696969; 53 | font-family: 'Open Sans'; 54 | font-weight: normal; 55 | font-size: 12pt; 56 | line-height: 12pt; 57 | cursor: pointer; 58 | } 59 | 60 | .profile-info__following { 61 | margin: 4px 0 -14px 0; 62 | color: #4183c4; 63 | font-family: 'Open Sans'; 64 | font-weight: normal; 65 | font-size: 10pt; 66 | line-height: 10pt; 67 | cursor: pointer; 68 | } 69 | 70 | .profile-info__logout { 71 | margin-left: 6px; 72 | 73 | color: #444; 74 | cursor: pointer; 75 | } 76 | -------------------------------------------------------------------------------- /javascript/styles/Config.styl: -------------------------------------------------------------------------------- 1 | .config__gear { 2 | position: absolute; 3 | z-index: 100; 4 | 5 | top: 9px; 6 | right: 8px; 7 | 8 | -webkit-transform: rotate(0deg); 9 | transition: -webkit-transform .5s; 10 | } 11 | 12 | .config__gear.true { 13 | -webkit-transform: rotate(-135deg); 14 | } 15 | 16 | .config__overlay { 17 | position: absolute; 18 | z-index: 98; 19 | 20 | top: 0; 21 | bottom: 0; 22 | left: 0; 23 | right: 0; 24 | } 25 | 26 | .config__panel { 27 | position: absolute; 28 | z-index: 99; 29 | 30 | top: 3px; 31 | right: 4px; 32 | padding: 20px 15px 15px 15px; 33 | 34 | background: white; 35 | border: 1px solid #c3c3c3; 36 | border-radius: 3px; 37 | color: #808080; 38 | font-family: 'Open Sans'; 39 | font-size: 10pt; 40 | } 41 | 42 | .config__startup, 43 | .config__quit { 44 | margin-right: 8px; 45 | } 46 | 47 | .config__item * { 48 | cursor: pointer; 49 | } 50 | 51 | .config__separator { 52 | width: 100%; 53 | margin: 8px 0; 54 | 55 | border-bottom: 1px solid #aaa; 56 | } 57 | 58 | 59 | .panel-enter.panel-enter-active, 60 | .panel-leave { 61 | -webkit-transform: scale(1, 1); 62 | transition: -webkit-transform .2s; 63 | 64 | .content { 65 | opacity: 1; 66 | transition: opacity .15s; 67 | } 68 | } 69 | 70 | .panel-enter, 71 | .panel-leave { 72 | -webkit-transform-origin: top right; 73 | } 74 | 75 | .panel-enter, 76 | .panel-leave.panel-leave-active { 77 | -webkit-transform: scale(0.115, 0.21); 78 | 79 | .content { 80 | opacity: 0; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /javascript/components/Stats.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | require('../styles/Stats'); 4 | 5 | var p = (l, n) => n === 1 ? l : l + 's'; 6 | 7 | var Stats = React.createClass({ 8 | propTypes: { 9 | repos: React.PropTypes.number.isRequired, 10 | followers: React.PropTypes.number.isRequired, 11 | streak: React.PropTypes.number.isRequired, 12 | today: React.PropTypes.number.isRequired 13 | }, 14 | render() { 15 | return ( 16 |
17 |
18 |
{ this.props.repos }
19 |
{ p('repo', this.props.repos) }
20 |
21 | 22 |
23 |
{ this.props.followers }
24 |
{ p('follower', this.props.followers) }
25 |
26 | 27 |
28 |
{ this.props.streak }{ this.props.streak > 15 ? : '' }
29 |
{ p('day', this.props.streak) } streak
30 |
31 | 32 |
33 |
{ this.props.today }{ !this.props.today ? : '' }
34 |
{ p('commit', this.props.today) } today
35 |
36 |
37 | ); 38 | } 39 | }); 40 | 41 | module.exports = Stats; 42 | -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.3.1 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 0.3.10 25 | GithubRepo 26 | tadeuzagallo/GithubPulse 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | 36 | NSHumanReadableCopyright 37 | Copyright © 2014 Tadeu Zagallo. All rights reserved. 38 | NSMainNibFile 39 | MainMenu 40 | NSPrincipalClass 41 | NSApplication 42 | 43 | -------------------------------------------------------------------------------- /widget/GithubPulse/Resources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon32_2.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon256_2.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon512_2.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /front/images/sync.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slice 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /javascript/components/UserLine.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var cx = require('classnames'); 4 | 5 | require('../styles/UserLine'); 6 | 7 | var UserLine = React.createClass({ 8 | render() { 9 | var hasToday = this.props.user.today > 0; 10 | var hasStreak = this.props.user.streak > 0; 11 | 12 | var todayClass = cx({'user-line__today': true, 'user-line__today-zero': !hasToday}); 13 | var streakClass = cx({'user-line__bar-streak': true, 'user-line__bar-streak-zero': !hasStreak}); 14 | var usernameClass = cx({'user-line__username': true, 'user-line__username-zero': !hasStreak}); 15 | 16 | var widthPercent = Math.round((this.props.user.streak / this.props.maxStreak) * 55) + 45; 17 | if (isNaN(widthPercent)) { widthPercent = 45; } 18 | 19 | return ( 20 |
21 |
22 |
23 | { 'streak ' + (typeof this.props.user.streak !== 'undefined' ? this.props.user.streak : '-') } 24 |
25 |
26 |
@{ this.props.user.login }
27 |
28 | { typeof this.props.user.today !== 'undefined' ? this.props.user.today : '-' } 29 |
30 |
today
31 | 32 |
33 | ); 34 | }, 35 | _profile() { 36 | Utils.openURL('https://github.com/' + this.props.user.login); 37 | } 38 | }); 39 | 40 | module.exports = UserLine; 41 | -------------------------------------------------------------------------------- /javascript/styles/Profile.styl: -------------------------------------------------------------------------------- 1 | .profile { 2 | position: relative; 3 | 4 | width: 400px; 5 | height: 316px; 6 | 7 | background: white; 8 | } 9 | 10 | .update { 11 | height: 35px; 12 | padding: 7px 6px; 13 | 14 | background: linear-gradient(#fff, #e0e0e0); 15 | border-top: 1px solid #c3c3c3; 16 | color: #8d8d8d; 17 | font-family: 'Open Sans'; 18 | font-size: 10pt; 19 | } 20 | 21 | .update__sync, 22 | .update__last { 23 | float: left; 24 | } 25 | 26 | .update__sync { 27 | width: 14px; 28 | height: 14px; 29 | margin: 3px; 30 | } 31 | 32 | .update__last { 33 | margin: 1px 0 0 3px; 34 | } 35 | 36 | @-webkit-keyframes rotate { 37 | from { 38 | -webkit-transform: rotate(0deg); 39 | } 40 | 41 | to { 42 | -webkit-transform: rotate(360deg); 43 | } 44 | } 45 | 46 | .rotate { 47 | -webkit-animation: rotate 2s infinite linear; 48 | } 49 | 50 | .profile-title { 51 | height: 34px; 52 | padding-top: 4px; 53 | 54 | border-bottom: 1px solid #c3c3c3; 55 | background: linear-gradient(#fff, #e0e0e0); 56 | color: #696969; 57 | font-family: montserrat; 58 | font-size: 10.5pt; 59 | line-height: 26px; 60 | text-align: center; 61 | } 62 | 63 | .version { 64 | float: right; 65 | margin: 1px 8px 0 0; 66 | 67 | color: #8d8d8d; 68 | font-weight: bold; 69 | 70 | .octicon-alert { 71 | cursor: pointer; 72 | margin-left: 3px; 73 | font-size: 9pt; 74 | } 75 | 76 | &.true:hover::before { 77 | content: 'Update available, click to restart and apply'; 78 | position: absolute; 79 | display: block; 80 | width: 150px; 81 | height: 40px; 82 | bottom: 30px; 83 | padding: 10px; 84 | border-radius: 5px; 85 | background: black; 86 | font: open sans; 87 | color: white; 88 | right: 3px; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /javascript/components/ProfileInfo.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | require('../styles/ProfileInfo'); 4 | 5 | var ProfileInfo = React.createClass({ 6 | propTypes: { 7 | picture: React.PropTypes.string.isRequired, 8 | name: React.PropTypes.string, 9 | username: React.PropTypes.string.isRequired 10 | }, 11 | render() { 12 | return ( 13 |
14 |
15 | 16 | 17 |
18 | 19 |
20 | {this.props.name} 21 | 22 | 25 |
26 |
27 | @{ this.props.username } 28 |
29 |
30 | Show Users I'm Following 31 |
32 |
33 |
34 |
35 |
36 | ); 37 | }, 38 | _logout() { 39 | console.log(this.props); 40 | Utils.clear('username'); 41 | this.props.history.pushState(null, '/'); 42 | }, 43 | _gotoUsername() { 44 | Utils.openURL('https://github.com/' + this.props.username); 45 | }, 46 | _showFollowing() { 47 | this.props.history.pushState(null, `/compare/following/${this.props.username}`); 48 | }, 49 | }); 50 | 51 | module.exports = ProfileInfo; 52 | -------------------------------------------------------------------------------- /widget/GithubPulse/Views/ContentViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /javascript/components/ActivityGraph.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Chart = require('chart.js'); 3 | 4 | 5 | require('../styles/ActivityGraph'); 6 | 7 | var ActivityGraph = React.createClass({ 8 | propTypes: { 9 | commits: React.PropTypes.arrayOf(React.PropTypes.number).isRequired 10 | }, 11 | render() { 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }, 18 | shouldComponentUpdate(nextProps) { 19 | var pc = this.props.commits || []; 20 | var nc = nextProps.commits || []; 21 | 22 | if (pc.length != nc.length) { 23 | return true; 24 | } 25 | 26 | for (var i = 0, l = pc.length; i < l; i++) { 27 | if (pc[i] !== nc[i]) { 28 | return true; 29 | } 30 | } 31 | 32 | return false; 33 | }, 34 | componentDidUpdate() { 35 | if (this.chart) { 36 | this.chart.destroy(); 37 | } 38 | 39 | var canvas = this.refs.canvas; 40 | var ctx = canvas.getContext('2d'); 41 | var commits = this.props.commits; 42 | var labels = []; 43 | var max = commits.reduce(function (a, b) { 44 | labels.push(''); 45 | return Math.max(a, b); 46 | }, 0); 47 | var step = Math.ceil(max / 2); 48 | 49 | this.chart = new Chart(ctx).Line({ 50 | labels: labels, 51 | datasets: [{ 52 | label: 'Commits', 53 | fillColor: 'rgba(65,131,196,0.2)', 54 | strokeColor: 'rgba(65,131,196,1)', 55 | pointColor: 'rgba(65,131,196,1)', 56 | pointStrokeColor: '#fff', 57 | pointHighlightFill: '#fff', 58 | pointHighlightStroke: 'rgba(151,187,205,1)', 59 | data: commits 60 | }] 61 | }, { 62 | scaleShowVerticalLines: false, 63 | scaleOverride: true, 64 | scaleSteps: 2, 65 | scaleStepWidth: step, 66 | scaleStartValue: 0, 67 | scaleLabel: " <%=value%>", 68 | pointHitDetectionRadius: 3, 69 | tooltipXPadding: 10, 70 | }); 71 | } 72 | }); 73 | 74 | module.exports = ActivityGraph; 75 | -------------------------------------------------------------------------------- /javascript/styles/UserLine.styl: -------------------------------------------------------------------------------- 1 | $blue = #4183c4; 2 | $red = #c44141; 3 | $zero = #EDEDED; 4 | 5 | .user-line { 6 | position: relative; 7 | margin: 2px 2px 2px 2px; 8 | height: 40px; 9 | border: 2px solid $zero; 10 | border-radius: 20px; 11 | cursor: pointer; 12 | } 13 | 14 | .user-line__picture { 15 | position: absolute; 16 | top: 0px; 17 | left: 36px; 18 | width: 36px; 19 | height: 36px; 20 | border-top-right-radius: 18px; 21 | border-bottom-right-radius: 18px; 22 | } 23 | 24 | .user-line__today { 25 | position: absolute; 26 | top: 0px; 27 | left: 0px; 28 | margin: 10px 0 0 2px; 29 | width: 36px; 30 | height: 36px; 31 | font-family: montserrat; 32 | text-align: center; 33 | color: $blue; 34 | font-size: 12pt; 35 | line-height: 12pt; 36 | border-top-left-radius: 18px; 37 | border-bottom-left-radius: 18px; 38 | } 39 | 40 | .user-line__today-label { 41 | position: absolute; 42 | top: 0px; 43 | left: 0px; 44 | margin: 24px 0 0 2px; 45 | width: 36px; 46 | height: 36px; 47 | font-family: montserrat; 48 | text-align: center; 49 | color: #a4a4a4; 50 | font-size: 7pt; 51 | line-height: 7pt; 52 | } 53 | 54 | .user-line__today-zero { 55 | color: $red; 56 | } 57 | 58 | .user-line__username { 59 | position: absolute; 60 | top: 0px; 61 | left: 72px; 62 | color: white; 63 | font-family: 'Open Sans'; 64 | font-size: 10pt; 65 | font-weight: bold; 66 | line-height: 10pt; 67 | } 68 | 69 | .user-line__username-zero { 70 | color: $blue; 71 | } 72 | 73 | .user-line__bar { 74 | position: absolute; 75 | top: 0px; 76 | bottom: 0px; 77 | left: 36px; 78 | right: 0px; 79 | } 80 | 81 | .user-line__bar-streak { 82 | background-color: $blue; 83 | height: 100%; 84 | border-top-right-radius: 18px; 85 | border-bottom-right-radius: 18px; 86 | 87 | span { 88 | margin: 16px 16px 0 0; 89 | font-family: montserrat; 90 | color: white; 91 | font-size: 12pt; 92 | line-height: 12pt; 93 | float: right; 94 | } 95 | } 96 | 97 | .user-line__bar-streak-zero { 98 | background-color: $zero; 99 | } 100 | -------------------------------------------------------------------------------- /javascript/components/Login.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var assign = require('object-assign'); 3 | 4 | var GithubApi = require('../github-api'); 5 | var Config = require('./Config.react'); 6 | 7 | require('../styles/Login'); 8 | 9 | var Login = React.createClass({ 10 | getInitialState() { 11 | return { 12 | username: '', 13 | zen: '' 14 | }; 15 | }, 16 | render() { 17 | return ( 18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 |

Github Pulse

26 |
27 |
28 | 29 |
30 |
31 |
{ this.state.zen }
32 | api.github.com/zen 33 |
34 |
35 |
36 | ); 37 | }, 38 | componentDidMount() { 39 | this._mounted = true; 40 | this._fetchZen(); 41 | this._fetchUserName(); 42 | }, 43 | componentWillUnmount() { 44 | this._mounted = false; 45 | }, 46 | _fetchZen() { 47 | Utils.fetch('zen', 60 * 60 * 1000, (zen) => { 48 | if (zen) { 49 | this._mounted && this.setState({ zen: zen }); 50 | } else { 51 | GithubApi.get('zen', (err, result) => { 52 | this._mounted && this.setState({ zen: result }); 53 | Utils.save('zen', result); 54 | }); 55 | } 56 | }); 57 | }, 58 | _fetchUserName() { 59 | Utils.fetch('username', (username) => { 60 | if (username) { 61 | this.props.history.pushState(null, `/${username}`); 62 | } 63 | }); 64 | }, 65 | _onChange(event) { 66 | this.setState({ username: event.target.value.trim() }); 67 | }, 68 | _onKeyDown(event) { 69 | if (event.keyCode === 13) { 70 | let username = this.state.username; 71 | Utils.save('username', username); 72 | this.props.history.pushState(null, `/${username}`); 73 | } 74 | } 75 | }); 76 | 77 | module.exports = Login; 78 | -------------------------------------------------------------------------------- /chrome_extension/js/update.js: -------------------------------------------------------------------------------- 1 | // jshint expr: true 2 | 3 | var notify = function () { 4 | chrome.notifications.create('gotta_commit', { 5 | type: 'basic', 6 | iconUrl: '../images/icons/icon128.png', 7 | title: 'You haven\'t committed yet today...', 8 | message: 'Rush to keep your streak going!' 9 | }, function (id) { }); 10 | }; 11 | 12 | var checkNotificationInterval = function () { 13 | var d = new Date(); 14 | 15 | if (d.getHours() < 18) { 16 | return; 17 | } 18 | 19 | chrome.storage.sync.get('last_notification', function (r) { 20 | d.setHours(1); 21 | d.setMinutes(0); 22 | 23 | if (r.last_notification && 24 | r.last_notification > d.getTime()) { 25 | return; 26 | } 27 | 28 | notify(); 29 | chrome.storage.sync.set({ 30 | last_notification: Date.now() 31 | }); 32 | }); 33 | }; 34 | 35 | var checkShouldNotify = function () { 36 | chrome.storage.sync.get('dont_notify', function (r) { 37 | var dont_notify = r.dont_notify && JSON.parse(r.dont_notify).data; 38 | 39 | if (dont_notify) { 40 | return; 41 | } 42 | 43 | checkNotificationInterval(); 44 | }); 45 | }; 46 | 47 | var update = function (username) { 48 | var request = new XMLHttpRequest(); 49 | request.onload = function () { 50 | var parser = new DOMParser(); 51 | var svg = parser.parseFromString(this.responseText, "image/svg+xml"); 52 | var commits = svg.querySelectorAll('rect'); 53 | var today = parseInt(commits[commits.length - 1].getAttribute('data-count'), 10); 54 | var color = today === 0 ? 'red' : 'blue'; 55 | var imgs = {}; 56 | [19, 38].forEach(function (size) { 57 | imgs[size] = '../images/' + color + '/icon' + size + '.png'; 58 | }); 59 | 60 | chrome.browserAction.setIcon({ 61 | path: imgs 62 | }); 63 | 64 | if (today === 0) { 65 | checkShouldNotify(); 66 | } 67 | }; 68 | 69 | request.open('GET', 'https://github.com/users/' + username + '/contributions', true); 70 | request.send(null); 71 | }; 72 | 73 | function onAlarm(alarm) { 74 | chrome.storage.sync.get('username', function (r) { 75 | var item = r.username && JSON.parse(r.username); 76 | item && item.data && update(item.data); 77 | }); 78 | } 79 | chrome.alarms.onAlarm.addListener(onAlarm); 80 | 81 | 82 | chrome.alarms.clearAll(function () { 83 | chrome.alarms.create('check_contributions', { 84 | delayInMinutes: 15, 85 | periodInMinutes: 15 86 | }); 87 | }); 88 | 89 | onAlarm(); 90 | -------------------------------------------------------------------------------- /front/images/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slice 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /javascript/components/Config.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var CSSTransitionGroup = require('react-addons-css-transition-group'); 3 | 4 | require('../styles/Config'); 5 | 6 | var Config = React.createClass({ 7 | getInitialState() { 8 | return { 9 | open: false, 10 | active: false, 11 | notify: false 12 | }; 13 | }, 14 | render() { 15 | var panel = ''; 16 | var overlay = ''; 17 | 18 | if (this.state.open) { 19 | overlay =
; 20 | 21 | panel = ( 22 |
23 |
24 |
25 | 31 | 32 |
33 |
34 |
35 | 41 | 42 |
43 |
44 |
45 | 46 | Quit Github Pulse 47 |
48 |
49 |
50 | ); 51 | } 52 | 53 | return ( 54 |
55 | 56 | 57 | { overlay } 58 | 59 | { panel } 60 | 61 |
62 | ); 63 | }, 64 | componentDidMount() { 65 | Utils.raw('check_login()', (active) => { 66 | this.setState({ active: active }); 67 | }); 68 | 69 | Utils.fetch('dont_notify', (dontNotify) => { 70 | this.setState({ notify: !dontNotify }); 71 | }); 72 | }, 73 | _toggleActive() { 74 | this.setState({ active: !this.state.active }); 75 | Utils.raw('toggle_login()'); 76 | }, 77 | _toggleNotifications() { 78 | var notify = !this.state.notify; 79 | this.setState({ notify: notify }); 80 | Utils.save('dont_notify', !notify); 81 | }, 82 | _togglePanel() { 83 | this.setState({ open: !this.state.open }); 84 | }, 85 | _quit() { 86 | Utils.quit(); 87 | } 88 | }); 89 | 90 | module.exports = Config; 91 | -------------------------------------------------------------------------------- /javascript/utils.js: -------------------------------------------------------------------------------- 1 | window.Utils = (function () { 2 | var Utils = {}; 3 | 4 | var queue = []; 5 | var running = false; 6 | var pop = function () { 7 | if (queue.length) { 8 | setTimeout(function() { 9 | window.location = encodeURI(queue.shift()); 10 | pop(); 11 | }, 0); 12 | } else { 13 | running = false; 14 | } 15 | }; 16 | 17 | Utils.redirect = function (url) { 18 | queue.push(url); 19 | if (!running) { 20 | running = true; 21 | pop(); 22 | } 23 | }; 24 | 25 | Utils.log = function () { 26 | Utils.redirect('log:' + [].join.call(arguments, ' ')); 27 | }; 28 | 29 | Utils.save = function (key, value) { 30 | if (Array.isArray(key)) { 31 | key = key.join('/'); 32 | } 33 | 34 | value = JSON.stringify({ 35 | time: Date.now(), 36 | data: value 37 | }); 38 | 39 | Utils.redirect('osx:set(' + key + '%%' + value + ')'); 40 | }; 41 | 42 | var callbacks = {}; 43 | window.get = function (key, value, expiration) { 44 | key = decodeURI(key); 45 | value = decodeURI(value); 46 | 47 | var item = value && JSON.parse(value); 48 | var time = null; 49 | 50 | if (expiration !== -1 && item && Date.now() - item.time > expiration) { 51 | item = null; 52 | } else if (item) { 53 | time = item.time; 54 | item = item.data; 55 | } 56 | 57 | var callback = callbacks[key]; 58 | callbacks[key] = null; 59 | callback(item, time); 60 | }; 61 | 62 | Utils.fetch = function (key, expiration, callback) { 63 | if (Array.isArray(key)) { 64 | key = key.join('/'); 65 | } 66 | 67 | if (typeof expiration === 'function') { 68 | callback = expiration; 69 | expiration = undefined; 70 | } 71 | 72 | 73 | callbacks[key] = callback; 74 | Utils.redirect('osx:get(' + key + '%%' + (expiration || -1) + ')'); 75 | }; 76 | 77 | var rawCallbacks = {}; 78 | window.raw = function (fnName) { 79 | var args = [].slice.call(arguments, 1); 80 | var callback = rawCallbacks[fnName]; 81 | rawCallbacks[fnName] = null; 82 | callback.apply(null, args); 83 | }; 84 | 85 | Utils.raw = function (expression, callback) { 86 | if (callback) { 87 | fnName = expression.split('(').shift(); 88 | rawCallbacks[fnName] = callback; 89 | } 90 | 91 | Utils.redirect('osx:' + expression); 92 | }; 93 | 94 | Utils.clear = function (key) { 95 | if (Array.isArray(key)) { 96 | key = key.join('/'); 97 | } 98 | 99 | Utils.redirect('osx:remove(' + key + ')'); 100 | }; 101 | 102 | Utils.openURL = function (url) { 103 | Utils.redirect('osx:open_url(' + url + ')'); 104 | }; 105 | 106 | var contributionsCallbacks = {}; 107 | window.contributions = function (username) { 108 | var fn = contributionsCallbacks[username]; 109 | if (fn) { 110 | contributionsCallbacks[username] = null; 111 | fn.apply(null, [].slice.call(arguments, 1)); 112 | } 113 | }; 114 | 115 | Utils.contributions = function (username, callback, skipUpdateIcon) { 116 | contributionsCallbacks[username] = callback; 117 | Utils.redirect('osx:contributions(' + username + (skipUpdateIcon?'%%false':'') + ')'); 118 | }; 119 | 120 | Utils.quit = function () { 121 | this.raw('quit()'); 122 | }; 123 | 124 | return Utils; 125 | })(); 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Github Pulse](resources/icon.png) Github Pulse [![Release 0.3.10](https://img.shields.io/badge/Release-0.3.10-green.svg)](https://github.com/tadeuzagallo/GithubPulse/releases) 2 | 3 | Github Pulse is an app to help you keep your streaks, making a commit every day. 4 | 5 | It shows a graph of your last month commits and how long is your current streak. Its icon also turns red if you haven't committed today yet, and by the end of the afternoon it'll remind you once, in case you still haven't committed. 6 | 7 | ![Screenshot 1](resources/screenshot1.png) 8 | ![Screenshot 2](resources/screenshot2.png) 9 | ![Screenshot 3](resources/screenshot3.png) 10 | 11 | ## Installation 12 | 13 | Right now it is available for OSX and Google Chrome 14 | 15 | For OSX you can just download it [here](https://github.com/tadeuzagallo/GithubPulse/raw/master/dist/GithubPulse.zip), unzip and copy to your applications folder. 16 | 17 | The Google Chrome Extension is available on the [Chrome Web Store](https://chrome.google.com/webstore/detail/githubpulse/ppkickbgijieebbgfipephpafiiebapg) and if, for any reason, you want to download directly, it's also available [here](https://github.com/tadeuzagallo/GithubPulse/raw/master/dist/GithubPulse.crx) 18 | 19 | ## What's being used 20 | 21 | The OSX application has a small shell of native code, written in Swift and renders a React app (using JSX) on a WebView and uses `window.location` redirects to communicate with the native app. 22 | 23 | The same react code is used for the Chrome Extension, just replacing the storage helpers and background workers. 24 | 25 | ### 3rd party libraries (Front) 26 | 27 | * React 28 | * Webpack 29 | * Stylus 30 | * Octicons 31 | * react-router 32 | * Chart.js 33 | 34 | ### Pods 35 | 36 | * IYLoginItem 37 | * INPopoverController 38 | 39 | ## Building it locally 40 | 41 | As mentioned above I'm using Swift and React, so you'll need to have XCode 6+, Node and CocoaPods already installed in order to build. 42 | 43 | ### To get started: 44 | 45 | * Clone the repo: `$ git clone https://github.com/tadeuzagallo/GithubPulse.git` 46 | * Install the npm dependencies: `$ npm install` 47 | * Install the pods: `$ cd widget && pod install`. If you haven't used pod before, you will need to run `pod setup` first. 48 | 49 | ### Debug building 50 | 51 | The debug build points the WebView to `webpack-dev-server`'s default address: `localhost:8080`, so in order to get it running 52 | 53 | * Start the webpack dev server: `$ cd front && make osx.watch` 54 | * Just build the app through XCode interface 55 | 56 | ### Release building 57 | 58 | __NOTICE__: If you just cloned the repo and wants `make` it, you'll have to open the file `widget/GithubPulse.xcworkspace` on XCode at least once for the build to succeed. 59 | 60 | Just run `$ make osx` and the file `GithubPulse.zip` will be placed inside the `dist` directory. 61 | 62 | ### Chrome Extension 63 | 64 | There actually is a target on the root `Makefile` called `chrome` but it won't work, because the private key is, well... private. 65 | 66 | But you can still build the front end and load the unpacked extensions. Here is how: 67 | 68 | * Run `$ cd front && make chrome` ( or `make chrome.watch` if you want to watch for changes) 69 | * Then go to `chrome:extension` 70 | * click on `Load unpacked extension...` (make sure `Developer mode` is checked on the top right of the page) 71 | * Select `GithubPulse/chrome_extension` 72 | * Done! 73 | 74 | ## Credits and Motivation 75 | 76 | I really believe committing every day on an open source project is the best practice a developer can keep, so I made this project to show my love to Github and make I sure I never miss a commit! :D 77 | -------------------------------------------------------------------------------- /widget/GithubPulse/Models/Contributions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Contributions.swift 3 | // GithubPulse 4 | // 5 | // Created by Tadeu Zagallo on 12/30/14. 6 | // Copyright (c) 2014 Tadeu Zagallo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | typealias FetchCallback = ((Bool, [Int], Int, Int) -> Void)! 12 | 13 | class Contributions { 14 | var username = "" 15 | var year = [Int]() 16 | var commits = [Int]() 17 | var today = 0 18 | var streak = 0 19 | var succeeded = false 20 | var state = 0 21 | var callback: FetchCallback = nil 22 | let streakRegex = try? NSRegularExpression(pattern: "Current streak\\s*]*?>(\\d+)\\s*days", options: NSRegularExpressionOptions.CaseInsensitive) 23 | let dayRegex = try? NSRegularExpression(pattern: " Void) { 30 | let url = NSURL(string: URLString) 31 | let request = NSMutableURLRequest(URL: url!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) 32 | request.HTTPShouldHandleCookies = false 33 | 34 | NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response, data, error) in 35 | if error != nil || data == nil { 36 | self.invokeCallback(false) 37 | return 38 | } 39 | 40 | completionBlock(String(data: data!, encoding: NSUTF8StringEncoding)!) 41 | } 42 | } 43 | 44 | func fetch(username: String, completionBlock: FetchCallback) { 45 | self.username = username 46 | self.year = [] 47 | self.callback = completionBlock 48 | 49 | baseFetch("https://github.com/\(username)") { (body) in 50 | self.parse(body) 51 | } 52 | } 53 | 54 | func fetchContributions() { 55 | baseFetch("https://github.com/users/\(username)/contributions") { (body) in 56 | let range = self.getRange(body) 57 | 58 | if range.location == NSNotFound { 59 | self.invokeCallback(false) 60 | return 61 | } 62 | 63 | self.parseStreak(body, range: range) 64 | self.invokeCallback(true) 65 | } 66 | } 67 | 68 | private func invokeCallback(success: Bool) { 69 | if callback != nil { 70 | callback(success, commits, streak, today) 71 | } 72 | 73 | self.callback = nil 74 | } 75 | 76 | private func getRange(input: String) -> NSRange { 77 | let start = (input as NSString).rangeOfString("
); 26 | var progressBar = ''; 27 | 28 | if (this.state.following) { 29 | usersLines = this.state.following.map( (user) => { 30 | return (); 31 | }); 32 | 33 | if (this.state.updated < this.state.following.length) { 34 | var widthPercent = Math.round((this.state.updated / this.state.following.length) * 100); 35 | progressBar = ( 36 |
37 |
38 |
39 | ); 40 | } 41 | } 42 | 43 | return ( 44 |
45 | 46 |
47 | Back 48 |
49 |
50 | Following ({ usersLines.length }) 51 |
52 |
53 |
54 | { usersLines } 55 |
56 |
57 | { progressBar } 58 |
59 | ); 60 | }, 61 | componentDidMount() { 62 | window.update = function () {}; 63 | this._fetchUserFollowing(false); 64 | }, 65 | componentWillUnmount() { 66 | window.update = null; 67 | }, 68 | _fetchUserFollowing(force) { 69 | var _this = this; 70 | var username = this.props.params.username; 71 | var arr = []; 72 | 73 | var getPage = function (page) { 74 | GithubApi.get('users', username + '/following?per_page=100&page=' + page, (err, result) => { 75 | arr = arr.concat(result); 76 | 77 | if (result.length === 100) { 78 | getPage(++page); 79 | } else { 80 | arr.forEach((u) => { u.streak = u.today = 0; }); 81 | _this.setState({ 82 | following: arr 83 | }); 84 | _this._fetchContributions(); 85 | } 86 | }); 87 | }; 88 | 89 | Utils.fetch([username, 'following'], 15*60*1000, function (following) { 90 | if (following) { 91 | _this.setState(following); 92 | } else { 93 | getPage(1); 94 | } 95 | }); 96 | }, 97 | _fetchContributions() { 98 | var _this = this; 99 | var updated = 0; 100 | this.state.following.forEach((user) => { 101 | Utils.contributions(user.login, (success, today, streak, commits) => { 102 | var isLast = (++updated === this.state.following.length); 103 | var following = _this.state.following; 104 | 105 | user.today = today; 106 | user.streak = streak; 107 | var maxStreak = Math.max(_this.state.maxStreak, user.streak); 108 | 109 | if (updated % SORT_FREQ === 0 || isLast) { 110 | following.sort(userSort); 111 | } 112 | 113 | _this.setState({ 114 | updated: updated, 115 | maxStreak: maxStreak, 116 | following: following 117 | }); 118 | 119 | if (isLast) { 120 | Utils.save([this.props.params.username, 'following'], { 121 | maxStreak: maxStreak, 122 | following: following 123 | }); 124 | } 125 | }, true); 126 | }); 127 | }, 128 | _profile() { 129 | this.props.history.pushState(null, `/${this.props.params.username}`); 130 | } 131 | }); 132 | 133 | module.exports = Following; 134 | -------------------------------------------------------------------------------- /javascript/fonts/montserrat/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Julieta Ulanovsky (julieta.ulanovsky@gmail.com), with Reserved Font Names 'Montserrat' 2 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 3 | This license is copied below, and is also available with a FAQ at: 4 | http://scripts.sil.org/OFL 5 | 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION & CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /widget/GithubPulse/Other Sources/GithubUpdate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GithubUpdate.swift 3 | // GithubPulse 4 | // 5 | // Created by Tadeu Zagallo on 1/19/15. 6 | // Copyright (c) 2015 Tadeu Zagallo. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class GithubUpdate { 12 | var bundleVersion:String? 13 | var repoName:String? 14 | var githubVersion:String? 15 | var install:Bool = false 16 | 17 | class func check() { 18 | GithubUpdate().check() 19 | } 20 | 21 | class func check(install:Bool) { 22 | let instance = GithubUpdate() 23 | instance.install = install 24 | instance.check() 25 | } 26 | 27 | func check() { 28 | self.getBundleInfo() 29 | self.getGithubVersion() 30 | } 31 | 32 | func getBundleInfo() { 33 | let bundle = NSBundle.mainBundle() 34 | self.bundleVersion = bundle.objectForInfoDictionaryKey("CFBundleVersion") as? String 35 | self.repoName = bundle.objectForInfoDictionaryKey("GithubRepo") as? String 36 | } 37 | 38 | func getGithubVersion() { 39 | if self.repoName == nil { 40 | return 41 | } 42 | 43 | let url = NSURL(string: "https://api.github.com/repos/\(self.repoName!)/tags") 44 | let request = NSURLRequest(URL: url!) 45 | 46 | NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (response, data, error) in 47 | if data == nil || error != nil { 48 | return 49 | } 50 | 51 | if let tags = (try? NSJSONSerialization.JSONObjectWithData(data!, options: [])) as? NSArray { 52 | 53 | if tags.count > 0 { 54 | let lastTag = tags[0]["name"] as! String 55 | 56 | print("Latest version is \(lastTag)") 57 | 58 | if EDSemver(string: lastTag).isGreaterThan(EDSemver(string: self.bundleVersion!)) { 59 | NSUserDefaults.standardUserDefaults().setValue("{\"data\":true}", forKey: "update_available") 60 | self.download(lastTag) 61 | } else { 62 | NSUserDefaults.standardUserDefaults().setValue("{\"data\":false}", forKey: "update_available") 63 | } 64 | } 65 | } 66 | 67 | } 68 | } 69 | 70 | func download(tag:String) { 71 | let fileManager = NSFileManager.defaultManager() 72 | let url = NSURL(string: "https://github.com/tadeuzagallo/GithubPulse/raw/\(tag)/dist/GithubPulse.zip") 73 | let request = NSURLRequest(URL: url!) 74 | let folder = NSBundle.mainBundle().bundleURL.URLByAppendingPathComponent("Contents/Versions") 75 | 76 | if !fileManager.fileExistsAtPath(folder.path!) { 77 | do { 78 | try fileManager.createDirectoryAtPath(folder.path!, withIntermediateDirectories: true, attributes: nil) 79 | } catch _ {} 80 | } 81 | 82 | let path = folder.URLByAppendingPathComponent("\(tag).zip").path! 83 | 84 | if !fileManager.fileExistsAtPath(path) { 85 | print("Downloading \(tag)...") 86 | NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { (_, data, _) in 87 | print("Download complete!") 88 | data?.writeToFile(path, atomically: true) 89 | if self.install { 90 | self.extract(folder, tag: tag, path: path) 91 | } 92 | } 93 | } else { 94 | print("Version \(tag) is already on the cache!") 95 | if self.install { 96 | self.extract(folder, tag: tag, path: path) 97 | } 98 | } 99 | } 100 | 101 | func extract(folder:NSURL, tag:String, path:String) { 102 | let fileManager = NSFileManager.defaultManager() 103 | let versionFolder = folder.URLByAppendingPathComponent(tag).path! 104 | 105 | if !fileManager.fileExistsAtPath(versionFolder) { 106 | do { 107 | try fileManager.createDirectoryAtPath(versionFolder, withIntermediateDirectories: false, attributes: nil) 108 | } catch _ { 109 | } 110 | print("Unziping \(tag) to \(versionFolder)") 111 | SSZipArchive.unzipFileAtPath(path, toDestination: versionFolder) 112 | } 113 | 114 | self.copy(tag) 115 | } 116 | 117 | func copy(tag:String) { 118 | let relaunchPath = NSBundle.mainBundle().executablePath 119 | let currentPath = NSBundle.mainBundle().bundleURL 120 | print("Replacing old version by \(tag)") 121 | system("rm -rf /tmp/GithubPulse.app && mv \(currentPath.path!) /tmp && mv /tmp/GithubPulse.app/Contents/Versions/\(tag)/GithubPulse.app \(currentPath.URLByDeletingLastPathComponent?.path!)") 122 | self.relaunch(relaunchPath!) 123 | } 124 | 125 | func relaunch(path:String) { 126 | NSUserDefaults.standardUserDefaults().setValue("{\"data\":false}", forKey: "update_available") 127 | NSUserDefaults.standardUserDefaults().synchronize() 128 | 129 | print("Relaunching at \(path)...") 130 | NSTask.launchedTaskWithLaunchPath(path, arguments: [String(format: "%d", getpid())]) 131 | exit(0) 132 | } 133 | } -------------------------------------------------------------------------------- /javascript/components/Profile.react.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var GithubApi = require('../github-api'); 3 | 4 | var ProfileInfo = require('./ProfileInfo.react'); 5 | var ActivityGraph = require('./ActivityGraph.react'); 6 | var Stats = require('./Stats.react'); 7 | var Config = require('./Config.react'); 8 | var pkg = require('../../package.json'); 9 | 10 | require('../styles/Profile'); 11 | 12 | var Profile = React.createClass({ 13 | getInitialState() { 14 | return { 15 | avatar_url: 'https://secure.gravatar.com/avatar?size=100', 16 | login: '', 17 | name: '', 18 | public_repos: 0, 19 | followers: 0, 20 | streak: 0, 21 | today: 0, 22 | lastUpdatedAt: '', 23 | commits: [], 24 | _fetchingUserInfo: true, 25 | _fetchingUserContributions: true, 26 | updateAvailable: false 27 | }; 28 | }, 29 | render() { 30 | var className = this.state._fetchUserInfo || this.state._fetchingUserContributions ? 'rotate' : ''; 31 | 32 | return ( 33 |
34 | 35 |
36 | Github Pulse 37 |
38 |
39 | 44 | 45 | 50 |
51 |
52 | 56 |
57 | Last updated at:  58 | { this.state.lastUpdatedAt } 59 |
60 |
61 | v{ pkg.version } 62 | { this.state.updateAvailable ? : "" } 63 |
64 |
65 |
66 | ); 67 | }, 68 | componentDidMount() { 69 | window.update = this._update; 70 | this._update(false); 71 | }, 72 | componentWillUnmount() { 73 | window.update = null; 74 | }, 75 | _update(force) { 76 | this.setState({ 77 | _fetchingUserInfo: true, 78 | _fetchingUserContributions: true 79 | }); 80 | 81 | this._fetchUserInfo(force); 82 | this._fetchUserContributions(force); 83 | this._checkForUpdates(); 84 | }, 85 | _fetchUserInfo(force) { 86 | var username = this.props.params.username; 87 | 88 | var callback = (userInfo) => { 89 | if (userInfo) { 90 | if (userInfo.updated_at !== this.state.updated_at) { 91 | userInfo._fetchUserInfo = false; 92 | this.setState(userInfo); 93 | } 94 | } else { 95 | GithubApi.get('users', username, (err, result) => { 96 | Utils.save(['user_info', username], result); 97 | result._fetchUserInfo = false; 98 | this.setState(result); 99 | }); 100 | } 101 | }; 102 | 103 | if (force) { 104 | callback(false); 105 | } else { 106 | Utils.fetch(['user_info', username], 15*60*1000, callback); 107 | } 108 | }, 109 | _fetchUserContributions(force) { 110 | var username = this.props.params.username; 111 | 112 | var callback = (userContributions, time) => { 113 | if (userContributions) { 114 | userContributions.lastUpdatedAt = new Date(time).toTimeString().split(' ').shift(); 115 | if (userContributions.lastUpdatedAt !== this.state.lastUpdatedAt) { 116 | userContributions._fetchingUserContributions = false; 117 | this.setState(userContributions); 118 | } else { 119 | this.setState({ _fetchingUserContributions: false }); 120 | } 121 | } else { 122 | Utils.contributions(username, (success, today, streak, commits) => { 123 | if (!success) { 124 | this.setState({ 125 | _fetchingUserContributions: false, 126 | lastUpdatedAt: this.state.lastUpdatedAt.replace(' (offline)', '') + ' (offline)' 127 | }); 128 | return; 129 | } 130 | 131 | var newState = { 132 | streak: streak, 133 | commits: commits, 134 | today: today, 135 | _fetchingUserContributions: false, 136 | lastUpdatedAt: new Date().toTimeString().split(' ').shift() 137 | }; 138 | 139 | Utils.save(['user_contributions', username], newState); 140 | this.setState(newState); 141 | }); 142 | } 143 | }; 144 | 145 | if (force) { 146 | callback(false); 147 | } else { 148 | Utils.fetch(['user_contributions', username], 15*60*1000, callback); 149 | } 150 | }, 151 | _checkForUpdates() { 152 | Utils.fetch('update_available', function (updateAvailable) { 153 | this.setState({ 154 | updateAvailable: updateAvailable 155 | }); 156 | }.bind(this)); 157 | }, 158 | _updateVersion() { 159 | Utils.raw('update()'); 160 | } 161 | }); 162 | 163 | module.exports = Profile; 164 | -------------------------------------------------------------------------------- /widget/GithubPulse/Controllers/ContentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.swift 3 | // GithubPulse 4 | // 5 | // Created by Tadeu Zagallo on 12/28/14. 6 | // Copyright (c) 2014 Tadeu Zagallo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import WebKit 11 | 12 | class ContentViewController: NSViewController, NSXMLParserDelegate, WebPolicyDelegate { 13 | @IBOutlet weak var webView:WebView? 14 | @IBOutlet weak var lastUpdate:NSTextField? 15 | 16 | var regex = try? NSRegularExpression(pattern: "^osx:(\\w+)\\((.*)\\)$", options: NSRegularExpressionOptions.CaseInsensitive) 17 | var calls: [String: [String] -> Void] 18 | 19 | func loadCalls() { 20 | self.calls = [:] 21 | self.calls["contributions"] = { (args) in 22 | Contributions.fetch(args[0]) { (success, commits, streak, today) in 23 | if success { 24 | if args.count < 2 || args[1] == "true" { 25 | NSNotificationCenter.defaultCenter().postNotificationName("check_icon", object: nil, userInfo: ["today": today]) 26 | } 27 | } 28 | let _ = self.webView?.stringByEvaluatingJavaScriptFromString("contributions(\"\(args[0])\", \(success), \(today),\(streak),\(commits))") 29 | } 30 | } 31 | 32 | self.calls["set"] = { (args) in 33 | let userDefaults = NSUserDefaults.standardUserDefaults() 34 | userDefaults.setValue(args[1], forKey: args[0]) 35 | userDefaults.synchronize() 36 | 37 | if args[0] == "username" { 38 | NSNotificationCenter.defaultCenter().postNotificationName("check_username", object: self, userInfo: nil) 39 | } 40 | 41 | } 42 | 43 | self.calls["get"] = { (args) in 44 | var value = NSUserDefaults.standardUserDefaults().valueForKey(args[0]) as? String 45 | 46 | if value == nil { 47 | value = "" 48 | } 49 | 50 | let key = args[0].stringByReplacingOccurrencesOfString("'", withString: "\\'", options: [], range: nil) 51 | let v = value!.stringByReplacingOccurrencesOfString("'", withString: "\\'", options: [], range: nil) 52 | 53 | self.webView?.stringByEvaluatingJavaScriptFromString("get('\(key)', '\(v)', \(args[1]))"); 54 | } 55 | 56 | self.calls["remove"] = { (args) in 57 | let userDefaults = NSUserDefaults.standardUserDefaults() 58 | userDefaults.removeObjectForKey(args[0]) 59 | userDefaults.synchronize() 60 | 61 | if args[0] == "username" { 62 | NSNotificationCenter.defaultCenter().postNotificationName("check_username", object: self, userInfo: nil) 63 | } 64 | } 65 | 66 | self.calls["check_login"] = { (args) in 67 | let active = NSBundle.mainBundle().isLoginItem() 68 | self.webView?.stringByEvaluatingJavaScriptFromString("raw('check_login', \(active))") 69 | } 70 | 71 | self.calls["toggle_login"] = { (args) in 72 | if NSBundle.mainBundle().isLoginItem() { 73 | NSBundle.mainBundle().removeFromLoginItems() 74 | } else { 75 | NSBundle.mainBundle().addToLoginItems() 76 | } 77 | } 78 | 79 | self.calls["quit"] = { (args) in 80 | NSApplication.sharedApplication().terminate(self) 81 | } 82 | 83 | self.calls["update"] = { (args) in 84 | GithubUpdate.check(true) 85 | } 86 | 87 | self.calls["open_url"] = { (args) in 88 | if let checkURL = NSURL(string: args[0]) { 89 | NSWorkspace.sharedWorkspace().openURL(checkURL) 90 | } 91 | } 92 | } 93 | 94 | required init?(coder: NSCoder) { 95 | self.calls = [:] 96 | super.init(coder: coder) 97 | self.loadCalls() 98 | } 99 | 100 | override init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 101 | self.calls = [:] 102 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 103 | self.loadCalls() 104 | } 105 | 106 | override func viewDidLoad() { 107 | #if DEBUG 108 | let url = NSURL(string: "http://0.0.0.0:8080")! 109 | #else 110 | let indexPath = NSBundle.mainBundle().pathForResource("index", ofType: "html", inDirectory: "front") 111 | let url = NSURL(fileURLWithPath: indexPath!) 112 | #endif 113 | let request = NSURLRequest(URL: url) 114 | 115 | self.webView?.policyDelegate = self 116 | self.webView?.drawsBackground = false 117 | self.webView?.wantsLayer = true 118 | self.webView?.layer?.cornerRadius = 5 119 | self.webView?.layer?.masksToBounds = true 120 | 121 | self.webView?.mainFrame.loadRequest(request) 122 | 123 | super.viewDidLoad() 124 | } 125 | 126 | @IBAction func refresh(sender: AnyObject?) { 127 | self.webView?.reload(sender) 128 | } 129 | 130 | func webView(webView: WebView!, decidePolicyForNavigationAction actionInformation: [NSObject : AnyObject]!, request: NSURLRequest!, frame: WebFrame!, decisionListener listener: WebPolicyDecisionListener!) { 131 | let url:String = request.URL!.absoluteString.stringByRemovingPercentEncoding! 132 | 133 | if url.hasPrefix("osx:") { 134 | let matches = self.regex?.matchesInString(url, options: [], range: NSMakeRange(0, url.characters.count)) 135 | if let match = matches?[0] { 136 | let fn = (url as NSString).substringWithRange(match.rangeAtIndex(1)) 137 | let args = (url as NSString).substringWithRange(match.rangeAtIndex(2)).componentsSeparatedByString("%%") 138 | 139 | #if DEBUG 140 | print(fn, args) 141 | #endif 142 | 143 | let closure = self.calls[fn] 144 | closure?(args) 145 | } 146 | } else if (url.hasPrefix("log:")) { 147 | #if DEBUG 148 | print(url) 149 | #endif 150 | } else { 151 | listener.use() 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /widget/GithubPulse/Other Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GithubPulse 4 | // 5 | // Created by Tadeu Zagallo on 12/27/14. 6 | // Copyright (c) 2014 Tadeu Zagallo. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | private var myContext = 0 12 | 13 | @NSApplicationMain 14 | 15 | class AppDelegate: NSObject, NSApplicationDelegate { 16 | var open:Bool = false 17 | var contentViewController:ContentViewController 18 | var popover:INPopoverController 19 | var statusItem:NSStatusItem! 20 | var statusButton:CustomButton! 21 | var timer:NSTimer! 22 | 23 | override init() { 24 | self.contentViewController = ContentViewController(nibName: "ContentViewController", bundle: nil)! 25 | self.popover = INPopoverController(contentViewController: self.contentViewController) 26 | 27 | self.popover.animates = false; 28 | self.popover.color = NSColor.whiteColor() 29 | self.popover.borderWidth = 1 30 | self.popover.cornerRadius = 5; 31 | self.popover.borderColor = NSColor(calibratedWhite: 0.76, alpha: 1) 32 | 33 | super.init() 34 | 35 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "_checkUsernameNotification:", name: "check_username", object: nil) 36 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "_checkIconNotification:", name: "check_icon", object: nil) 37 | NSDistributedNotificationCenter.defaultCenter().addObserver(self, selector: "_darkModeChanged:", name: "AppleInterfaceThemeChangedNotification", object: nil) 38 | 39 | } 40 | 41 | func applicationDidFinishLaunching(aNotification: NSNotification) { 42 | self.statusButton = CustomButton(frame: NSRect(x: 0, y: 0, width: 32, height: 24)) 43 | self.statusButton.bordered = false 44 | self.statusButton.target = self 45 | self.statusButton.action = "toggle:" 46 | self.updateIcon(1) 47 | self.statusButton.rightAction = { (_) in 48 | self.contentViewController.refresh(nil) 49 | } 50 | 51 | self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(32) 52 | self.statusItem.title = "Github Pulse" 53 | self.statusItem.highlightMode = true 54 | self.statusItem.view = self.statusButton 55 | 56 | self.timer = NSTimer(fireDate: NSDate(), interval: 15*60, target: self, selector: "checkForCommits", userInfo: nil, repeats: true) 57 | NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSDefaultRunLoopMode) 58 | } 59 | 60 | deinit { 61 | NSNotificationCenter.defaultCenter().removeObserver(self) 62 | NSDistributedNotificationCenter.defaultCenter().removeObserver(self) 63 | self.timer.invalidate() 64 | self.timer = nil 65 | } 66 | 67 | func checkForCommits() { 68 | GithubUpdate.check() 69 | 70 | if let username = parseData("username") as? String { 71 | self.fetchCommits(username) 72 | } 73 | } 74 | 75 | func parseData(key: String) -> AnyObject? { 76 | if let input = NSUserDefaults.standardUserDefaults().valueForKey(key) as? String { 77 | if let data = input.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) { 78 | if let object = (try? NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)) as? NSDictionary { 79 | return object["data"] 80 | } 81 | } 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func fetchCommits(username: String) { 88 | let dontNotify = parseData("dont_notify") as? Bool 89 | 90 | Contributions.fetch(username) { (success, _, _, today) in 91 | if success { 92 | self.updateIcon(today) 93 | 94 | 95 | if today == 0 && (dontNotify == nil || !dontNotify!) { 96 | self.checkForNotification() 97 | } 98 | } 99 | } 100 | } 101 | 102 | var lastIconCount = 0 103 | func updateIcon(_count: Int) { 104 | var count:Int 105 | 106 | if _count == -1 { 107 | count = self.lastIconCount 108 | } else { 109 | count = _count 110 | self.lastIconCount = count 111 | } 112 | 113 | var imageName = count == 0 ? "icon_notification" : "icon" 114 | 115 | if let domain = NSUserDefaults.standardUserDefaults().persistentDomainForName(NSGlobalDomain) { 116 | if let style = domain["AppleInterfaceStyle"] as? String { 117 | if style == "Dark" { 118 | imageName += "_dark" 119 | } 120 | } 121 | } 122 | 123 | self.statusButton.image = NSImage(named: imageName) 124 | } 125 | 126 | func checkForNotification() { 127 | let userDefaults = NSUserDefaults.standardUserDefaults() 128 | let now = NSDate() 129 | let components = NSCalendar.currentCalendar().components(NSCalendarUnit.Hour, fromDate: now) 130 | 131 | 132 | if components.hour >= 18 { 133 | let lastNotification = userDefaults.valueForKey("last_notification") as? NSDate 134 | let todayStart = NSCalendar.currentCalendar().dateBySettingHour(1, minute: 0, second: 0, ofDate: now, options: []) 135 | 136 | if lastNotification == nil || todayStart!.timeIntervalSinceDate(lastNotification!) > 0 { 137 | let notification = NSUserNotification() 138 | notification.title = "You haven't committed yet today..."; 139 | notification.subtitle = "Rush to keep your streak going!" 140 | 141 | let notificationCenter = NSUserNotificationCenter.defaultUserNotificationCenter() 142 | notificationCenter.scheduleNotification(notification) 143 | 144 | userDefaults.setValue(now, forKey: "last_notification") 145 | } 146 | } 147 | } 148 | 149 | func applicationWillResignActive(notification: NSNotification) { 150 | self.open = false 151 | self.popover.closePopover(nil) 152 | } 153 | 154 | func toggle(_: AnyObject) { 155 | if (self.open) { 156 | self.popover.closePopover(nil) 157 | } else { 158 | let controller = self.popover.contentViewController as! ContentViewController 159 | controller.webView?.stringByEvaluatingJavaScriptFromString("update(false)") 160 | 161 | self.popover.presentPopoverFromRect(self.statusItem.view!.bounds, inView: self.statusItem.view!, preferredArrowDirection: INPopoverArrowDirection.Up, anchorsToPositionView: true) 162 | NSApp.activateIgnoringOtherApps(true) 163 | } 164 | 165 | self.open = !self.open 166 | } 167 | 168 | func _checkIconNotification(notification:NSNotification) { 169 | self.updateIcon(notification.userInfo?["today"] as! Int) 170 | } 171 | 172 | func _darkModeChanged(notification:NSNotification) { 173 | self.updateIcon(-1) 174 | } 175 | 176 | func _checkUsernameNotification(notification:NSNotification) { 177 | if let username = self.parseData("username") as? String { 178 | self.fetchCommits(username) 179 | } else { 180 | self.updateIcon(1) 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /chrome_extension/js/utils.js: -------------------------------------------------------------------------------- 1 | window.Utils = (function () { 2 | var Utils = {}; 3 | 4 | Utils.log = function () { 5 | console.log.apply(console, arguments); 6 | }; 7 | 8 | Utils.save = function (key, value) { 9 | if (Array.isArray(key)) { 10 | key = key.join('/'); 11 | } 12 | 13 | var time = Date.now(); 14 | var data = {}; 15 | var stringifiedValue = JSON.stringify(value); 16 | 17 | var quota = chrome.storage.sync.QUOTA_BYTES_PER_ITEM - key.length - 3; 18 | if (stringifiedValue.length > quota) { 19 | var n = 0; 20 | var size = 0; 21 | var value = ''; 22 | for (var i = 0; i < stringifiedValue.length; i++) { 23 | var chr = stringifiedValue.charAt(i); 24 | var code = stringifiedValue.charCodeAt(i); 25 | var s = code > 0xFF || chr == '"' || chr == '\\' ? 2 : 1; 26 | size += s; 27 | if (size > quota) { 28 | data[key + n++] = value; 29 | value = stringifiedValue[i]; 30 | size = s; 31 | } else { 32 | value += stringifiedValue[i]; 33 | } 34 | } 35 | data[key + n++] = value; 36 | data[key] = JSON.stringify({ time: time, n: n }); 37 | } else { 38 | data[key] = JSON.stringify({ 39 | time: time, 40 | data: value, 41 | }); 42 | } 43 | 44 | chrome.storage.sync.set(data); 45 | }; 46 | 47 | Utils.fetch = function (key, expiration, callback) { 48 | if (Array.isArray(key)) { 49 | key = key.join('/'); 50 | } 51 | 52 | if (typeof expiration === 'function') { 53 | callback = expiration; 54 | expiration = undefined; 55 | } 56 | 57 | chrome.storage.sync.get(key, function (r) { 58 | var value = r[key]; 59 | 60 | var item = value && JSON.parse(value); 61 | var time = null; 62 | 63 | if (expiration !== -1 && item && Date.now() - item.time > expiration) { 64 | done(null); 65 | } else if (item) { 66 | time = item.time; 67 | if (item.hasOwnProperty('n')) { 68 | var num = item.n; 69 | var keys = [] 70 | for (var i = 0; i < num; i++) { 71 | keys.push(key+i); 72 | } 73 | chrome.storage.sync.get(keys, function (r) { 74 | var data = keys.map(function (key) { 75 | return r[key]; 76 | }).join(''); 77 | var item = JSON.parse(data); 78 | done(item); 79 | }); 80 | } else { 81 | item = item.data; 82 | done(item); 83 | } 84 | } else { 85 | done(null); 86 | } 87 | 88 | function done(item) { 89 | if (key.indexOf('user_contributions') === 0 && item) { 90 | Utils.updateIcon(item.today); 91 | } 92 | 93 | callback(item, time); 94 | } 95 | }); 96 | }; 97 | 98 | Utils.raw = function (expression, callback) { 99 | //if (callback) { 100 | //fnName = expression.split('(').shift(); 101 | //rawCallbacks[fnName] = callback; 102 | //} 103 | 104 | //Utils.redirect('osx:' + expression); 105 | }; 106 | 107 | Utils.clear = function (key) { 108 | if (Array.isArray(key)) { 109 | key = key.join('/'); 110 | } 111 | 112 | if (key === 'username') { 113 | this.updateIcon(1); 114 | } 115 | 116 | chrome.storage.sync.remove(key); 117 | }; 118 | 119 | Utils.openURL = function (url) { 120 | chrome.tabs.create({ url: url }); 121 | }; 122 | 123 | Utils.streakRegex = /Current streak<\/span>\s*]*?>(\d+)\s*days/i; 124 | Utils.contributions = function (username, callback, skipUpdateIcon) { 125 | var request = new XMLHttpRequest(); 126 | request.onload = function () { 127 | var body = this.responseText; 128 | var index = body.indexOf(''); 145 | var svg = body.substr(0, closeIndex + 6); 146 | finishSVG(svg); 147 | 148 | function finishSVG(svg) { 149 | var commits = Utils.parseSVG(svg); 150 | var today = commits.length && commits[commits.length - 1]; 151 | 152 | if (!skipUpdateIcon) { 153 | Utils.updateIcon(today); 154 | } 155 | 156 | callback(commits.length === 30, today, streak, commits); 157 | } 158 | }; 159 | 160 | request.onerror = function () { 161 | callback(false, null, null, null); 162 | console.error(this.statusText); 163 | }; 164 | 165 | request.open('GET', `https://github.com/${username}`, true); 166 | request.send(null); 167 | }; 168 | 169 | Utils.parseSVG = function (svg) { 170 | var parser = new DOMParser(); 171 | var root = parser.parseFromString(svg, "image/svg+xml"); 172 | var nodeCounts = root.querySelectorAll('rect'); 173 | return [].slice.call(nodeCounts, -30).map(function (node) { 174 | return parseInt(node.getAttribute('data-count'), 10); 175 | }); 176 | }; 177 | 178 | Utils.graphContributions = function (username, callback) { 179 | var request = new XMLHttpRequest(); 180 | request.onload = function () { 181 | callback(this.responseText); 182 | }; 183 | 184 | request.onerror = function () { 185 | callback(null); 186 | console.error(this.statusText); 187 | }; 188 | 189 | request.open('GET', 'https://github.com/users/' + username + '/contributions', true); 190 | //request.open('GET', 'http://localhost:8081/contributions.svg', true); 191 | request.send(null); 192 | }; 193 | 194 | chrome.webRequest.onBeforeSendHeaders.addListener(function (details) { 195 | for (var i = 0; i < details.requestHeaders.length; ++i) { 196 | if (details.requestHeaders[i].name === 'Cookie') { 197 | details.requestHeaders.splice(i, 1); 198 | break; 199 | } 200 | } 201 | return { requestHeaders: details.requestHeaders }; 202 | }, { 203 | urls: ['https://github.com/users/*/contributions'] 204 | }, ['blocking', 'requestHeaders']); 205 | 206 | Utils.updateIcon = function (commits) { 207 | var color = commits === 0 ? 'red' : 'blue'; 208 | var imgs = {}; 209 | [19, 38].forEach(function (size) { 210 | imgs[size] = '../images/' + color + '/icon' + size + '.png'; 211 | }); 212 | 213 | chrome.browserAction.setIcon({ 214 | path: imgs 215 | }); 216 | }; 217 | 218 | Utils.terminate = function () { 219 | chrome.management.setEnabled(chrome.i18n.getMessage("@@extension_id"), false); 220 | window.close(); 221 | }; 222 | 223 | Utils.quit = function () { 224 | chrome.notifications.create('bye', { 225 | type: 'basic', 226 | iconUrl: '../images/icons/icon128.png', 227 | title: 'Github Pulse is being disabled...', 228 | message: 'To re-enable it navigate to chrome:extensions', 229 | priority: 2, 230 | buttons: [{ title: 'cancel' }, { title: 'OK' }] 231 | }, function (id) { }); 232 | 233 | chrome.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) { 234 | if (notificationId === 'bye') { 235 | if (buttonIndex === 1) { 236 | Utils.terminate(); 237 | } else { 238 | chrome.notifications.clear('bye', function () {}); 239 | } 240 | } 241 | }); 242 | }; 243 | 244 | document.body.className = 'chrome'; 245 | 246 | return Utils; 247 | })(); 248 | -------------------------------------------------------------------------------- /front/octicons/octicons.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'octicons'; 3 | src: url('octicons.woff') format('woff'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | /* 9 | 10 | .octicon is optimized for 16px. 11 | .mega-octicon is optimized for 32px but can be used larger. 12 | 13 | */ 14 | .octicon, .mega-octicon { 15 | font: normal normal normal 16px/1 octicons; 16 | display: inline-block; 17 | text-decoration: none; 18 | text-rendering: auto; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | -webkit-user-select: none; 22 | -moz-user-select: none; 23 | -ms-user-select: none; 24 | user-select: none; 25 | } 26 | .mega-octicon { font-size: 32px; } 27 | 28 | 29 | .octicon-alert:before { content: '\f02d'} /*  */ 30 | .octicon-alignment-align:before { content: '\f08a'} /*  */ 31 | .octicon-alignment-aligned-to:before { content: '\f08e'} /*  */ 32 | .octicon-alignment-unalign:before { content: '\f08b'} /*  */ 33 | .octicon-arrow-down:before { content: '\f03f'} /*  */ 34 | .octicon-arrow-left:before { content: '\f040'} /*  */ 35 | .octicon-arrow-right:before { content: '\f03e'} /*  */ 36 | .octicon-arrow-small-down:before { content: '\f0a0'} /*  */ 37 | .octicon-arrow-small-left:before { content: '\f0a1'} /*  */ 38 | .octicon-arrow-small-right:before { content: '\f071'} /*  */ 39 | .octicon-arrow-small-up:before { content: '\f09f'} /*  */ 40 | .octicon-arrow-up:before { content: '\f03d'} /*  */ 41 | .octicon-beer:before { content: '\f069'} /*  */ 42 | .octicon-book:before { content: '\f007'} /*  */ 43 | .octicon-bookmark:before { content: '\f07b'} /*  */ 44 | .octicon-briefcase:before { content: '\f0d3'} /*  */ 45 | .octicon-broadcast:before { content: '\f048'} /*  */ 46 | .octicon-browser:before { content: '\f0c5'} /*  */ 47 | .octicon-bug:before { content: '\f091'} /*  */ 48 | .octicon-calendar:before { content: '\f068'} /*  */ 49 | .octicon-check:before { content: '\f03a'} /*  */ 50 | .octicon-checklist:before { content: '\f076'} /*  */ 51 | .octicon-chevron-down:before { content: '\f0a3'} /*  */ 52 | .octicon-chevron-left:before { content: '\f0a4'} /*  */ 53 | .octicon-chevron-right:before { content: '\f078'} /*  */ 54 | .octicon-chevron-up:before { content: '\f0a2'} /*  */ 55 | .octicon-circle-slash:before { content: '\f084'} /*  */ 56 | .octicon-circuit-board:before { content: '\f0d6'} /*  */ 57 | .octicon-clippy:before { content: '\f035'} /*  */ 58 | .octicon-clock:before { content: '\f046'} /*  */ 59 | .octicon-cloud-download:before { content: '\f00b'} /*  */ 60 | .octicon-cloud-upload:before { content: '\f00c'} /*  */ 61 | .octicon-code:before { content: '\f05f'} /*  */ 62 | .octicon-color-mode:before { content: '\f065'} /*  */ 63 | .octicon-comment-add:before, 64 | .octicon-comment:before { content: '\f02b'} /*  */ 65 | .octicon-comment-discussion:before { content: '\f04f'} /*  */ 66 | .octicon-credit-card:before { content: '\f045'} /*  */ 67 | .octicon-dash:before { content: '\f0ca'} /*  */ 68 | .octicon-dashboard:before { content: '\f07d'} /*  */ 69 | .octicon-database:before { content: '\f096'} /*  */ 70 | .octicon-device-camera:before { content: '\f056'} /*  */ 71 | .octicon-device-camera-video:before { content: '\f057'} /*  */ 72 | .octicon-device-desktop:before { content: '\f27c'} /*  */ 73 | .octicon-device-mobile:before { content: '\f038'} /*  */ 74 | .octicon-diff:before { content: '\f04d'} /*  */ 75 | .octicon-diff-added:before { content: '\f06b'} /*  */ 76 | .octicon-diff-ignored:before { content: '\f099'} /*  */ 77 | .octicon-diff-modified:before { content: '\f06d'} /*  */ 78 | .octicon-diff-removed:before { content: '\f06c'} /*  */ 79 | .octicon-diff-renamed:before { content: '\f06e'} /*  */ 80 | .octicon-ellipsis:before { content: '\f09a'} /*  */ 81 | .octicon-eye-unwatch:before, 82 | .octicon-eye-watch:before, 83 | .octicon-eye:before { content: '\f04e'} /*  */ 84 | .octicon-file-binary:before { content: '\f094'} /*  */ 85 | .octicon-file-code:before { content: '\f010'} /*  */ 86 | .octicon-file-directory:before { content: '\f016'} /*  */ 87 | .octicon-file-media:before { content: '\f012'} /*  */ 88 | .octicon-file-pdf:before { content: '\f014'} /*  */ 89 | .octicon-file-submodule:before { content: '\f017'} /*  */ 90 | .octicon-file-symlink-directory:before { content: '\f0b1'} /*  */ 91 | .octicon-file-symlink-file:before { content: '\f0b0'} /*  */ 92 | .octicon-file-text:before { content: '\f011'} /*  */ 93 | .octicon-file-zip:before { content: '\f013'} /*  */ 94 | .octicon-flame:before { content: '\f0d2'} /*  */ 95 | .octicon-fold:before { content: '\f0cc'} /*  */ 96 | .octicon-gear:before { content: '\f02f'} /*  */ 97 | .octicon-gift:before { content: '\f042'} /*  */ 98 | .octicon-gist:before { content: '\f00e'} /*  */ 99 | .octicon-gist-secret:before { content: '\f08c'} /*  */ 100 | .octicon-git-branch-create:before, 101 | .octicon-git-branch-delete:before, 102 | .octicon-git-branch:before { content: '\f020'} /*  */ 103 | .octicon-git-commit:before { content: '\f01f'} /*  */ 104 | .octicon-git-compare:before { content: '\f0ac'} /*  */ 105 | .octicon-git-merge:before { content: '\f023'} /*  */ 106 | .octicon-git-pull-request-abandoned:before, 107 | .octicon-git-pull-request:before { content: '\f009'} /*  */ 108 | .octicon-globe:before { content: '\f0b6'} /*  */ 109 | .octicon-graph:before { content: '\f043'} /*  */ 110 | .octicon-heart:before { content: '\2665'} /* ♥ */ 111 | .octicon-history:before { content: '\f07e'} /*  */ 112 | .octicon-home:before { content: '\f08d'} /*  */ 113 | .octicon-horizontal-rule:before { content: '\f070'} /*  */ 114 | .octicon-hourglass:before { content: '\f09e'} /*  */ 115 | .octicon-hubot:before { content: '\f09d'} /*  */ 116 | .octicon-inbox:before { content: '\f0cf'} /*  */ 117 | .octicon-info:before { content: '\f059'} /*  */ 118 | .octicon-issue-closed:before { content: '\f028'} /*  */ 119 | .octicon-issue-opened:before { content: '\f026'} /*  */ 120 | .octicon-issue-reopened:before { content: '\f027'} /*  */ 121 | .octicon-jersey:before { content: '\f019'} /*  */ 122 | .octicon-jump-down:before { content: '\f072'} /*  */ 123 | .octicon-jump-left:before { content: '\f0a5'} /*  */ 124 | .octicon-jump-right:before { content: '\f0a6'} /*  */ 125 | .octicon-jump-up:before { content: '\f073'} /*  */ 126 | .octicon-key:before { content: '\f049'} /*  */ 127 | .octicon-keyboard:before { content: '\f00d'} /*  */ 128 | .octicon-law:before { content: '\f0d8'} /* */ 129 | .octicon-light-bulb:before { content: '\f000'} /*  */ 130 | .octicon-link:before { content: '\f05c'} /*  */ 131 | .octicon-link-external:before { content: '\f07f'} /*  */ 132 | .octicon-list-ordered:before { content: '\f062'} /*  */ 133 | .octicon-list-unordered:before { content: '\f061'} /*  */ 134 | .octicon-location:before { content: '\f060'} /*  */ 135 | .octicon-gist-private:before, 136 | .octicon-mirror-private:before, 137 | .octicon-git-fork-private:before, 138 | .octicon-lock:before { content: '\f06a'} /*  */ 139 | .octicon-logo-github:before { content: '\f092'} /*  */ 140 | .octicon-mail:before { content: '\f03b'} /*  */ 141 | .octicon-mail-read:before { content: '\f03c'} /*  */ 142 | .octicon-mail-reply:before { content: '\f051'} /*  */ 143 | .octicon-mark-github:before { content: '\f00a'} /*  */ 144 | .octicon-markdown:before { content: '\f0c9'} /*  */ 145 | .octicon-megaphone:before { content: '\f077'} /*  */ 146 | .octicon-mention:before { content: '\f0be'} /*  */ 147 | .octicon-microscope:before { content: '\f089'} /*  */ 148 | .octicon-milestone:before { content: '\f075'} /*  */ 149 | .octicon-mirror-public:before, 150 | .octicon-mirror:before { content: '\f024'} /*  */ 151 | .octicon-mortar-board:before { content: '\f0d7'} /* */ 152 | .octicon-move-down:before { content: '\f0a8'} /*  */ 153 | .octicon-move-left:before { content: '\f074'} /*  */ 154 | .octicon-move-right:before { content: '\f0a9'} /*  */ 155 | .octicon-move-up:before { content: '\f0a7'} /*  */ 156 | .octicon-mute:before { content: '\f080'} /*  */ 157 | .octicon-no-newline:before { content: '\f09c'} /*  */ 158 | .octicon-octoface:before { content: '\f008'} /*  */ 159 | .octicon-organization:before { content: '\f037'} /*  */ 160 | .octicon-package:before { content: '\f0c4'} /*  */ 161 | .octicon-paintcan:before { content: '\f0d1'} /*  */ 162 | .octicon-pencil:before { content: '\f058'} /*  */ 163 | .octicon-person-add:before, 164 | .octicon-person-follow:before, 165 | .octicon-person:before { content: '\f018'} /*  */ 166 | .octicon-pin:before { content: '\f041'} /*  */ 167 | .octicon-playback-fast-forward:before { content: '\f0bd'} /*  */ 168 | .octicon-playback-pause:before { content: '\f0bb'} /*  */ 169 | .octicon-playback-play:before { content: '\f0bf'} /*  */ 170 | .octicon-playback-rewind:before { content: '\f0bc'} /*  */ 171 | .octicon-plug:before { content: '\f0d4'} /*  */ 172 | .octicon-repo-create:before, 173 | .octicon-gist-new:before, 174 | .octicon-file-directory-create:before, 175 | .octicon-file-add:before, 176 | .octicon-plus:before { content: '\f05d'} /*  */ 177 | .octicon-podium:before { content: '\f0af'} /*  */ 178 | .octicon-primitive-dot:before { content: '\f052'} /*  */ 179 | .octicon-primitive-square:before { content: '\f053'} /*  */ 180 | .octicon-pulse:before { content: '\f085'} /*  */ 181 | .octicon-puzzle:before { content: '\f0c0'} /*  */ 182 | .octicon-question:before { content: '\f02c'} /*  */ 183 | .octicon-quote:before { content: '\f063'} /*  */ 184 | .octicon-radio-tower:before { content: '\f030'} /*  */ 185 | .octicon-repo-delete:before, 186 | .octicon-repo:before { content: '\f001'} /*  */ 187 | .octicon-repo-clone:before { content: '\f04c'} /*  */ 188 | .octicon-repo-force-push:before { content: '\f04a'} /*  */ 189 | .octicon-gist-fork:before, 190 | .octicon-repo-forked:before { content: '\f002'} /*  */ 191 | .octicon-repo-pull:before { content: '\f006'} /*  */ 192 | .octicon-repo-push:before { content: '\f005'} /*  */ 193 | .octicon-rocket:before { content: '\f033'} /*  */ 194 | .octicon-rss:before { content: '\f034'} /*  */ 195 | .octicon-ruby:before { content: '\f047'} /*  */ 196 | .octicon-screen-full:before { content: '\f066'} /*  */ 197 | .octicon-screen-normal:before { content: '\f067'} /*  */ 198 | .octicon-search-save:before, 199 | .octicon-search:before { content: '\f02e'} /*  */ 200 | .octicon-server:before { content: '\f097'} /*  */ 201 | .octicon-settings:before { content: '\f07c'} /*  */ 202 | .octicon-log-in:before, 203 | .octicon-sign-in:before { content: '\f036'} /*  */ 204 | .octicon-log-out:before, 205 | .octicon-sign-out:before { content: '\f032'} /*  */ 206 | .octicon-split:before { content: '\f0c6'} /*  */ 207 | .octicon-squirrel:before { content: '\f0b2'} /*  */ 208 | .octicon-star-add:before, 209 | .octicon-star-delete:before, 210 | .octicon-star:before { content: '\f02a'} /*  */ 211 | .octicon-steps:before { content: '\f0c7'} /*  */ 212 | .octicon-stop:before { content: '\f08f'} /*  */ 213 | .octicon-repo-sync:before, 214 | .octicon-sync:before { content: '\f087'} /*  */ 215 | .octicon-tag-remove:before, 216 | .octicon-tag-add:before, 217 | .octicon-tag:before { content: '\f015'} /*  */ 218 | .octicon-telescope:before { content: '\f088'} /*  */ 219 | .octicon-terminal:before { content: '\f0c8'} /*  */ 220 | .octicon-three-bars:before { content: '\f05e'} /*  */ 221 | .octicon-tools:before { content: '\f031'} /*  */ 222 | .octicon-trashcan:before { content: '\f0d0'} /*  */ 223 | .octicon-triangle-down:before { content: '\f05b'} /*  */ 224 | .octicon-triangle-left:before { content: '\f044'} /*  */ 225 | .octicon-triangle-right:before { content: '\f05a'} /*  */ 226 | .octicon-triangle-up:before { content: '\f0aa'} /*  */ 227 | .octicon-unfold:before { content: '\f039'} /*  */ 228 | .octicon-unmute:before { content: '\f0ba'} /*  */ 229 | .octicon-versions:before { content: '\f064'} /*  */ 230 | .octicon-remove-close:before, 231 | .octicon-x:before { content: '\f081'} /*  */ 232 | .octicon-zap:before { content: '\26A1'} /* ⚡ */ 233 | -------------------------------------------------------------------------------- /widget/GithubPulse.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 10DAAD523F5C7C1C6185D6B9 /* libPods-GithubPulse.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 43C97BE66B33F57FE3EF09C1 /* libPods-GithubPulse.a */; }; 11 | 5C406D7E1A4FC7850005AF61 /* front in Resources */ = {isa = PBXBuildFile; fileRef = 5C406D7D1A4FC7850005AF61 /* front */; }; 12 | 5C6F3C6B1A65782E00181655 /* icon_notification_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 5C6F3C671A65782E00181655 /* icon_notification_dark.png */; }; 13 | 5C6F3C6C1A65782E00181655 /* icon_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 5C6F3C681A65782E00181655 /* icon_dark.png */; }; 14 | 5C6F3C6D1A65782E00181655 /* icon_dark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5C6F3C691A65782E00181655 /* icon_dark@2x.png */; }; 15 | 5C6F3C6E1A65782E00181655 /* icon_notification_dark@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5C6F3C6A1A65782E00181655 /* icon_notification_dark@2x.png */; }; 16 | 5CC58B761A6CB65D0024A418 /* GithubUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC58B751A6CB65D0024A418 /* GithubUpdate.swift */; }; 17 | 5CFF3A161A603B26001B3536 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF3A031A603B25001B3536 /* CustomButton.swift */; }; 18 | 5CFF3A171A603B26001B3536 /* ContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF3A051A603B25001B3536 /* ContentViewController.swift */; }; 19 | 5CFF3A181A603B26001B3536 /* Contributions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF3A071A603B25001B3536 /* Contributions.swift */; }; 20 | 5CFF3A191A603B26001B3536 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFF3A091A603B25001B3536 /* AppDelegate.swift */; }; 21 | 5CFF3A1A1A603B26001B3536 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A0D1A603B25001B3536 /* icon.png */; }; 22 | 5CFF3A1B1A603B26001B3536 /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A0E1A603B25001B3536 /* icon@2x.png */; }; 23 | 5CFF3A1C1A603B26001B3536 /* icon_notification.png in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A0F1A603B25001B3536 /* icon_notification.png */; }; 24 | 5CFF3A1D1A603B26001B3536 /* icon_notification@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A101A603B25001B3536 /* icon_notification@2x.png */; }; 25 | 5CFF3A1E1A603B26001B3536 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A111A603B25001B3536 /* Images.xcassets */; }; 26 | 5CFF3A201A603B26001B3536 /* ContentViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A141A603B26001B3536 /* ContentViewController.xib */; }; 27 | 5CFF3A211A603B26001B3536 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5CFF3A151A603B26001B3536 /* MainMenu.xib */; }; 28 | 5CFF3A231A603B45001B3536 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CFF3A221A603B45001B3536 /* WebKit.framework */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 43C97BE66B33F57FE3EF09C1 /* libPods-GithubPulse.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-GithubPulse.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 537663C2FEED19D550D9B685 /* Pods-GithubPulse.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GithubPulse.release.xcconfig"; path = "Pods/Target Support Files/Pods-GithubPulse/Pods-GithubPulse.release.xcconfig"; sourceTree = ""; }; 34 | 5C406D491A4F93140005AF61 /* GithubPulse.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GithubPulse.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 5C406D7D1A4FC7850005AF61 /* front */ = {isa = PBXFileReference; lastKnownFileType = folder; name = front; path = ../front; sourceTree = ""; }; 36 | 5C6F3C671A65782E00181655 /* icon_notification_dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_notification_dark.png; sourceTree = ""; }; 37 | 5C6F3C681A65782E00181655 /* icon_dark.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_dark.png; sourceTree = ""; }; 38 | 5C6F3C691A65782E00181655 /* icon_dark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_dark@2x.png"; sourceTree = ""; }; 39 | 5C6F3C6A1A65782E00181655 /* icon_notification_dark@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_notification_dark@2x.png"; sourceTree = ""; }; 40 | 5CC58B751A6CB65D0024A418 /* GithubUpdate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GithubUpdate.swift; sourceTree = ""; }; 41 | 5CC58B841A6DCDB20024A418 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 42 | 5CFF3A031A603B25001B3536 /* CustomButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = ""; }; 43 | 5CFF3A051A603B25001B3536 /* ContentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentViewController.swift; sourceTree = ""; }; 44 | 5CFF3A071A603B25001B3536 /* Contributions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Contributions.swift; sourceTree = ""; }; 45 | 5CFF3A091A603B25001B3536 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | 5CFF3A0A1A603B25001B3536 /* BridgeHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BridgeHeader.h; sourceTree = ""; }; 47 | 5CFF3A0D1A603B25001B3536 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; 48 | 5CFF3A0E1A603B25001B3536 /* icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon@2x.png"; sourceTree = ""; }; 49 | 5CFF3A0F1A603B25001B3536 /* icon_notification.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon_notification.png; sourceTree = ""; }; 50 | 5CFF3A101A603B25001B3536 /* icon_notification@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "icon_notification@2x.png"; sourceTree = ""; }; 51 | 5CFF3A111A603B25001B3536 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 52 | 5CFF3A121A603B25001B3536 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 5CFF3A141A603B26001B3536 /* ContentViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContentViewController.xib; sourceTree = ""; }; 54 | 5CFF3A151A603B26001B3536 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 55 | 5CFF3A221A603B45001B3536 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 56 | B2C688CC2B78A1B8230CD0BC /* Pods-GithubPulse.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GithubPulse.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GithubPulse/Pods-GithubPulse.debug.xcconfig"; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | 5C406D461A4F93140005AF61 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 5CFF3A231A603B45001B3536 /* WebKit.framework in Frameworks */, 65 | 10DAAD523F5C7C1C6185D6B9 /* libPods-GithubPulse.a in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 34E85245A6050C883BA59349 /* Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 5CC58B841A6DCDB20024A418 /* Cocoa.framework */, 76 | 5CFF3A221A603B45001B3536 /* WebKit.framework */, 77 | 43C97BE66B33F57FE3EF09C1 /* libPods-GithubPulse.a */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | 501C92BDD72B84DA8F71F478 /* Pods */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | B2C688CC2B78A1B8230CD0BC /* Pods-GithubPulse.debug.xcconfig */, 86 | 537663C2FEED19D550D9B685 /* Pods-GithubPulse.release.xcconfig */, 87 | ); 88 | name = Pods; 89 | sourceTree = ""; 90 | }; 91 | 5C406D401A4F93140005AF61 = { 92 | isa = PBXGroup; 93 | children = ( 94 | 5C406D7D1A4FC7850005AF61 /* front */, 95 | 5C406D4B1A4F93140005AF61 /* GithubPulse */, 96 | 5C406D4A1A4F93140005AF61 /* Products */, 97 | 501C92BDD72B84DA8F71F478 /* Pods */, 98 | 34E85245A6050C883BA59349 /* Frameworks */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 5C406D4A1A4F93140005AF61 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 5C406D491A4F93140005AF61 /* GithubPulse.app */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | 5C406D4B1A4F93140005AF61 /* GithubPulse */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 5CFF3A021A603B25001B3536 /* Components */, 114 | 5CFF3A041A603B25001B3536 /* Controllers */, 115 | 5CFF3A061A603B25001B3536 /* Models */, 116 | 5CFF3A081A603B25001B3536 /* Other Sources */, 117 | 5CFF3A0B1A603B25001B3536 /* Resources */, 118 | 5CFF3A131A603B25001B3536 /* Views */, 119 | ); 120 | path = GithubPulse; 121 | sourceTree = ""; 122 | }; 123 | 5CFF3A021A603B25001B3536 /* Components */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 5CFF3A031A603B25001B3536 /* CustomButton.swift */, 127 | ); 128 | path = Components; 129 | sourceTree = ""; 130 | }; 131 | 5CFF3A041A603B25001B3536 /* Controllers */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 5CFF3A051A603B25001B3536 /* ContentViewController.swift */, 135 | ); 136 | path = Controllers; 137 | sourceTree = ""; 138 | }; 139 | 5CFF3A061A603B25001B3536 /* Models */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 5CFF3A071A603B25001B3536 /* Contributions.swift */, 143 | ); 144 | path = Models; 145 | sourceTree = ""; 146 | }; 147 | 5CFF3A081A603B25001B3536 /* Other Sources */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 5CFF3A091A603B25001B3536 /* AppDelegate.swift */, 151 | 5CFF3A0A1A603B25001B3536 /* BridgeHeader.h */, 152 | 5CC58B751A6CB65D0024A418 /* GithubUpdate.swift */, 153 | ); 154 | path = "Other Sources"; 155 | sourceTree = ""; 156 | }; 157 | 5CFF3A0B1A603B25001B3536 /* Resources */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 5CFF3A0C1A603B25001B3536 /* Images */, 161 | 5CFF3A111A603B25001B3536 /* Images.xcassets */, 162 | 5CFF3A121A603B25001B3536 /* Info.plist */, 163 | ); 164 | path = Resources; 165 | sourceTree = ""; 166 | }; 167 | 5CFF3A0C1A603B25001B3536 /* Images */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 5C6F3C671A65782E00181655 /* icon_notification_dark.png */, 171 | 5C6F3C681A65782E00181655 /* icon_dark.png */, 172 | 5C6F3C691A65782E00181655 /* icon_dark@2x.png */, 173 | 5C6F3C6A1A65782E00181655 /* icon_notification_dark@2x.png */, 174 | 5CFF3A0D1A603B25001B3536 /* icon.png */, 175 | 5CFF3A0E1A603B25001B3536 /* icon@2x.png */, 176 | 5CFF3A0F1A603B25001B3536 /* icon_notification.png */, 177 | 5CFF3A101A603B25001B3536 /* icon_notification@2x.png */, 178 | ); 179 | path = Images; 180 | sourceTree = ""; 181 | }; 182 | 5CFF3A131A603B25001B3536 /* Views */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 5CFF3A141A603B26001B3536 /* ContentViewController.xib */, 186 | 5CFF3A151A603B26001B3536 /* MainMenu.xib */, 187 | ); 188 | path = Views; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXGroup section */ 192 | 193 | /* Begin PBXNativeTarget section */ 194 | 5C406D481A4F93140005AF61 /* GithubPulse */ = { 195 | isa = PBXNativeTarget; 196 | buildConfigurationList = 5C406D631A4F93140005AF61 /* Build configuration list for PBXNativeTarget "GithubPulse" */; 197 | buildPhases = ( 198 | 3AC05D0D29D27DC49F34E7AF /* Check Pods Manifest.lock */, 199 | 5C406D451A4F93140005AF61 /* Sources */, 200 | 5C406D461A4F93140005AF61 /* Frameworks */, 201 | 5C406D471A4F93140005AF61 /* Resources */, 202 | AD4755B43057778C998501E7 /* Copy Pods Resources */, 203 | D98E86498E97783920492065 /* Embed Pods Frameworks */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | ); 209 | name = GithubPulse; 210 | productName = GithubPulse; 211 | productReference = 5C406D491A4F93140005AF61 /* GithubPulse.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | /* End PBXNativeTarget section */ 215 | 216 | /* Begin PBXProject section */ 217 | 5C406D411A4F93140005AF61 /* Project object */ = { 218 | isa = PBXProject; 219 | attributes = { 220 | LastSwiftMigration = 0700; 221 | LastSwiftUpdateCheck = 0700; 222 | LastUpgradeCheck = 0700; 223 | ORGANIZATIONNAME = "Tadeu Zagallo"; 224 | TargetAttributes = { 225 | 5C406D481A4F93140005AF61 = { 226 | CreatedOnToolsVersion = 6.1; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = 5C406D441A4F93140005AF61 /* Build configuration list for PBXProject "GithubPulse" */; 231 | compatibilityVersion = "Xcode 3.2"; 232 | developmentRegion = English; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | Base, 237 | ); 238 | mainGroup = 5C406D401A4F93140005AF61; 239 | productRefGroup = 5C406D4A1A4F93140005AF61 /* Products */; 240 | projectDirPath = ""; 241 | projectRoot = ""; 242 | targets = ( 243 | 5C406D481A4F93140005AF61 /* GithubPulse */, 244 | ); 245 | }; 246 | /* End PBXProject section */ 247 | 248 | /* Begin PBXResourcesBuildPhase section */ 249 | 5C406D471A4F93140005AF61 /* Resources */ = { 250 | isa = PBXResourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 5CFF3A1D1A603B26001B3536 /* icon_notification@2x.png in Resources */, 254 | 5CFF3A1A1A603B26001B3536 /* icon.png in Resources */, 255 | 5C6F3C6C1A65782E00181655 /* icon_dark.png in Resources */, 256 | 5C406D7E1A4FC7850005AF61 /* front in Resources */, 257 | 5CFF3A1E1A603B26001B3536 /* Images.xcassets in Resources */, 258 | 5CFF3A201A603B26001B3536 /* ContentViewController.xib in Resources */, 259 | 5CFF3A1B1A603B26001B3536 /* icon@2x.png in Resources */, 260 | 5C6F3C6B1A65782E00181655 /* icon_notification_dark.png in Resources */, 261 | 5CFF3A1C1A603B26001B3536 /* icon_notification.png in Resources */, 262 | 5CFF3A211A603B26001B3536 /* MainMenu.xib in Resources */, 263 | 5C6F3C6E1A65782E00181655 /* icon_notification_dark@2x.png in Resources */, 264 | 5C6F3C6D1A65782E00181655 /* icon_dark@2x.png in Resources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXShellScriptBuildPhase section */ 271 | 3AC05D0D29D27DC49F34E7AF /* Check Pods Manifest.lock */ = { 272 | isa = PBXShellScriptBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | inputPaths = ( 277 | ); 278 | name = "Check Pods Manifest.lock"; 279 | outputPaths = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 284 | showEnvVarsInLog = 0; 285 | }; 286 | AD4755B43057778C998501E7 /* Copy Pods Resources */ = { 287 | isa = PBXShellScriptBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ); 291 | inputPaths = ( 292 | ); 293 | name = "Copy Pods Resources"; 294 | outputPaths = ( 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | shellPath = /bin/sh; 298 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-GithubPulse/Pods-GithubPulse-resources.sh\"\n"; 299 | showEnvVarsInLog = 0; 300 | }; 301 | D98E86498E97783920492065 /* Embed Pods Frameworks */ = { 302 | isa = PBXShellScriptBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | ); 306 | inputPaths = ( 307 | ); 308 | name = "Embed Pods Frameworks"; 309 | outputPaths = ( 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | shellPath = /bin/sh; 313 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-GithubPulse/Pods-GithubPulse-frameworks.sh\"\n"; 314 | showEnvVarsInLog = 0; 315 | }; 316 | /* End PBXShellScriptBuildPhase section */ 317 | 318 | /* Begin PBXSourcesBuildPhase section */ 319 | 5C406D451A4F93140005AF61 /* Sources */ = { 320 | isa = PBXSourcesBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | 5CFF3A191A603B26001B3536 /* AppDelegate.swift in Sources */, 324 | 5CFF3A181A603B26001B3536 /* Contributions.swift in Sources */, 325 | 5CFF3A171A603B26001B3536 /* ContentViewController.swift in Sources */, 326 | 5CC58B761A6CB65D0024A418 /* GithubUpdate.swift in Sources */, 327 | 5CFF3A161A603B26001B3536 /* CustomButton.swift in Sources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | /* End PBXSourcesBuildPhase section */ 332 | 333 | /* Begin XCBuildConfiguration section */ 334 | 5C406D611A4F93140005AF61 /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 339 | CLANG_CXX_LIBRARY = "libc++"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_CONSTANT_CONVERSION = YES; 344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 349 | CLANG_WARN_UNREACHABLE_CODE = YES; 350 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 351 | CODE_SIGN_IDENTITY = "-"; 352 | COPY_PHASE_STRIP = NO; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | ENABLE_TESTABILITY = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu99; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_OPTIMIZATION_LEVEL = 0; 358 | GCC_PREPROCESSOR_DEFINITIONS = ( 359 | "DEBUG=1", 360 | "$(inherited)", 361 | ); 362 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | MACOSX_DEPLOYMENT_TARGET = 10.10; 370 | MTL_ENABLE_DEBUG_INFO = YES; 371 | ONLY_ACTIVE_ARCH = YES; 372 | SDKROOT = macosx; 373 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 374 | }; 375 | name = Debug; 376 | }; 377 | 5C406D621A4F93140005AF61 /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ALWAYS_SEARCH_USER_PATHS = NO; 381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 382 | CLANG_CXX_LIBRARY = "libc++"; 383 | CLANG_ENABLE_MODULES = YES; 384 | CLANG_ENABLE_OBJC_ARC = YES; 385 | CLANG_WARN_BOOL_CONVERSION = YES; 386 | CLANG_WARN_CONSTANT_CONVERSION = YES; 387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 388 | CLANG_WARN_EMPTY_BODY = YES; 389 | CLANG_WARN_ENUM_CONVERSION = YES; 390 | CLANG_WARN_INT_CONVERSION = YES; 391 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 392 | CLANG_WARN_UNREACHABLE_CODE = YES; 393 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 394 | CODE_SIGN_IDENTITY = "-"; 395 | COPY_PHASE_STRIP = YES; 396 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 397 | ENABLE_NS_ASSERTIONS = NO; 398 | ENABLE_STRICT_OBJC_MSGSEND = YES; 399 | GCC_C_LANGUAGE_STANDARD = gnu99; 400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 402 | GCC_WARN_UNDECLARED_SELECTOR = YES; 403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 404 | GCC_WARN_UNUSED_FUNCTION = YES; 405 | GCC_WARN_UNUSED_VARIABLE = YES; 406 | MACOSX_DEPLOYMENT_TARGET = 10.10; 407 | MTL_ENABLE_DEBUG_INFO = NO; 408 | SDKROOT = macosx; 409 | }; 410 | name = Release; 411 | }; 412 | 5C406D641A4F93140005AF61 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | baseConfigurationReference = B2C688CC2B78A1B8230CD0BC /* Pods-GithubPulse.debug.xcconfig */; 415 | buildSettings = { 416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 417 | COMBINE_HIDPI_IMAGES = YES; 418 | INFOPLIST_FILE = GithubPulse/Resources/Info.plist; 419 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 420 | "OTHER_SWIFT_FLAGS[arch=*]" = "-D DEBUG"; 421 | PRODUCT_BUNDLE_IDENTIFIER = "com.tadeuzagallo.$(PRODUCT_NAME:rfc1034identifier)"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | SWIFT_OBJC_BRIDGING_HEADER = "GithubPulse/Other Sources/BridgeHeader.h"; 424 | }; 425 | name = Debug; 426 | }; 427 | 5C406D651A4F93140005AF61 /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 537663C2FEED19D550D9B685 /* Pods-GithubPulse.release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | COMBINE_HIDPI_IMAGES = YES; 433 | INFOPLIST_FILE = GithubPulse/Resources/Info.plist; 434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 435 | PRODUCT_BUNDLE_IDENTIFIER = "com.tadeuzagallo.$(PRODUCT_NAME:rfc1034identifier)"; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SWIFT_OBJC_BRIDGING_HEADER = "GithubPulse/Other Sources/BridgeHeader.h"; 438 | }; 439 | name = Release; 440 | }; 441 | /* End XCBuildConfiguration section */ 442 | 443 | /* Begin XCConfigurationList section */ 444 | 5C406D441A4F93140005AF61 /* Build configuration list for PBXProject "GithubPulse" */ = { 445 | isa = XCConfigurationList; 446 | buildConfigurations = ( 447 | 5C406D611A4F93140005AF61 /* Debug */, 448 | 5C406D621A4F93140005AF61 /* Release */, 449 | ); 450 | defaultConfigurationIsVisible = 0; 451 | defaultConfigurationName = Release; 452 | }; 453 | 5C406D631A4F93140005AF61 /* Build configuration list for PBXNativeTarget "GithubPulse" */ = { 454 | isa = XCConfigurationList; 455 | buildConfigurations = ( 456 | 5C406D641A4F93140005AF61 /* Debug */, 457 | 5C406D651A4F93140005AF61 /* Release */, 458 | ); 459 | defaultConfigurationIsVisible = 0; 460 | defaultConfigurationName = Release; 461 | }; 462 | /* End XCConfigurationList section */ 463 | }; 464 | rootObject = 5C406D411A4F93140005AF61 /* Project object */; 465 | } 466 | -------------------------------------------------------------------------------- /widget/GithubPulse/Views/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | Default 535 | 536 | 537 | 538 | 539 | 540 | 541 | Left to Right 542 | 543 | 544 | 545 | 546 | 547 | 548 | Right to Left 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | Default 560 | 561 | 562 | 563 | 564 | 565 | 566 | Left to Right 567 | 568 | 569 | 570 | 571 | 572 | 573 | Right to Left 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | --------------------------------------------------------------------------------