├── .bowerrc ├── .editorconfig ├── .ember-cli ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .watchmanconfig ├── README.md ├── Shrink.icns ├── app ├── app.js ├── components │ ├── .gitkeep │ ├── login-dialog.js │ ├── mention-item.js │ ├── notification-feed.js │ └── title-bar.js ├── controllers │ └── .gitkeep ├── helpers │ ├── .gitkeep │ └── format-github-url.js ├── index.html ├── initializers │ └── component-router-injector.js ├── models │ └── .gitkeep ├── resolver.js ├── router.js ├── routes │ ├── .gitkeep │ ├── assigned.js │ ├── login.js │ ├── mentions.js │ └── subscribed.js ├── styles │ └── app.css └── templates │ ├── assigned.hbs │ ├── components │ ├── .gitkeep │ ├── login-dialog.hbs │ ├── mention-item.hbs │ ├── notification-feed.hbs │ └── title-bar.hbs │ ├── login.hbs │ ├── mentions.hbs │ └── subscribed.hbs ├── bower.json ├── config └── environment.js ├── electron.js ├── ember-cli-build.js ├── package.json ├── public ├── crossdomain.xml └── robots.txt ├── testem.js ├── tests ├── .jshintrc ├── electron.js ├── helpers │ ├── destroy-app.js │ ├── module-for-acceptance.js │ ├── resolver.js │ └── start-app.js ├── index.html ├── integration │ ├── .gitkeep │ └── components │ │ ├── login-dialog-test.js │ │ ├── mention-item-test.js │ │ ├── notification-feed-test.js │ │ └── title-bar-test.js ├── package.json ├── test-helper.js └── unit │ ├── .gitkeep │ ├── helpers │ └── format-github-url-test.js │ └── routes │ ├── assigned-test.js │ ├── login-test.js │ ├── mentions-test.js │ └── subscribed-test.js └── vendor └── .gitkeep /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /electron-builds 7 | 8 | # dependencies 9 | /node_modules 10 | /bower_components 11 | 12 | # misc 13 | /.sass-cache 14 | /connect.lock 15 | /coverage/* 16 | /libpeerconnection.log 17 | npm-debug.log 18 | testem.log 19 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esversion": 6, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "4" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - npm config set spin false 14 | - npm install -g bower 15 | - bower --version 16 | - npm install phantomjs-prebuilt 17 | - phantomjs --version 18 | 19 | install: 20 | - npm install 21 | - bower install 22 | 23 | script: 24 | - npm test 25 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shrink 2 | 3 | Shrink is an macOS client for your Github issues. Easily sort between issues and pull requests you've been mentioned in, assigned or subscribed to. 4 | 5 | [Download for macOS](https://github.com/almonk/shrink2/releases) 6 | 7 | ![](http://almonk-share.s3-eu-west-1.amazonaws.com/ElGPItG7jv/dfsdsf.png) 8 | 9 | ## Prerequisites 10 | 11 | You will need the following things properly installed on your computer. 12 | 13 | * [Git](http://git-scm.com/) 14 | * [Node.js](http://nodejs.org/) (with NPM) 15 | * [Bower](http://bower.io/) 16 | * [Ember CLI](http://ember-cli.com/) 17 | * [PhantomJS](http://phantomjs.org/) 18 | 19 | ## Installation 20 | 21 | * `git clone ` this repository 22 | * change into the new directory 23 | * `npm install` 24 | * `bower install` 25 | 26 | ## Running / Development 27 | 28 | * `ember serve` 29 | * Visit your app at [http://localhost:4200](http://localhost:4200). 30 | 31 | ### Code Generators 32 | 33 | Make use of the many generators for code, try `ember help generate` for more details 34 | 35 | ### Running Tests 36 | 37 | * `ember test` 38 | * `ember test --server` 39 | 40 | ### Building 41 | 42 | * `ember build` (development) 43 | * `ember build --environment production` (production) 44 | 45 | ### Thanks 46 | Thanks to [Hector Simpson](http://hector.me/) for the icon design :hearts: 47 | -------------------------------------------------------------------------------- /Shrink.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/Shrink.icns -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | let App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/app/components/.gitkeep -------------------------------------------------------------------------------- /app/components/login-dialog.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const {shell} = require('electron'); 3 | const Config = require('electron-config'); 4 | const config = new Config(); 5 | 6 | export default Ember.Component.extend({ 7 | actions: { 8 | signIn: function() { 9 | config.set('token', this.get('authToken')); 10 | this.get('router').transitionTo('mentions'); 11 | }, 12 | openUrl: function(url) { 13 | shell.openExternal(url) 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /app/components/mention-item.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const {shell} = require('electron'); 3 | 4 | export default Ember.Component.extend({ 5 | isExpanded: false, 6 | 7 | actions :{ 8 | expand: function() { 9 | this.toggleProperty('isExpanded'); 10 | }, 11 | 12 | openUrl: function(url) { 13 | shell.openExternal(url) 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /app/components/notification-feed.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | 5 | }); 6 | -------------------------------------------------------------------------------- /app/components/title-bar.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const Mousetrap = require('mousetrap'); 3 | 4 | export default Ember.Component.extend({ 5 | init() { 6 | this._super(...arguments); 7 | this.errors = []; 8 | Mousetrap.bind('meta+1', function() { 9 | this.send('goToMentioned'); 10 | }.bind(this)); 11 | 12 | Mousetrap.bind('meta+2', function() { 13 | this.send('goToAssigned'); 14 | }.bind(this)); 15 | 16 | Mousetrap.bind('meta+3', function() { 17 | this.send('goToSubscribed'); 18 | }.bind(this)); 19 | }, 20 | 21 | actions :{ 22 | goToMentioned: function() { 23 | this.get('router').transitionTo('mentions'); 24 | }, 25 | goToAssigned: function() { 26 | this.get('router').transitionTo('assigned'); 27 | }, 28 | goToSubscribed: function() { 29 | this.get('router').transitionTo('subscribed'); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/app/helpers/.gitkeep -------------------------------------------------------------------------------- /app/helpers/format-github-url.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export function formatGithubUrl(params/*, hash*/) { 4 | var str = params[0]; 5 | var res = str.replace("https://api.github.com/repos/", "https://github.com/").replace('/pulls/', '/pull/'); 6 | return res; 7 | } 8 | 9 | export default Ember.Helper.helper(formatGithubUrl); 10 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shrink2 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/initializers/component-router-injector.js: -------------------------------------------------------------------------------- 1 | // app/initializers/component-router-injector.js 2 | export function initialize(application) { 3 | // Injects all Ember components with a router object: 4 | application.inject('component', 'router', 'router:main'); 5 | } 6 | 7 | export default { 8 | name: 'component-router-injector', 9 | initialize: initialize 10 | }; 11 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/app/models/.gitkeep -------------------------------------------------------------------------------- /app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | // cc28e926a87447789186942afa09a0405ae76191 2 | 3 | import Ember from 'ember'; 4 | import config from './config/environment'; 5 | 6 | const Router = Ember.Router.extend({ 7 | location: config.locationType, 8 | rootURL: config.rootURL 9 | }); 10 | 11 | Router.map(function() { 12 | this.route('mentions', {path: '/'}); 13 | this.route('assigned', {path: '/assigned'}); 14 | this.route('subscribed'); 15 | this.route('login'); 16 | }); 17 | 18 | export default Router; 19 | -------------------------------------------------------------------------------- /app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/app/routes/.gitkeep -------------------------------------------------------------------------------- /app/routes/assigned.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const Config = require('electron-config'); 3 | const config = new Config(); 4 | 5 | export default Ember.Route.extend({ 6 | model() { 7 | var token = config.get('token'); 8 | return new Promise(function(resolve, reject){ 9 | Ember.$.getJSON(`https://api.github.com/issues?access_token=${token}&filter=assigned&sort=updated`, function(data) { 10 | resolve({mentions: data}); 11 | }).fail(function(jqxhr,textStatus,error){ 12 | if (error === 'Unauthorized') { 13 | this.transitionTo('login'); 14 | console.log(error); 15 | alert('Could not authenticate'); 16 | } 17 | }.bind(this)); 18 | }.bind(this)); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /app/routes/login.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /app/routes/mentions.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const Config = require('electron-config'); 3 | const config = new Config(); 4 | 5 | export default Ember.Route.extend({ 6 | model() { 7 | var token = config.get('token'); 8 | return new Promise(function(resolve, reject){ 9 | Ember.$.getJSON(`https://api.github.com/issues?access_token=${token}&filter=mentioned&sort=updated`, function(data) { 10 | resolve({mentions: data}); 11 | }).fail(function(jqxhr,textStatus,error){ 12 | if (error === 'Unauthorized') { 13 | this.transitionTo('login'); 14 | console.log(error); 15 | alert('Could not authenticate'); 16 | } 17 | }.bind(this)); 18 | }.bind(this)); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/routes/subscribed.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | const Config = require('electron-config'); 3 | const config = new Config(); 4 | 5 | export default Ember.Route.extend({ 6 | model() { 7 | var token = config.get('token'); 8 | return new Promise(function(resolve, reject){ 9 | Ember.$.getJSON(`https://api.github.com/issues?access_token=${token}&filter=subscribed&sort=updated`, function(data) { 10 | resolve({mentions: data}); 11 | }).fail(function(jqxhr,textStatus,error){ 12 | if (error === 'Unauthorized') { 13 | this.transitionTo('login'); 14 | console.log(error); 15 | alert('Could not authenticate'); 16 | } 17 | }.bind(this)); 18 | }.bind(this)); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/styles/app.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 3 | font-size: 13px; 4 | background-color: #fff; 5 | -webkit-font-smoothing: antialiased; 6 | -webkit-user-select: none; 7 | cursor: default; 8 | overflow: hidden; 9 | } 10 | 11 | .drag { 12 | -webkit-user-select: none; 13 | -webkit-app-region: drag; 14 | } 15 | 16 | .title-bar { 17 | -webkit-user-select: none; 18 | -webkit-app-region: drag; 19 | height: 60px; 20 | width: 100%; 21 | background-color: #fff; 22 | position: fixed; 23 | text-align: center; 24 | line-height: 20px; 25 | display: block; 26 | z-index: 10; 27 | color: #666; 28 | } 29 | 30 | .title-bar .menu a { 31 | font-size: 15px; 32 | -webkit-app-region: no-drag; 33 | text-decoration: none; 34 | color: #383838; 35 | height: 38px; 36 | margin-top: 4px; 37 | line-height: 32px; 38 | border-bottom: 2px solid #E2E2E2; 39 | cursor: default; 40 | } 41 | 42 | .menu a.active { 43 | color: #2783EF; 44 | border-color: #2783EF; 45 | } 46 | 47 | .title-bar-spacer { 48 | height: 64px; 49 | } 50 | 51 | .msg__repo { 52 | font-size: 11px; 53 | } 54 | 55 | .msg__body { 56 | border-radius: 4px; 57 | background-color: #F6F8F8; 58 | margin-top: 10px; 59 | padding: 10px 15px; 60 | max-height: 83px; 61 | overflow: hidden; 62 | cursor: pointer; 63 | position: relative; 64 | color: #484848; 65 | transition: all 0.4s ease-in-out; 66 | } 67 | 68 | .msg__body:after { 69 | content:''; 70 | width:100%; 71 | height:40px; 72 | position:absolute; 73 | left:0; 74 | bottom:0; 75 | background:linear-gradient(transparent, #F2F2F2); 76 | } 77 | 78 | .msg__body p { 79 | -webkit-margin-before: 0px; 80 | -webkit-margin-after: 0px; 81 | line-height: 16px; 82 | margin-bottom: 6px; 83 | } 84 | 85 | .msg__body img { 86 | background-color: white; 87 | border-radius: 4px; 88 | overflow: hidden; 89 | margin: 10px 0; 90 | box-shadow: 0px 2px 5px rgba(0,0,0,0.2); 91 | } 92 | 93 | .msg__body blockquote { 94 | border-left: 3px solid #E6E6E6; 95 | margin: 0; 96 | padding-left: 20px; 97 | } 98 | 99 | .msg__users__user { 100 | border-radius: 20px; 101 | overflow: hidden; 102 | display: inline-block; 103 | height: 22px; 104 | min-width: 22px; 105 | } 106 | 107 | .msg__owner img { 108 | border-radius: 4px; 109 | overflow: hidden; 110 | min-width: 48px; 111 | } 112 | 113 | .msg__repo { 114 | color: #A0A0A0; 115 | } 116 | 117 | .msg__body ul { 118 | margin: 0; 119 | } 120 | 121 | .msg__body ul li { 122 | margin-bottom: 6px; 123 | line-height: 16px; 124 | } 125 | 126 | .msg__body h1,h2,h3,h4 { 127 | font-size: 14px; 128 | } 129 | 130 | .msg__body.msg__body--expanded { 131 | max-height: 900px; 132 | opacity: 1; 133 | } 134 | 135 | .msg__body--expanded:after { 136 | display: none; 137 | } 138 | 139 | .msg__body img { 140 | max-width: 100%; 141 | } 142 | 143 | .msg__body code { 144 | font-size: 12px; 145 | } 146 | 147 | .msg__body table { 148 | border-radius: 4px; 149 | width: 100%; 150 | overflow: hidden; 151 | margin: 10px 0; 152 | padding: 0; 153 | border-collapse: collapse; 154 | } 155 | 156 | .msg__body table th { 157 | border-bottom: 1px solid rgba(0,0,0,0.1); 158 | padding: 0px; 159 | text-align: left; 160 | } 161 | 162 | .msg__body table td, .msg__body table th { 163 | padding: 4px; 164 | margin: 0px; 165 | } 166 | 167 | .b--light-silver { 168 | border-color: #E2E2E2; 169 | } 170 | 171 | .label { 172 | font-size: 11px; 173 | margin-right: 8px; 174 | } 175 | 176 | .label__dot { 177 | height: 9px; 178 | width: 9px; 179 | margin-right: 1px; 180 | border-radius: 50%; 181 | position: relative; 182 | top: 1px; 183 | display: inline-block; 184 | } 185 | 186 | .label span { 187 | color: #767682; 188 | } 189 | 190 | .msg__title { 191 | color: #383838!important; 192 | } 193 | 194 | .list { 195 | height: calc(100vh - 64px); 196 | overflow: scroll; 197 | } 198 | -------------------------------------------------------------------------------- /app/templates/assigned.hbs: -------------------------------------------------------------------------------- 1 | {{#title-bar}}{{/title-bar}} 2 | 3 |
4 | {{#notification-feed model=model}}{{/notification-feed}} 5 |
6 | -------------------------------------------------------------------------------- /app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /app/templates/components/login-dialog.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | Enter a personal access token below 6 |

7 | {{input action='signIn' value=authToken class="mb2 flex-auto"}} 8 |
9 |
10 | 13 |
14 | -------------------------------------------------------------------------------- /app/templates/components/mention-item.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 | 7 |
8 |
9 |
10 |
11 | {{msg.repository.name}} 12 |
13 |
14 | {{moment-calendar msg.updated_at}} 15 |
16 |
17 |
18 | {{msg.title}} (#{{msg.number}}) 19 |
20 | {{#if msg.labels}} 21 |
22 | {{#each msg.labels as |label|}} 23 |
24 |
25 | {{label.name}} 26 |
27 | {{/each}} 28 |
29 | {{/if}} 30 | 31 | {{#if msg.body}} 32 |
33 | {{md-text 34 | text = msg.body 35 | typographer = false 36 | linkify = false 37 | html = true 38 | }} 39 |
40 | {{/if}} 41 |
42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /app/templates/components/notification-feed.hbs: -------------------------------------------------------------------------------- 1 | {{#each model.mentions as |msg|}} 2 | {{#mention-item msg=msg}}{{/mention-item}} 3 | {{/each}} 4 | 5 | {{#unless model.mentions}} 6 |
7 |

8 | 9 | {{material-design-icon name='check'}} 10 | 11 |
12 | There's nothing here 13 |

14 |
15 | {{/unless}} 16 | -------------------------------------------------------------------------------- /app/templates/components/title-bar.hbs: -------------------------------------------------------------------------------- 1 |
2 | Shrink 3 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /app/templates/login.hbs: -------------------------------------------------------------------------------- 1 | {{#login-dialog}}{{/login-dialog}} 2 | -------------------------------------------------------------------------------- /app/templates/mentions.hbs: -------------------------------------------------------------------------------- 1 | {{#title-bar}}{{/title-bar}} 2 | 3 |
4 | {{#notification-feed model=model}}{{/notification-feed}} 5 |
6 | -------------------------------------------------------------------------------- /app/templates/subscribed.hbs: -------------------------------------------------------------------------------- 1 | {{#title-bar}}{{/title-bar}} 2 | 3 |
4 | {{#notification-feed model=model}}{{/notification-feed}} 5 |
6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shrink2", 3 | "dependencies": { 4 | "ember": "~2.7.0", 5 | "ember-cli-shims": "0.1.1", 6 | "ember-qunit-notifications": "0.1.0", 7 | "remarkable": "^1.7.1", 8 | "highlightjs": "^9.7.0", 9 | "mousetrap": "~1.5.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'shrink2', 6 | environment: environment, 7 | rootURL: null, 8 | locationType: 'hash', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | } 20 | }; 21 | 22 | if (environment === 'development') { 23 | // ENV.APP.LOG_RESOLVER = true; 24 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 25 | // ENV.APP.LOG_TRANSITIONS = true; 26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 27 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 28 | } 29 | 30 | if (environment === 'test') { 31 | // Testem prefers this... 32 | 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | } 41 | 42 | if (environment === 'production') { 43 | } 44 | 45 | return ENV; 46 | }; 47 | -------------------------------------------------------------------------------- /electron.js: -------------------------------------------------------------------------------- 1 | /* jshint node: true */ 2 | 'use strict'; 3 | 4 | const electron = require('electron'); 5 | const path = require('path'); 6 | const app = electron.app; 7 | const BrowserWindow = electron.BrowserWindow; 8 | const dirname = __dirname || path.resolve(path.dirname()); 9 | const emberAppLocation = `file://${dirname}/dist/index.html`; 10 | const windowStateKeeper = require('electron-window-state'); 11 | const defaultMenu = require('electron-default-menu'); 12 | const { Menu, shell } = electron; 13 | 14 | 15 | let win; 16 | let mainWindow = null; 17 | 18 | // Uncomment the lines below to enable Electron's crash reporter 19 | // For more information, see http://electron.atom.io/docs/api/crash-reporter/ 20 | 21 | // electron.crashReporter.start({ 22 | // productName: 'YourName', 23 | // companyName: 'YourCompany', 24 | // submitURL: 'https://your-domain.com/url-to-submit', 25 | // autoSubmit: true 26 | // }); 27 | 28 | app.on('window-all-closed', function onWindowAllClosed() { 29 | if (process.platform !== 'darwin') { 30 | app.quit(); 31 | } 32 | }); 33 | 34 | app.on('ready', function onReady() { 35 | // Get template for default menu 36 | const menu = defaultMenu(app, shell); 37 | Menu.setApplicationMenu(Menu.buildFromTemplate(menu)); 38 | 39 | let mainWindowState = windowStateKeeper({ 40 | defaultWidth: 400, 41 | defaultHeight: 700 42 | }); 43 | 44 | mainWindow = new BrowserWindow({ 45 | 'x': mainWindowState.x, 46 | 'y': mainWindowState.y, 47 | 'width': mainWindowState.width, 48 | 'height': mainWindowState.height, 49 | minWidth: 300, 50 | minHeight: 400, 51 | titleBarStyle: 'hidden' 52 | }); 53 | 54 | mainWindowState.manage(mainWindow); 55 | 56 | 57 | delete mainWindow.module; 58 | 59 | // If you want to open up dev tools programmatically, call 60 | // mainWindow.openDevTools(); 61 | 62 | // By default, we'll open the Ember App by directly going to the 63 | // file system. 64 | // 65 | // Please ensure that you have set the locationType option in the 66 | // config/environment.js file to 'hash'. For more information, 67 | // please consult the ember-electron readme. 68 | mainWindow.loadURL(emberAppLocation); 69 | 70 | // If a loading operation goes wrong, we'll send Electron back to 71 | // Ember App entry point 72 | mainWindow.webContents.on('did-fail-load', () => { 73 | mainWindow.loadURL(emberAppLocation); 74 | }); 75 | 76 | mainWindow.webContents.on('crashed', () => { 77 | console.log('Your Ember app (or other code) in the main window has crashed.'); 78 | console.log('This is a serious issue that needs to be handled and/or debugged.'); 79 | }); 80 | 81 | mainWindow.on('unresponsive', () => { 82 | console.log('Your Ember app (or other code) has made the window unresponsive.'); 83 | }); 84 | 85 | mainWindow.on('responsive', () => { 86 | console.log('The main window has become responsive again.'); 87 | }); 88 | 89 | mainWindow.on('closed', () => { 90 | mainWindow = null; 91 | }); 92 | 93 | // Handle an unhandled error in the main thread 94 | // 95 | // Note that 'uncaughtException' is a crude mechanism for exception handling intended to 96 | // be used only as a last resort. The event should not be used as an equivalent to 97 | // "On Error Resume Next". Unhandled exceptions inherently mean that an application is in 98 | // an undefined state. Attempting to resume application code without properly recovering 99 | // from the exception can cause additional unforeseen and unpredictable issues. 100 | // 101 | // Attempting to resume normally after an uncaught exception can be similar to pulling out 102 | // of the power cord when upgrading a computer -- nine out of ten times nothing happens - 103 | // but the 10th time, the system becomes corrupted. 104 | // 105 | // The correct use of 'uncaughtException' is to perform synchronous cleanup of allocated 106 | // resources (e.g. file descriptors, handles, etc) before shutting down the process. It is 107 | // not safe to resume normal operation after 'uncaughtException'. 108 | process.on('uncaughtException', (err) => { 109 | console.log('An exception in the main thread was not handled.'); 110 | console.log('This is a serious issue that needs to be handled and/or debugged.'); 111 | console.log(`Exception: ${err}`); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | /* global require, module */ 3 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | var app = new EmberApp(defaults, { 7 | // Add options here 8 | }); 9 | 10 | // Use `app.import` to add additional libraries to the generated 11 | // output files. 12 | // 13 | // If you need to use different assets in different 14 | // environments, specify an object as the first parameter. That 15 | // object's keys should be the environment name and the values 16 | // should be the asset to use in that environment. 17 | // 18 | // If the library that you are including contains AMD or ES6 19 | // modules that you would like to import into your application 20 | // please specify an object with the list of modules as keys 21 | // along with the exports of each module as its value. 22 | 23 | return app.toTree(); 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shrink2", 3 | "version": "0.0.0", 4 | "description": "Small description for shrink2 goes here", 5 | "private": true, 6 | "directories": { 7 | "doc": "doc", 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "build": "ember build", 12 | "start": "ember server", 13 | "test": "ember test" 14 | }, 15 | "repository": "", 16 | "engines": { 17 | "node": ">= 0.10.0" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "broccoli-asset-rev": "^2.4.2", 23 | "devtron": "1.4.0", 24 | "electron": "1.4.3", 25 | "electron-packager": "8.1.0", 26 | "electron-rebuild": "1.2.1", 27 | "ember-ajax": "^2.0.1", 28 | "ember-cli": "2.7.0", 29 | "ember-cli-app-version": "^1.0.0", 30 | "ember-cli-babel": "^5.1.6", 31 | "ember-cli-dependency-checker": "^1.2.0", 32 | "ember-cli-htmlbars": "^1.0.3", 33 | "ember-cli-htmlbars-inline-precompile": "^0.3.1", 34 | "ember-cli-inject-live-reload": "^1.4.0", 35 | "ember-cli-jshint": "^1.0.0", 36 | "ember-cli-material-design-icons": "1.0.3", 37 | "ember-cli-moment-shim": "2.1.0", 38 | "ember-cli-qunit": "^2.0.0", 39 | "ember-cli-release": "^0.2.9", 40 | "ember-cli-sri": "^2.1.0", 41 | "ember-cli-test-loader": "^1.1.0", 42 | "ember-cli-uglify": "^1.2.0", 43 | "ember-data": "^2.7.0", 44 | "ember-electron": "1.10.8", 45 | "ember-export-application-global": "^1.0.5", 46 | "ember-inspector": "2.0.3", 47 | "ember-load-initializers": "^0.5.1", 48 | "ember-moment": "6.1.0", 49 | "ember-remarkable": "3.2.0", 50 | "ember-resolver": "^2.0.3", 51 | "ember-truth-helpers": "1.2.0", 52 | "ember-welcome-page": "^1.0.1", 53 | "loader.js": "^4.0.1" 54 | }, 55 | "main": "electron.js", 56 | "ember-electron": { 57 | "WHAT IS THIS?": "Please see the README.md", 58 | "copy-files": [ 59 | "electron.js", 60 | "package.json" 61 | ], 62 | "name": null, 63 | "platform": null, 64 | "arch": null, 65 | "version": null, 66 | "app-bundle-id": null, 67 | "app-category-type": null, 68 | "app-copyright": null, 69 | "app-version": null, 70 | "asar": null, 71 | "asar-unpack": null, 72 | "asar-unpack-dir": null, 73 | "build-version": null, 74 | "cache": null, 75 | "extend-info": null, 76 | "extra-resource": null, 77 | "helper-bundle-id": null, 78 | "icon": null, 79 | "ignore": null, 80 | "out": null, 81 | "osx-sign": { 82 | "identity": null, 83 | "entitlements": null, 84 | "entitlements-inherit": null 85 | }, 86 | "overwrite": null, 87 | "prune": null, 88 | "strict-ssl": null, 89 | "version-string": { 90 | "CompanyName": null, 91 | "FileDescription": null, 92 | "OriginalFilename": null, 93 | "ProductName": null, 94 | "InternalName": null 95 | } 96 | }, 97 | "dependencies": { 98 | "electron-config": "^0.2.1", 99 | "electron-default-menu": "^1.0.0", 100 | "electron-window-state": "^3.1.0", 101 | "mousetrap": "^1.6.0" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | "framework": "qunit", 4 | "test_page": "tests/index.html?hidepassed", 5 | "disable_watching": true, 6 | "launch_in_ci": [ 7 | "PhantomJS" 8 | ], 9 | "launch_in_dev": [ 10 | "PhantomJS", 11 | "Chrome" 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "$", 8 | "-Promise", 9 | "define", 10 | "console", 11 | "visit", 12 | "exists", 13 | "fillIn", 14 | "click", 15 | "keyEvent", 16 | "triggerEvent", 17 | "find", 18 | "findWithAssert", 19 | "wait", 20 | "DS", 21 | "andThen", 22 | "currentURL", 23 | "currentPath", 24 | "currentRouteName" 25 | ], 26 | "node": false, 27 | "browser": false, 28 | "boss": true, 29 | "curly": true, 30 | "debug": false, 31 | "devel": false, 32 | "eqeqeq": true, 33 | "evil": true, 34 | "forin": false, 35 | "immed": false, 36 | "laxbreak": false, 37 | "newcap": true, 38 | "noarg": true, 39 | "noempty": false, 40 | "nonew": false, 41 | "nomen": false, 42 | "onevar": false, 43 | "plusplus": false, 44 | "regexp": false, 45 | "undef": true, 46 | "sub": true, 47 | "strict": false, 48 | "white": false, 49 | "eqnull": true, 50 | "esversion": 6, 51 | "unused": true 52 | } 53 | -------------------------------------------------------------------------------- /tests/electron.js: -------------------------------------------------------------------------------- 1 | /* jshint undef: false */ 2 | 3 | const BrowserWindow = require('electron').BrowserWindow; 4 | const app = require('electron').app; 5 | 6 | let mainWindow = null; 7 | 8 | app.on('window-all-closed', function onWindowAllClosed() { 9 | if (process.platform !== 'darwin') { 10 | app.quit(); 11 | } 12 | }); 13 | 14 | app.on('ready', function onReady() { 15 | mainWindow = new BrowserWindow({ 16 | width: 800, 17 | height: 600 18 | }); 19 | 20 | delete mainWindow.module; 21 | 22 | if (process.env.EMBER_ENV === 'test') { 23 | mainWindow.loadURL('file://' + __dirname + '/index.html'); 24 | } else { 25 | mainWindow.loadURL('file://' + __dirname + '/dist/index.html'); 26 | } 27 | 28 | mainWindow.on('closed', function onClosed() { 29 | mainWindow = null; 30 | }); 31 | }); 32 | 33 | /* jshint undef: true */ 34 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function destroyApp(application) { 4 | Ember.run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import Ember from 'ember'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | 6 | const { RSVP: { Promise } } = Ember; 7 | 8 | export default function(name, options = {}) { 9 | module(name, { 10 | beforeEach() { 11 | this.application = startApp(); 12 | 13 | if (options.beforeEach) { 14 | return options.beforeEach.apply(this, arguments); 15 | } 16 | }, 17 | 18 | afterEach() { 19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments); 20 | return Promise.resolve(afterEach).then(() => destroyApp(this.application)); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from '../../resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let application; 7 | 8 | let attributes = Ember.merge({}, config.APP); 9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override; 10 | 11 | Ember.run(() => { 12 | application = Application.create(attributes); 13 | application.setupForTesting(); 14 | application.injectTestHelpers(); 15 | }); 16 | 17 | return application; 18 | } 19 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Shrink2 Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/tests/integration/.gitkeep -------------------------------------------------------------------------------- /tests/integration/components/login-dialog-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('login-dialog', 'Integration | Component | login dialog', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{login-dialog}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#login-dialog}} 19 | template block text 20 | {{/login-dialog}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/mention-item-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('mention-item', 'Integration | Component | mention item', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{mention-item}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#mention-item}} 19 | template block text 20 | {{/mention-item}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/notification-feed-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('notification-feed', 'Integration | Component | notification feed', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{notification-feed}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#notification-feed}} 19 | template block text 20 | {{/notification-feed}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/title-bar-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('title-bar', 'Integration | Component | title bar', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{title-bar}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#title-bar}} 19 | template block text 20 | {{/title-bar}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-electron-test", 3 | "main": "electron.js" 4 | } 5 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | 6 | setResolver(resolver); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/helpers/format-github-url-test.js: -------------------------------------------------------------------------------- 1 | 2 | import { formatGithubUrl } from 'shrink2/helpers/format-github-url'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Helper | format github url'); 6 | 7 | // Replace this with your real tests. 8 | test('it works', function(assert) { 9 | let result = formatGithubUrl([42]); 10 | assert.ok(result); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /tests/unit/routes/assigned-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:assigned', 'Unit | Route | assigned', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/login-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:login', 'Unit | Route | login', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/mentions-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:mentions', 'Unit | Route | mentions', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/subscribed-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:subscribed', 'Unit | Route | subscribed', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/almonk/shrink2/d1ab390d4d32f48bcc803f1604f5f6ba251a2ce1/vendor/.gitkeep --------------------------------------------------------------------------------