17 | <% // FIXME: move this to its own view %>
18 |
19 | <% _.each(files, function(file) { %>
20 |
21 |
28 |
29 |
<%- file.patch %>
30 |
31 | <% }) %>
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/templates/timeline/head_ref_deleted.us:
--------------------------------------------------------------------------------
1 |
2 |
3 |
<%- actor.login %>
4 | deleted the
<%- subject.head.ref %> branch
5 |
<%- created_at %>
6 |
--------------------------------------------------------------------------------
/app/templates/timeline/head_ref_restored.us:
--------------------------------------------------------------------------------
1 |
2 |
3 |
<%- actor.login %>
4 | restored the
<%- subject.head.ref %> branch
5 |
<%- created_at %>
6 |
--------------------------------------------------------------------------------
/app/templates/timeline/merged.us:
--------------------------------------------------------------------------------
1 |
2 |
3 |
<%- actor.login %>
4 | merged commit
<%- commit_id.substring(0, 8) %>
5 | into
<%- subject.base.label %>
6 | from
<%- subject.head.label %>
7 |
<%- created_at %>
8 |
--------------------------------------------------------------------------------
/app/templates/timeline/referenced.us:
--------------------------------------------------------------------------------
1 |
2 |
3 |
<%- actor.login %>
4 | referenced this <%- subject.display_type %>
5 | from commit
<%- commit_id.substring(0, 8) %>
6 |
<%- created_at %>
7 |
--------------------------------------------------------------------------------
/app/templates/timeline/reopened.us:
--------------------------------------------------------------------------------
1 |
2 |
3 |
<%- actor.login %>
4 | reopened the <%- subject.display_type %>
5 |
<%- created_at %>
6 |
--------------------------------------------------------------------------------
/app/templates/tips.us:
--------------------------------------------------------------------------------
1 |
2 |
3 | ProTip!
4 | <%= tip %>
5 |
6 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notifications",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "jquery": "~1.9.0",
6 | "underscore": "~1.4.3",
7 | "backbone": "~1.0.0",
8 | "modernizr": "~2.6.2",
9 | "normalize-css": "~2.1.2",
10 | "moment": "~2.1.0",
11 | "mousetrap": "~1.4.5",
12 | "backbone.mousetrap": "https://github.com/elasticsales/backbone.mousetrap.git",
13 | "jQuery.scrollIntoView": "https://github.com/Arwid/jQuery.scrollIntoView.git",
14 | "fastclick": "~0.6.11",
15 | "octicons": "*"
16 | },
17 | "devDependencies": {
18 | "primer": "https://github.com/github/primer.git"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/config/application.coffee:
--------------------------------------------------------------------------------
1 | # Exports an object that defines
2 | # all of the configuration needed by the projects'
3 | # depended-on grunt tasks.
4 | #
5 | # You can find the parent object in: node_modules/lineman/config/application.coffee
6 | module.exports = require(process.env["LINEMAN_MAIN"]).config.extend "application",
7 | # Override application configuration here. Common examples follow in the comments.
8 |
9 | removeTasks:
10 | common: ["less"]
11 |
--------------------------------------------------------------------------------
/config/defaults.json:
--------------------------------------------------------------------------------
1 | {
2 | "oauth_client_id": "7af5378b831dda8f4ae3",
3 | "oauth_client_secret": "32f502eb3ada281bca20683bc82aa1f88fbfaaa7",
4 | "oauth_host": "github.com",
5 | "oauth_port": 443,
6 | "oauth_path": "/login/oauth/access_token",
7 | "oauth_method": "POST",
8 | "oauth_scope": "notifications,repo"
9 | }
10 |
--------------------------------------------------------------------------------
/config/files.coffee:
--------------------------------------------------------------------------------
1 | # Exports an object that defines
2 | # all of the paths & globs that the project
3 | # is concerned with.
4 | #
5 | # The "configure" task will require this file and
6 | # then re-initialize the grunt config such that
7 | # directives like
will work
8 | # regardless of the point you're at in the build
9 | # lifecycle.
10 | #
11 | # You can find the parent object in: node_modules/lineman/config/files.coffee
12 | module.exports = require(process.env["LINEMAN_MAIN"]).config.extend "files",
13 | coffee:
14 | app: [
15 | "app/js/lib/*.coffee",
16 | "app/js/app.coffee",
17 | "app/js/models/**/*.coffee",
18 | "app/js/views/comment.coffee",
19 | "app/js/**/*.coffee"
20 | ]
21 |
22 | js:
23 | vendor: [
24 | "vendor/bower/jquery/jquery.js",
25 | "vendor/bower/underscore/underscore.js",
26 | "vendor/bower/backbone/backbone.js",
27 | "vendor/bower/moment/moment.js",
28 | "vendor/bower/mousetrap/mousetrap.js",
29 | "vendor/bower/backbone.mousetrap/backbone.mousetrap.js",
30 | "vendor/bower/jQuery.scrollIntoView/jquery.scrollIntoView.js",
31 | "vendor/bower/fastclick/lib/fastclick.js",
32 | "vendor/js/**/*.js"
33 | ]
34 | app: ["app/js/**/*.js"]
35 |
36 | css:
37 | vendor: [
38 | "vendor/bower/octicons/octicons/octicons.css",
39 | "vendor/bower/normalize-css/normalize.css",
40 | "vendor/css/**/*.css"
41 | ]
42 |
--------------------------------------------------------------------------------
/config/plugins/bower-custom.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (lineman) ->
2 | lineman.config.application.bower.install.options.bowerOptions =
3 | production: true
4 | {}
5 |
--------------------------------------------------------------------------------
/config/plugins/concat-sourcemap.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (lineman) ->
2 | config:
3 | loadNpmTasks: lineman.config.application.loadNpmTasks.concat("grunt-concat-sourcemap")
4 |
5 | appendTasks:
6 | common: lineman.config.application.prependTasks.common.concat("concat_sourcemap")
7 |
8 | removeTasks:
9 | common: lineman.config.application.removeTasks.common.concat("concat")
10 |
11 | concat_sourcemap:
12 | options:
13 | sourcesContent: true
14 | js:
15 | src: [
16 | "",
17 | "<%= files.js.vendor %>",
18 | "<%= files.template.generated %>",
19 | "<%= files.js.app %>",
20 | "<%= files.coffee.generated %>"
21 | ]
22 | dest: "<%= files.js.concatenated %>"
23 |
24 | spec:
25 | src: [
26 | "<%= files.js.specHelpers %>",
27 | "<%= files.coffee.generatedSpecHelpers %>",
28 | "<%= files.js.spec %>",
29 | "<%= files.coffee.generatedSpec %>"
30 | ]
31 | dest: "<%= files.js.concatenatedSpec %>"
32 |
33 | css:
34 | src: [
35 | "<%= files.stylus.generatedVendor %>",
36 | "<%= files.css.vendor %>",
37 | "<%= files.stylus.generatedApp %>",
38 | "<%= files.css.app %>"
39 | ]
40 | dest: "<%= files.css.concatenated %>"
41 |
42 |
43 |
--------------------------------------------------------------------------------
/config/plugins/manifest.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (lineman) ->
2 | config:
3 | loadNpmTasks: lineman.config.application.loadNpmTasks.concat("grunt-manifest")
4 |
5 | appendTasks:
6 | dist: lineman.config.application.appendTasks.dist.concat("manifest")
7 |
8 | manifest:
9 | generate:
10 | options:
11 | basePath: './dist'
12 | hash: true
13 | verbose: false
14 | timestamp: false
15 | master: ['index.html']
16 | src: ["**/*.*"]
17 | dest: 'dist/manifest.appcache'
18 |
--------------------------------------------------------------------------------
/config/plugins/octicons.coffee:
--------------------------------------------------------------------------------
1 | # Override lineman's webfonts config to pull fonts from bower
2 | module.exports = (lineman) ->
3 | config:
4 | webfonts:
5 | files:
6 | "vendor/bower/octicons/octicons/": "vendor/bower/octicons/octicons/octicons.{ttf,eot,woff,svg}"
7 | root: "css"
8 |
--------------------------------------------------------------------------------
/config/plugins/stylus.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (lineman) ->
2 | config:
3 | loadNpmTasks: lineman.config.application.loadNpmTasks.concat("grunt-contrib-stylus")
4 |
5 | prependTasks:
6 | common: lineman.config.application.prependTasks.common.concat("stylus")
7 |
8 | stylus:
9 | compile:
10 | use: [require("nib")]
11 | src: "app/css/app.styl"
12 | dest: "<%= files.stylus.generatedApp %>"
13 |
14 | files:
15 | stylus:
16 | main: "app/css/main.styl"
17 | vendor: "vendor/css/**/*.styl"
18 | app: "app/css/**/*.styl"
19 | import: "app/css"
20 | generatedVendor: "generated/css/vendor.styl.css"
21 | generatedApp: "generated/css/app.styl.css"
22 |
--------------------------------------------------------------------------------
/config/plugins/watch.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (lineman) ->
2 | config:
3 | watch:
4 | js:
5 | files: ["<%= files.js.vendor %>", "<%= files.js.app %>"]
6 | tasks: ["concat_sourcemap:js"]
7 |
8 | coffee:
9 | files: "<%= files.coffee.app %>"
10 | tasks: ["coffee", "concat_sourcemap:js"]
11 |
12 | jsSpecs:
13 | files: ["<%= files.js.specHelpers %>", "<%= files.js.spec %>"]
14 | tasks: ["concat_sourcemap:spec"]
15 |
16 | coffeeSpecs:
17 | files: ["<%= files.coffee.specHelpers %>", "<%= files.coffee.spec %>"]
18 | tasks: ["coffee", "concat_sourcemap:spec"]
19 |
20 | css:
21 | files: ["<%= files.css.vendor %>", "<%= files.css.app %>"]
22 | tasks: ["concat_sourcemap:css"]
23 |
24 | stylus:
25 | files: ["<%= files.stylus.vendor %>", "<%= files.stylus.app %>"]
26 | tasks: ["stylus", "concat_sourcemap:css"]
27 |
28 | handlebars:
29 | tasks: ["handlebars", "concat_sourcemap:js"]
30 |
31 | underscore:
32 | tasks: ["jst", "concat_sourcemap:js"]
33 |
--------------------------------------------------------------------------------
/config/server.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | drawRoutes: function(app) {
3 | if(process.env.NODE_ENV == 'production') {
4 | app.use(requireHTTPS);
5 | }
6 |
7 | app.get('/authenticate', function(req, res) {
8 | res.json({client_id: config.oauth_client_id, scope: config.oauth_scope});
9 | });
10 |
11 | app.post('/authenticate/:code', function(req, res) {
12 | authenticate(req.params.code, function(err, token) {
13 | var result = err || !token ? {"error": "bad_code"} : {"token": token};
14 | res.json(result);
15 | });
16 | });
17 | }
18 | }
19 |
20 | function requireHTTPS(req, res, next) {
21 | res.setHeader('Strict-Transport-Security', 'max-age=31536000');
22 |
23 | var isSecure = req.secure || req.headers['x-forwarded-proto'] == 'https';
24 |
25 | if(isSecure) {
26 | return next();
27 | } else {
28 | return res.redirect("https://" + req.get('host') + req.url);
29 | }
30 | }
31 |
32 | var url = require('url'),
33 | https = require('https'),
34 | qs = require('querystring');
35 |
36 | // Load config defaults from JSON file.
37 | // Environment variables override defaults.
38 | function loadConfig() {
39 | var config = JSON.parse(require('fs').readFileSync(__dirname + '/defaults.json', 'utf-8'));
40 | for (var i in config) {
41 | config[i] = process.env[i.toUpperCase()] || config[i];
42 | }
43 | return config;
44 | }
45 |
46 | var config = loadConfig();
47 |
48 | function authenticate(code, cb) {
49 | var data = qs.stringify({
50 | client_id: config.oauth_client_id,
51 | client_secret: config.oauth_client_secret,
52 | code: code
53 | });
54 |
55 | var reqOptions = {
56 | host: config.oauth_host,
57 | port: config.oauth_port,
58 | path: config.oauth_path,
59 | method: config.oauth_method,
60 | headers: {
61 | 'content-length': data.length
62 | }
63 | };
64 |
65 | var body = "";
66 | var req = https.request(reqOptions, function(res) {
67 | res.setEncoding('utf8');
68 | res.on('data', function(chunk) { body += chunk; });
69 | res.on('end', function() { cb(null, qs.parse(body).access_token); });
70 | });
71 |
72 | req.write(data);
73 | req.end();
74 | req.on('error', function(e) { cb(e.message); });
75 | }
76 |
--------------------------------------------------------------------------------
/config/spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework" : "jasmine",
3 | "launch_in_dev" : ["Chrome"],
4 | "launch_in_ci" : ["PhantomJS"],
5 | "src_files" : [
6 | "generated/js/app.js",
7 | "generated/js/spec.js"
8 | ]
9 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-notifiations",
3 | "title": "A client for reading GitHub web notifications",
4 | "repository": {
5 | "type": "git",
6 | "url": "git://github.com/bkeepers/github-notifications.git"
7 | },
8 | "version": "0.0.1",
9 | "private": true,
10 | "author": {
11 | "name": "Brandon Keepers",
12 | "company": "GitHub"
13 | },
14 | "engines": {
15 | "node": "0.10.x",
16 | "npm": "1.3.x"
17 | },
18 | "dependencies": {
19 | "express": "~3.4.4"
20 | },
21 | "devDependencies": {
22 | "bower": "~1.3.8",
23 | "grunt-bower-task": "0.4.0",
24 | "lineman": ">=0.19.3",
25 | "grunt-concat-sourcemap": "~0.3.0",
26 | "grunt-contrib-stylus": "~0.9.0",
27 | "nib": "~1.0.1",
28 | "lineman-bower": "0.0.3",
29 | "grunt-manifest": "git+https://github.com/gunta/grunt-manifest.git#6b830e31"
30 | },
31 | "scripts": {
32 | "postinstall": "lineman build"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/script/bootstrap:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | npm install
4 |
--------------------------------------------------------------------------------
/script/server:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ./node_modules/.bin/lineman run
4 |
--------------------------------------------------------------------------------
/script/spec:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ./node_modules/.bin/lineman spec
4 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 |
4 | require('./config/server').drawRoutes(app);
5 |
6 | app.use(express.static(__dirname + '/dist'));
7 | app.listen(process.env.PORT || 3000);
8 |
--------------------------------------------------------------------------------
/spec/collections/notifications_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Collections.Notifications', ->
2 | context 'with a filter', ->
3 | beforeEach ->
4 | filter = (model) -> model.get('bonafide')
5 | @collection = new App.Collections.Notifications([], filter: filter)
6 |
7 | it 'adds models that satisfy the filter', ->
8 | @collection.add new Backbone.Model(name: 'Vernon T. Waldrip', bonafide: true)
9 | expect(@collection.size()).toBe(1)
10 |
11 | it 'ignores models that do not satisfy the filter', ->
12 | @collection.add new Backbone.Model(name: 'Ulysses Everett McGill', bonafide: false)
13 | expect(@collection.size()).toBe(0)
14 |
15 | it 'marks each notification individually as read', ->
16 | model = new Backbone.Model(name: 'Iliad', bonafide: true)
17 | model.read = jasmine.createSpy('read')
18 | @collection.add(model)
19 | @collection.read()
20 | expect(model.read).toHaveBeenCalled()
21 |
22 | describe 'comparator', ->
23 | it 'orders by updated at', ->
24 | @collection = new App.Collections.Notifications([])
25 | @collection.add([
26 | new Backbone.Model({id: 1, updated_at: "2014-09-14"})
27 | new Backbone.Model({id: 2, updated_at: "2014-09-15"})
28 | new Backbone.Model({id: 3, updated_at: "2014-09-13"})
29 | ])
30 |
31 | expect(@collection.at(0).id).toBe(2)
32 | expect(@collection.at(1).id).toBe(1)
33 | expect(@collection.at(2).id).toBe(3)
34 |
--------------------------------------------------------------------------------
/spec/collections/repositories_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Collections.Repositories', ->
2 | context 'findByName', ->
3 | beforeEach ->
4 | @collection = new App.Collections.Repositories([])
5 |
6 | it 'finds model by name', ->
7 | model = new App.Models.Repository(full_name: 'foo/bar')
8 | @collection.add(model)
9 | expect(@collection.findByName('foo/bar')).toEqual(model)
10 |
--------------------------------------------------------------------------------
/spec/collections/timeline_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Collections.Timeline', ->
2 | describe 'comparator', ->
3 | it 'sorts by created_at', ->
4 | @collection = new App.Collections.Timeline
5 | @collection.add new App.Models.Comment(created_at: '2014-02-13')
6 | @collection.add new App.Models.Comment(created_at: '2014-02-12')
7 | @collection.add new App.Models.Comment(created_at: '2014-02-15')
8 |
9 | expect(@collection.first().get('created_at')).toEqual('2014-02-12')
10 | expect(@collection.last().get('created_at')).toEqual('2014-02-15')
11 |
12 | describe 'observe', ->
13 | beforeEach ->
14 | @timeline = new App.Collections.Timeline
15 | @other = new Backbone.Collection()
16 |
17 | @timeline.observe @other
18 |
19 | it 'syncs adds and removes', ->
20 | model = new Backbone.Model
21 | @other.add model
22 | expect(@timeline.size()).toBe(1)
23 | expect(@timeline.first()).toEqual(model)
24 |
25 | @other.remove model
26 | expect(@timeline.size()).toBe(0)
27 |
--------------------------------------------------------------------------------
/spec/helpers/helper.js:
--------------------------------------------------------------------------------
1 | var root = this;
2 |
3 | root.context = root.describe;
4 | root.xcontext = root.xdescribe;
--------------------------------------------------------------------------------
/spec/helpers/jasmine-fixture.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | jasmine-fixture 1.0.5
4 | Makes injecting HTML snippets into the DOM easy & clean!
5 | site: https://github.com/searls/jasmine-fixture
6 | */
7 |
8 |
9 | (function() {
10 | var createHTMLBlock;
11 |
12 | (function($) {
13 | var jasmineFixture, originalAffix, originalInject, originalJasmineFixture, root, _;
14 | root = this;
15 | originalJasmineFixture = root.jasmineFixture;
16 | originalInject = root.inject;
17 | originalAffix = root.affix;
18 | _ = function(list) {
19 | return {
20 | inject: function(iterator, memo) {
21 | var item, _i, _len, _results;
22 | _results = [];
23 | for (_i = 0, _len = list.length; _i < _len; _i++) {
24 | item = list[_i];
25 | _results.push(memo = iterator(memo, item));
26 | }
27 | return _results;
28 | }
29 | };
30 | };
31 | root.jasmineFixture = function($) {
32 | var $whatsTheRootOf, applyAttributes, defaultConfiguration, defaults, init, injectContents, isReady, isString, itLooksLikeHtml, rootId, tidyUp;
33 | $.fn.affix = root.affix = function(selectorOptions) {
34 | var $top;
35 | $top = null;
36 | _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
37 | var $el;
38 | if (elementSelector === ">") {
39 | return $parent;
40 | }
41 | $el = createHTMLBlock($, elementSelector).appendTo($parent);
42 | $top || ($top = $el);
43 | return $el;
44 | }, $whatsTheRootOf(this));
45 | return $top;
46 | };
47 | $whatsTheRootOf = function(that) {
48 | if (that.jquery != null) {
49 | return that;
50 | } else if ($('#jasmine_content').length > 0) {
51 | return $('#jasmine_content');
52 | } else {
53 | return $('
').appendTo('body');
54 | }
55 | };
56 | afterEach(function() {
57 | return $('#jasmine_content').remove();
58 | });
59 | isReady = false;
60 | rootId = "specContainer";
61 | defaultConfiguration = {
62 | el: "div",
63 | cssClass: "",
64 | id: "",
65 | text: "",
66 | html: "",
67 | defaultAttribute: "class",
68 | attrs: {}
69 | };
70 | defaults = $.extend({}, defaultConfiguration);
71 | $.jasmine = {
72 | inject: function(arg, context) {
73 | var $toInject, config, parent;
74 | if (isReady !== true) {
75 | init();
76 | }
77 | parent = (context ? context : $("#" + rootId));
78 | $toInject = void 0;
79 | if (itLooksLikeHtml(arg)) {
80 | $toInject = $(arg);
81 | } else {
82 | config = $.extend({}, defaults, arg, {
83 | userString: arg
84 | });
85 | $toInject = $("<" + config.el + ">" + config.el + ">");
86 | applyAttributes($toInject, config);
87 | injectContents($toInject, config);
88 | }
89 | return $toInject.appendTo(parent);
90 | },
91 | configure: function(config) {
92 | return $.extend(defaults, config);
93 | },
94 | restoreDefaults: function() {
95 | return defaults = $.extend({}, defaultConfiguration);
96 | },
97 | noConflict: function() {
98 | root.jasmineFixture = originalJasmineFixture;
99 | root.inject = originalInject;
100 | root.affix = originalAffix;
101 | return this;
102 | }
103 | };
104 | $.fn.inject = function(html) {
105 | return $.jasmine.inject(html, $(this));
106 | };
107 | applyAttributes = function($html, config) {
108 | var attrs, key, _results;
109 | attrs = $.extend({}, {
110 | id: config.id,
111 | "class": config["class"] || config.cssClass
112 | }, config.attrs);
113 | if (isString(config.userString)) {
114 | attrs[config.defaultAttribute] = config.userString;
115 | }
116 | _results = [];
117 | for (key in attrs) {
118 | if (attrs[key]) {
119 | _results.push($html.attr(key, attrs[key]));
120 | } else {
121 | _results.push(void 0);
122 | }
123 | }
124 | return _results;
125 | };
126 | injectContents = function($el, config) {
127 | if (config.text && config.html) {
128 | throw "Error: because they conflict, you may only configure inject() to set `html` or `text`, not both! \n\nHTML was: " + config.html + " \n\n Text was: " + config.text;
129 | } else if (config.text) {
130 | return $el.text(config.text);
131 | } else {
132 | if (config.html) {
133 | return $el.html(config.html);
134 | }
135 | }
136 | };
137 | itLooksLikeHtml = function(arg) {
138 | return isString(arg) && arg.indexOf("<") !== -1;
139 | };
140 | isString = function(arg) {
141 | return arg && arg.constructor === String;
142 | };
143 | init = function() {
144 | $("body").append("
");
145 | return isReady = true;
146 | };
147 | tidyUp = function() {
148 | $("#" + rootId).remove();
149 | return isReady = false;
150 | };
151 | $(function($) {
152 | return init();
153 | });
154 | afterEach(function() {
155 | return tidyUp();
156 | });
157 | return $.jasmine;
158 | };
159 | if ($) {
160 | jasmineFixture = root.jasmineFixture($);
161 | return root.inject = root.inject || jasmineFixture.inject;
162 | }
163 | })(window.jQuery);
164 |
165 | createHTMLBlock = (function() {
166 | var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
167 | createHTMLBlock = function($, ZenObject, data, functions, indexes) {
168 | var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
169 | if ($.isPlainObject(ZenObject)) {
170 | ZenCode = ZenObject.main;
171 | } else {
172 | ZenCode = ZenObject;
173 | ZenObject = {
174 | main: ZenCode
175 | };
176 | }
177 | origZenCode = ZenCode;
178 | if (indexes === undefined) {
179 | indexes = {};
180 | }
181 | if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
182 | if ($.isArray(data)) {
183 | forScope = ZenCode;
184 | } else {
185 | obj = parseEnclosure(ZenCode, "!");
186 | obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
187 | forScope = parseVariableScope(ZenCode);
188 | }
189 | while (forScope.charAt(0) === "@") {
190 | forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
191 | }
192 | zo = ZenObject;
193 | zo.main = forScope;
194 | el = $();
195 | if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
196 | if (!$.isArray(data) && obj.indexOf(":") > 0) {
197 | indexName = obj.substring(0, obj.indexOf(":"));
198 | obj = obj.substr(obj.indexOf(":") + 1);
199 | }
200 | arr = ($.isArray(data) ? data : data[obj]);
201 | zc = zo.main;
202 | if ($.isArray(arr) || $.isPlainObject(arr)) {
203 | $.map(arr, function(value, index) {
204 | var next;
205 | zo.main = zc;
206 | if (indexName !== undefined) {
207 | indexes[indexName] = index;
208 | }
209 | if (!$.isPlainObject(value)) {
210 | value = {
211 | value: value
212 | };
213 | }
214 | next = createHTMLBlock($, zo, value, functions, indexes);
215 | if (el.length !== 0) {
216 | return $.each(next, function(index, value) {
217 | return el.push(value);
218 | });
219 | }
220 | });
221 | }
222 | if (!$.isArray(data)) {
223 | ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
224 | } else {
225 | ZenCode = "";
226 | }
227 | } else if (ZenCode.substring(0, 4) === "!if:") {
228 | result = parseContents("!" + obj + "!", data, indexes);
229 | if (result !== "undefined" || result !== "false" || result !== "") {
230 | el = createHTMLBlock($, zo, data, functions, indexes);
231 | }
232 | ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
233 | }
234 | ZenObject.main = ZenCode;
235 | } else if (ZenCode.charAt(0) === "(") {
236 | paren = parseEnclosure(ZenCode, "(", ")");
237 | inner = paren.substring(1, paren.length - 1);
238 | ZenCode = ZenCode.substr(paren.length);
239 | zo = ZenObject;
240 | zo.main = inner;
241 | el = createHTMLBlock($, zo, data, functions, indexes);
242 | } else {
243 | blocks = ZenCode.match(regZenTagDfn);
244 | block = blocks[0];
245 | if (block.length === 0) {
246 | return "";
247 | }
248 | if (block.indexOf("@") >= 0) {
249 | ZenCode = parseReferences(ZenCode, ZenObject);
250 | zo = ZenObject;
251 | zo.main = ZenCode;
252 | return createHTMLBlock($, zo, data, functions, indexes);
253 | }
254 | block = parseContents(block, data, indexes);
255 | blockClasses = parseClasses($, block);
256 | if (regId.test(block)) {
257 | blockId = regId.exec(block)[1];
258 | }
259 | blockAttrs = parseAttributes(block, data);
260 | blockTag = (block.charAt(0) === "{" ? "span" : "div");
261 | if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
262 | blockTag = regTag.exec(block)[1];
263 | }
264 | if (block.search(regCBrace) !== -1) {
265 | blockHTML = block.match(regCBrace)[1];
266 | }
267 | blockAttrs = $.extend(blockAttrs, {
268 | id: blockId,
269 | "class": blockClasses,
270 | html: blockHTML
271 | });
272 | el = $("<" + blockTag + ">", blockAttrs);
273 | el.attr(blockAttrs);
274 | el = bindEvents(block, el, functions);
275 | el = bindData(block, el, data);
276 | ZenCode = ZenCode.substr(blocks[0].length);
277 | ZenObject.main = ZenCode;
278 | }
279 | if (ZenCode.length > 0) {
280 | if (ZenCode.charAt(0) === ">") {
281 | if (ZenCode.charAt(1) === "(") {
282 | zc = parseEnclosure(ZenCode.substr(1), "(", ")");
283 | ZenCode = ZenCode.substr(zc.length + 1);
284 | } else if (ZenCode.charAt(1) === "!") {
285 | obj = parseEnclosure(ZenCode.substr(1), "!");
286 | forScope = parseVariableScope(ZenCode.substr(1));
287 | zc = obj + forScope;
288 | ZenCode = ZenCode.substr(zc.length + 1);
289 | } else {
290 | len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
291 | zc = ZenCode.substring(1, len);
292 | ZenCode = ZenCode.substr(len);
293 | }
294 | zo = ZenObject;
295 | zo.main = zc;
296 | els = $(createHTMLBlock($, zo, data, functions, indexes));
297 | els.appendTo(el);
298 | }
299 | if (ZenCode.charAt(0) === "+") {
300 | zo = ZenObject;
301 | zo.main = ZenCode.substr(1);
302 | el2 = createHTMLBlock($, zo, data, functions, indexes);
303 | $.each(el2, function(index, value) {
304 | return el.push(value);
305 | });
306 | }
307 | }
308 | ret = el;
309 | return ret;
310 | };
311 | bindData = function(ZenCode, el, data) {
312 | var datas, i, split;
313 | if (ZenCode.search(regDatas) === 0) {
314 | return el;
315 | }
316 | datas = ZenCode.match(regDatas);
317 | if (datas === null) {
318 | return el;
319 | }
320 | i = 0;
321 | while (i < datas.length) {
322 | split = regData.exec(datas[i]);
323 | if (split[3] === undefined) {
324 | $(el).data(split[1], data[split[1]]);
325 | } else {
326 | $(el).data(split[1], data[split[3]]);
327 | }
328 | i++;
329 | }
330 | return el;
331 | };
332 | bindEvents = function(ZenCode, el, functions) {
333 | var bindings, fn, i, split;
334 | if (ZenCode.search(regEvents) === 0) {
335 | return el;
336 | }
337 | bindings = ZenCode.match(regEvents);
338 | if (bindings === null) {
339 | return el;
340 | }
341 | i = 0;
342 | while (i < bindings.length) {
343 | split = regEvent.exec(bindings[i]);
344 | if (split[2] === undefined) {
345 | fn = functions[split[1]];
346 | } else {
347 | fn = functions[split[2]];
348 | }
349 | $(el).bind(split[1], fn);
350 | i++;
351 | }
352 | return el;
353 | };
354 | parseAttributes = function(ZenBlock, data) {
355 | var attrStrs, attrs, i, parts;
356 | if (ZenBlock.search(regAttrDfn) === -1) {
357 | return undefined;
358 | }
359 | attrStrs = ZenBlock.match(regAttrDfn);
360 | attrs = {};
361 | i = 0;
362 | while (i < attrStrs.length) {
363 | parts = regAttr.exec(attrStrs[i]);
364 | attrs[parts[1]] = "";
365 | if (parts[3] !== undefined) {
366 | attrs[parts[1]] = parseContents(parts[3], data);
367 | }
368 | i++;
369 | }
370 | return attrs;
371 | };
372 | parseClasses = function($, ZenBlock) {
373 | var classes, clsString, i;
374 | ZenBlock = ZenBlock.match(regTagNotContent)[0];
375 | if (ZenBlock.search(regClasses) === -1) {
376 | return undefined;
377 | }
378 | classes = ZenBlock.match(regClasses);
379 | clsString = "";
380 | i = 0;
381 | while (i < classes.length) {
382 | clsString += " " + regClass.exec(classes[i])[1];
383 | i++;
384 | }
385 | return $.trim(clsString);
386 | };
387 | parseContents = function(ZenBlock, data, indexes) {
388 | var html;
389 | if (indexes === undefined) {
390 | indexes = {};
391 | }
392 | html = ZenBlock;
393 | if (data === undefined) {
394 | return html;
395 | }
396 | while (regExclamation.test(html)) {
397 | html = html.replace(regExclamation, function(str, str2) {
398 | var begChar, fn, val;
399 | begChar = "";
400 | if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
401 | return str;
402 | }
403 | if (str.charAt(0) !== "!") {
404 | begChar = str.charAt(0);
405 | str = str.substring(2, str.length - 1);
406 | }
407 | fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
408 | val = unescape(fn(data, indexes));
409 | return begChar + val;
410 | });
411 | }
412 | html = html.replace(/\\./g, function(str) {
413 | return str.charAt(1);
414 | });
415 | return unescape(html);
416 | };
417 | parseEnclosure = function(ZenCode, open, close, count) {
418 | var index, ret;
419 | if (close === undefined) {
420 | close = open;
421 | }
422 | index = 1;
423 | if (count === undefined) {
424 | count = (ZenCode.charAt(0) === open ? 1 : 0);
425 | }
426 | if (count === 0) {
427 | return;
428 | }
429 | while (count > 0 && index < ZenCode.length) {
430 | if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
431 | count--;
432 | } else {
433 | if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
434 | count++;
435 | }
436 | }
437 | index++;
438 | }
439 | ret = ZenCode.substring(0, index);
440 | return ret;
441 | };
442 | parseReferences = function(ZenCode, ZenObject) {
443 | ZenCode = ZenCode.replace(regReference, function(str) {
444 | var fn;
445 | str = str.substr(1);
446 | fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
447 | return fn(ZenObject, parseReferences);
448 | });
449 | return ZenCode;
450 | };
451 | parseVariableScope = function(ZenCode) {
452 | var forCode, rest, tag;
453 | if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
454 | return undefined;
455 | }
456 | forCode = parseEnclosure(ZenCode, "!");
457 | ZenCode = ZenCode.substr(forCode.length);
458 | if (ZenCode.charAt(0) === "(") {
459 | return parseEnclosure(ZenCode, "(", ")");
460 | }
461 | tag = ZenCode.match(regZenTagDfn)[0];
462 | ZenCode = ZenCode.substr(tag.length);
463 | if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
464 | return tag;
465 | } else if (ZenCode.charAt(0) === ">") {
466 | rest = "";
467 | rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
468 | return tag + ">" + rest;
469 | }
470 | return undefined;
471 | };
472 | regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
473 | regTag = /(\w+)/i;
474 | regId = /#([\w-!]+)/i;
475 | regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
476 | regClasses = /(\.[\w-]+)/g;
477 | regClass = /\.([\w-]+)/i;
478 | regReference = /(@[\w$_][\w$_\d]+)/i;
479 | regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
480 | regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
481 | regAttr = /([\w-!]+)(="?(([^"\]]|\\")+)"?)?/i;
482 | regCBrace = /\{(([^\}]|\\\})+)\}/i;
483 | regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
484 | regEvents = /\~[\w$]+(=[\w$]+)?/g;
485 | regEvent = /\~([\w$]+)=([\w$]+)/i;
486 | regDatas = /&[\w$]+(=[\w$]+)?/g;
487 | regData = /&([\w$]+)(=([\w$]+))?/i;
488 | return createHTMLBlock;
489 | })();
490 |
491 | }).call(this);
492 |
--------------------------------------------------------------------------------
/spec/helpers/jasmine-given.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | jasmine-given 2.0.0
4 | Adds a Given-When-Then DSL to jasmine as an alternative style for specs
5 | site: https://github.com/searls/jasmine-given
6 | */
7 |
8 |
9 | (function() {
10 |
11 | (function(jasmine) {
12 | var getBlock, mostRecentExpectations, mostRecentlyUsed, o, root, stringifyExpectation, whenList;
13 | mostRecentlyUsed = null;
14 | stringifyExpectation = function(expectation) {
15 | var matches;
16 | matches = expectation.toString().replace(/\n/g, '').match(/function\s?\(\)\s?{\s*(return\s+)?(.*?)(;)?\s*}/i);
17 | if (matches && matches.length >= 3) {
18 | return matches[2];
19 | } else {
20 | return "";
21 | }
22 | };
23 | beforeEach(function() {
24 | return this.addMatchers({
25 | toHaveReturnedFalseFromThen: function(context, n) {
26 | var exception, result;
27 | result = false;
28 | exception = void 0;
29 | try {
30 | result = this.actual.call(context);
31 | } catch (e) {
32 | exception = e;
33 | }
34 | this.message = function() {
35 | var msg;
36 | msg = "Then clause " + (n > 1 ? " #" + n : "") + " `" + (stringifyExpectation(this.actual)) + "` failed by ";
37 | if (exception) {
38 | msg += "throwing: " + exception.toString();
39 | } else {
40 | msg += "returning false";
41 | }
42 | return msg;
43 | };
44 | return result === false;
45 | }
46 | });
47 | });
48 | root = this;
49 | root.Given = function() {
50 | mostRecentlyUsed = root.Given;
51 | return beforeEach(getBlock(arguments));
52 | };
53 | whenList = [];
54 | root.When = function() {
55 | var b;
56 | mostRecentlyUsed = root.When;
57 | b = getBlock(arguments);
58 | beforeEach(function() {
59 | return whenList.push(b);
60 | });
61 | return afterEach(function() {
62 | return whenList.pop();
63 | });
64 | };
65 | getBlock = function(thing) {
66 | var assignResultTo, setupFunction;
67 | setupFunction = o(thing).firstThat(function(arg) {
68 | return o(arg).isFunction();
69 | });
70 | assignResultTo = o(thing).firstThat(function(arg) {
71 | return o(arg).isString();
72 | });
73 | return function() {
74 | var context, result;
75 | context = jasmine.getEnv().currentSpec;
76 | result = setupFunction.call(context);
77 | if (assignResultTo) {
78 | if (!context[assignResultTo]) {
79 | return context[assignResultTo] = result;
80 | } else {
81 | throw new Error("Unfortunately, the variable '" + assignResultTo + "' is already assigned to: " + context[assignResultTo]);
82 | }
83 | }
84 | };
85 | };
86 | mostRecentExpectations = null;
87 | root.Then = function() {
88 | var expectationFunction, expectations, label;
89 | label = o(arguments).firstThat(function(arg) {
90 | return o(arg).isString();
91 | });
92 | expectationFunction = o(arguments).firstThat(function(arg) {
93 | return o(arg).isFunction();
94 | });
95 | mostRecentlyUsed = root.subsequentThen;
96 | mostRecentExpectations = expectations = [expectationFunction];
97 | it("then " + (label != null ? label : stringifyExpectation(expectations)), function() {
98 | var block, i, _i, _len, _ref, _results;
99 | _ref = whenList != null ? whenList : [];
100 | for (_i = 0, _len = _ref.length; _i < _len; _i++) {
101 | block = _ref[_i];
102 | block();
103 | }
104 | i = 0;
105 | _results = [];
106 | while (i < expectations.length) {
107 | expect(expectations[i]).not.toHaveReturnedFalseFromThen(jasmine.getEnv().currentSpec, i + 1);
108 | _results.push(i++);
109 | }
110 | return _results;
111 | });
112 | return {
113 | Then: subsequentThen,
114 | And: subsequentThen
115 | };
116 | };
117 | root.subsequentThen = function(additionalExpectation) {
118 | mostRecentExpectations.push(additionalExpectation);
119 | return this;
120 | };
121 | mostRecentlyUsed = root.Given;
122 | root.And = function() {
123 | return mostRecentlyUsed.apply(this, jasmine.util.argsToArray(arguments));
124 | };
125 | return o = function(thing) {
126 | return {
127 | isFunction: function() {
128 | return Object.prototype.toString.call(thing) === "[object Function]";
129 | },
130 | isString: function() {
131 | return Object.prototype.toString.call(thing) === "[object String]";
132 | },
133 | firstThat: function(test) {
134 | var i;
135 | i = 0;
136 | while (i < thing.length) {
137 | if (test(thing[i]) === true) {
138 | return thing[i];
139 | }
140 | i++;
141 | }
142 | return void 0;
143 | }
144 | };
145 | };
146 | })(jasmine);
147 |
148 | }).call(this);
149 |
--------------------------------------------------------------------------------
/spec/helpers/jasmine-stealth.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.3.3
2 |
3 | /*
4 | jasmine-stealth 0.0.12
5 | Makes Jasmine spies a bit more robust
6 | site: https://github.com/searls/jasmine-stealth
7 | */
8 |
9 |
10 | (function() {
11 | var Captor, fake, root, unfakes, whatToDoWhenTheSpyGetsCalled, _,
12 | __hasProp = {}.hasOwnProperty,
13 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
14 |
15 | root = this;
16 |
17 | _ = function(obj) {
18 | return {
19 | each: function(iterator) {
20 | var item, _i, _len, _results;
21 | _results = [];
22 | for (_i = 0, _len = obj.length; _i < _len; _i++) {
23 | item = obj[_i];
24 | _results.push(iterator(item));
25 | }
26 | return _results;
27 | },
28 | isFunction: function() {
29 | return Object.prototype.toString.call(obj) === "[object Function]";
30 | },
31 | isString: function() {
32 | return Object.prototype.toString.call(obj) === "[object String]";
33 | }
34 | };
35 | };
36 |
37 | root.spyOnConstructor = function(owner, classToFake, methodsToSpy) {
38 | var fakeClass, spies;
39 | if (methodsToSpy == null) {
40 | methodsToSpy = [];
41 | }
42 | if (_(methodsToSpy).isString()) {
43 | methodsToSpy = [methodsToSpy];
44 | }
45 | spies = {
46 | constructor: jasmine.createSpy("" + classToFake + "'s constructor")
47 | };
48 | fakeClass = (function() {
49 |
50 | function _Class() {
51 | spies.constructor.apply(this, arguments);
52 | }
53 |
54 | return _Class;
55 |
56 | })();
57 | _(methodsToSpy).each(function(methodName) {
58 | spies[methodName] = jasmine.createSpy("" + classToFake + "#" + methodName);
59 | return fakeClass.prototype[methodName] = function() {
60 | return spies[methodName].apply(this, arguments);
61 | };
62 | });
63 | fake(owner, classToFake, fakeClass);
64 | return spies;
65 | };
66 |
67 | unfakes = [];
68 |
69 | afterEach(function() {
70 | _(unfakes).each(function(u) {
71 | return u();
72 | });
73 | return unfakes = [];
74 | });
75 |
76 | fake = function(owner, thingToFake, newThing) {
77 | var originalThing;
78 | originalThing = owner[thingToFake];
79 | owner[thingToFake] = newThing;
80 | return unfakes.push(function() {
81 | return owner[thingToFake] = originalThing;
82 | });
83 | };
84 |
85 | root.stubFor = root.spyOn;
86 |
87 | jasmine.createStub = jasmine.createSpy;
88 |
89 | jasmine.createStubObj = function(baseName, stubbings) {
90 | var name, obj, stubbing;
91 | if (stubbings.constructor === Array) {
92 | return jasmine.createSpyObj(baseName, stubbings);
93 | } else {
94 | obj = {};
95 | for (name in stubbings) {
96 | stubbing = stubbings[name];
97 | obj[name] = jasmine.createSpy(baseName + "." + name);
98 | if (_(stubbing).isFunction()) {
99 | obj[name].andCallFake(stubbing);
100 | } else {
101 | obj[name].andReturn(stubbing);
102 | }
103 | }
104 | return obj;
105 | }
106 | };
107 |
108 | whatToDoWhenTheSpyGetsCalled = function(spy) {
109 | var matchesStub, priorStubbing;
110 | matchesStub = function(stubbing, args, context) {
111 | switch (stubbing.type) {
112 | case "args":
113 | return jasmine.getEnv().equals_(stubbing.ifThis, jasmine.util.argsToArray(args));
114 | case "context":
115 | return jasmine.getEnv().equals_(stubbing.ifThis, context);
116 | }
117 | };
118 | priorStubbing = spy.plan();
119 | return spy.andCallFake(function() {
120 | var i, stubbing;
121 | i = 0;
122 | while (i < spy._stealth_stubbings.length) {
123 | stubbing = spy._stealth_stubbings[i];
124 | if (matchesStub(stubbing, arguments, this)) {
125 | if (Object.prototype.toString.call(stubbing.thenThat) === "[object Function]") {
126 | return stubbing.thenThat();
127 | } else {
128 | return stubbing.thenThat;
129 | }
130 | }
131 | i++;
132 | }
133 | return priorStubbing;
134 | });
135 | };
136 |
137 | jasmine.Spy.prototype.whenContext = function(context) {
138 | var addStubbing, spy;
139 | spy = this;
140 | spy._stealth_stubbings || (spy._stealth_stubbings = []);
141 | whatToDoWhenTheSpyGetsCalled(spy);
142 | addStubbing = function(thenThat) {
143 | spy._stealth_stubbings.push({
144 | type: 'context',
145 | ifThis: context,
146 | thenThat: thenThat
147 | });
148 | return spy;
149 | };
150 | return {
151 | thenReturn: addStubbing,
152 | thenCallFake: addStubbing
153 | };
154 | };
155 |
156 | jasmine.Spy.prototype.when = function() {
157 | var addStubbing, ifThis, spy;
158 | spy = this;
159 | ifThis = jasmine.util.argsToArray(arguments);
160 | spy._stealth_stubbings || (spy._stealth_stubbings = []);
161 | whatToDoWhenTheSpyGetsCalled(spy);
162 | addStubbing = function(thenThat) {
163 | spy._stealth_stubbings.push({
164 | type: 'args',
165 | ifThis: ifThis,
166 | thenThat: thenThat
167 | });
168 | return spy;
169 | };
170 | return {
171 | thenReturn: addStubbing,
172 | thenCallFake: addStubbing
173 | };
174 | };
175 |
176 | jasmine.Spy.prototype.mostRecentCallThat = function(callThat, context) {
177 | var i;
178 | i = this.calls.length - 1;
179 | while (i >= 0) {
180 | if (callThat.call(context || this, this.calls[i]) === true) {
181 | return this.calls[i];
182 | }
183 | i--;
184 | }
185 | };
186 |
187 | jasmine.Matchers.ArgThat = (function(_super) {
188 |
189 | __extends(ArgThat, _super);
190 |
191 | function ArgThat(matcher) {
192 | this.matcher = matcher;
193 | }
194 |
195 | ArgThat.prototype.jasmineMatches = function(actual) {
196 | return this.matcher(actual);
197 | };
198 |
199 | return ArgThat;
200 |
201 | })(jasmine.Matchers.Any);
202 |
203 | jasmine.Matchers.ArgThat.prototype.matches = jasmine.Matchers.ArgThat.prototype.jasmineMatches;
204 |
205 | jasmine.argThat = function(expected) {
206 | return new jasmine.Matchers.ArgThat(expected);
207 | };
208 |
209 | jasmine.Matchers.Capture = (function(_super) {
210 |
211 | __extends(Capture, _super);
212 |
213 | function Capture(captor) {
214 | this.captor = captor;
215 | }
216 |
217 | Capture.prototype.jasmineMatches = function(actual) {
218 | this.captor.value = actual;
219 | return true;
220 | };
221 |
222 | return Capture;
223 |
224 | })(jasmine.Matchers.Any);
225 |
226 | jasmine.Matchers.Capture.prototype.matches = jasmine.Matchers.Capture.prototype.jasmineMatches;
227 |
228 | Captor = (function() {
229 |
230 | function Captor() {}
231 |
232 | Captor.prototype.capture = function() {
233 | return new jasmine.Matchers.Capture(this);
234 | };
235 |
236 | return Captor;
237 |
238 | })();
239 |
240 | jasmine.captor = function() {
241 | return new Captor();
242 | };
243 |
244 | }).call(this);
245 |
--------------------------------------------------------------------------------
/spec/lib/cache_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'Cache', ->
2 | beforeEach ->
3 | @cache = new Cache
4 |
5 | describe 'fetch', ->
6 | it 'sets cache with constructor', ->
7 | expect(@cache.fetch('key', -> 'value')).toEqual('value')
8 | expect(@cache.get('key')).toEqual('value')
9 |
10 | it 'returns exsiting value', ->
11 | @cache.set 'key', 'previous'
12 | @cache.fetch('key', -> 'new value')
13 | expect(@cache.get('key')).toEqual('previous')
14 |
15 | describe 'clean', ->
16 | it 'removes least recently used keys', ->
17 | @cache.set 'foo', 'a'
18 | @cache.set 'bar', 'b'
19 | @cache.set 'baz', 'b'
20 | @cache.get 'bar'
21 | @cache.clean(1)
22 | expect(@cache.keys).toEqual(['bar'])
23 |
--------------------------------------------------------------------------------
/spec/lib/paginated_collection_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'PaginatedCollection', ->
2 | beforeEach ->
3 | @collection = new PaginatedCollection()
4 | @request = $.Deferred()
5 | @xhr =
6 | getResponseHeader: jasmine.createSpy('getResponseHeader')
7 |
8 | spyOn($, 'ajax').andReturn(@request)
9 |
10 | describe 'paginate', ->
11 | context 'when there is a next link', ->
12 | beforeEach ->
13 | @xhr.getResponseHeader.andReturn('; rel="next"')
14 |
15 | it 'fetches next page', ->
16 | spyOn @collection, 'fetch'
17 | @collection.paginate({}, {}, @xhr)
18 | expect(@collection.fetch).toHaveBeenCalledWith({url:'/url?page=2', reset: false, remove:false})
19 |
20 | context 'when there is not a next link', ->
21 | beforeEach ->
22 | @xhr.getResponseHeader.andReturn('; rel="prev"')
23 | spyOn @collection, 'fetch'
24 |
25 | it 'does not call fetch', ->
26 | @collection.paginate({}, {}, @xhr)
27 | expect(@collection.fetch).not.toHaveBeenCalled()
28 |
29 | it 'triggers "paginated" event', ->
30 | spy = jasmine.createSpy()
31 | @collection.on 'paginated', spy
32 | @collection.paginate({}, {}, @xhr)
33 | expect(spy).toHaveBeenCalled()
34 |
--------------------------------------------------------------------------------
/spec/models/comment_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Models.Comment', ->
2 | describe 'isUnread', ->
3 | beforeEach ->
4 | @subject = new App.Models.Subject(last_read_at: moment('2013-12-25'))
5 | @collection = new App.Collections.Comments([], subject: @subject)
6 |
7 | it 'is true if no collection', ->
8 | comment = new App.Models.Comment()
9 | expect(comment.isUnread()).toBe(true)
10 |
11 | it 'is true if subject does not have last_read_at', ->
12 | @subject.last_read_at = null
13 | comment = new App.Models.Comment({}, collection: @collection)
14 | expect(comment.isUnread()).toBe(true)
15 |
16 | it 'is true created_at is later than last_read_at on subject', ->
17 | comment = new App.Models.Comment({created_at: moment('2013-12-26')}, collection: @collection)
18 | expect(comment.isUnread()).toBe(true)
19 |
20 | it 'is false if created_at is earlier than last_read_at on subject', ->
21 | comment = new App.Models.Comment({created_at: moment('2013-12-24')}, collection: @collection)
22 | expect(comment.isUnread()).toBe(false)
23 |
--------------------------------------------------------------------------------
/spec/models/oauth_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Models.OAuth', ->
2 | beforeEach ->
3 | spyOn app, 'ajax'
4 |
5 | # Test double for window.location
6 | @location =
7 | assign: jasmine.createSpy('assign')
8 | search: ''
9 | pathname: '/foobar'
10 |
11 | App.Models.OAuth.prototype.location = @location
12 |
13 | @oauth = new App.Models.OAuth
14 |
15 | describe 'redirect', ->
16 | it 'changes window.location', ->
17 | @oauth.redirect(client_id: 123, scope: 'scope')
18 | expect(@location.assign).toHaveBeenCalledWith(
19 | "https://github.com/login/oauth/authorize?client_id=123&scope=scope"
20 | )
21 |
22 | describe 'getCode', ->
23 | it 'returns undefined if href does not contain a code', ->
24 | expect(@oauth.getCode()).toBe(undefined)
25 |
26 | it 'returns code from window.location', ->
27 | @location.search = 'foo=bar&code=omg'
28 | expect(@oauth.getCode()).toEqual('omg')
29 |
--------------------------------------------------------------------------------
/spec/models/repository_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Models.Repository', ->
2 | describe 'decrement', ->
3 | it 'decreases count', ->
4 | @repository = new App.Models.Repository(unread_count: 5)
5 | @repository.decrement()
6 | expect(@repository.get('unread_count')).toBe(4)
7 |
8 | it 'does not go below 0', ->
9 | @repository = new App.Models.Repository(unread_count: 0)
10 | @repository.decrement()
11 | expect(@repository.get('unread_count')).toBe(0)
12 |
--------------------------------------------------------------------------------
/spec/models/subject_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Models.Subject', ->
2 | describe 'isUnread', ->
3 | it 'is true without last_read_at', ->
4 | model = new App.Models.Subject(last_read_at: null)
5 | expect(model.isUnread()).toBe(true)
6 |
7 | it 'is true created_at is later than last_read_at', ->
8 | model = new App.Models.Subject(created_at: moment('2013-12-26'), last_read_at: moment('2013-12-25'))
9 | expect(model.isUnread()).toBe(true)
10 |
11 | it 'is false if created_at is earlier than last_read_at', ->
12 | model = new App.Models.Subject(created_at: moment('2013-12-24'), last_read_at: moment('2013-12-25'))
13 | expect(model.isUnread()).toBe(false)
14 |
--------------------------------------------------------------------------------
/spec/models/token_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Models.Token', ->
2 | beforeEach ->
3 | @localStorage = App.Models.Token.localStorage = {}
4 |
5 | describe 'get', ->
6 | it 'gets token from localStorage', ->
7 | @localStorage['token'] = 'from localStorage'
8 | expect(App.Models.Token.get()).toEqual('from localStorage')
9 |
10 | describe 'set', ->
11 | it 'sets token in localStorage', ->
12 | App.Models.Token.set('foo')
13 | expect(@localStorage['token']).toEqual('foo')
14 |
--------------------------------------------------------------------------------
/spec/views/subject_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Views.Subject', ->
2 | beforeEach ->
3 | @view = new App.Models.Subject
4 |
5 | describe 'for', ->
6 | it 'returns PullRequest', ->
7 | model = new App.Models.Subject(type: 'PullRequest')
8 | expect(App.Views.Subject.for(model)).toBe(App.Views.Subject.PullRequest)
9 |
10 | it 'returns Issue', ->
11 | model = new App.Models.Subject(type: 'Issue')
12 | expect(App.Views.Subject.for(model)).toBe(App.Views.Subject.Issue)
13 |
14 | it 'returns Commit', ->
15 | model = new App.Models.Subject(type: 'Commit')
16 | expect(App.Views.Subject.for(model)).toBe(App.Views.Subject.Commit)
17 |
18 | it 'returns Subject for unknown type', ->
19 | model = new Backbone.Model()
20 | expect(App.Views.Subject.for(model)).toBe(App.Views.Subject.Unknown)
21 |
--------------------------------------------------------------------------------
/spec/views/timeline/event_spec.coffee:
--------------------------------------------------------------------------------
1 | describe 'App.Views.TimelineEvent', ->
2 | beforeEach ->
3 | @notification = new Backbone.Model
4 | repository: {html_url: 'https://github.com/bkeepers/github-notifications'}
5 | @issue = new App.Models.Subject.Issue({}, {notification: @notification})
6 | @pull = new App.Models.Subject.PullRequest(
7 | {head: {label: 'github:feature-branch'}, base: {label: 'github:master'}},
8 | {notification: @notification}
9 | )
10 |
11 | @event = new App.Models.Event(payload)
12 |
13 | @event.collection = {subject: @issue}
14 | @view = new App.Views.TimelineEvent(model: @event)
15 |
16 | text = ($el) ->
17 | $el.text().replace(/\s+/g, ' ')
18 |
19 | describe 'closed', ->
20 | it 'renders for an issue', ->
21 | @view.render()
22 | expect(text(@view.$el)).toMatch(/octocat closed the issue/)
23 |
24 | it 'renders for a pull request', ->
25 | @event.collection.subject = @pull
26 | @view.render()
27 | expect(text(@view.$el)).toMatch(/octocat closed the pull request/)
28 |
29 | describe 'reopened', ->
30 | beforeEach ->
31 | @event.set event: 'reopened'
32 |
33 | it 'renders for an issue', ->
34 | @view.render()
35 | expect(text(@view.$el)).toMatch(/octocat reopened the issue/)
36 |
37 | it 'renders for a pull request', ->
38 | @event.collection.subject = @pull
39 | @view.render()
40 | expect(text(@view.$el)).toMatch(/octocat reopened the pull request/)
41 |
42 | describe 'merged', ->
43 | beforeEach ->
44 | @event.collection.subject = @pull
45 | @event.set event: 'merged'
46 |
47 | it 'renders', ->
48 | @view.render()
49 | expect(text(@view.$el)).toMatch(/octocat merged commit 6dcb09b5 into github:master from github:feature-branch/)
50 |
51 | it 'links to commit', ->
52 | @view.render()
53 | expected = 'https://github.com/bkeepers/github-notifications/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e'
54 | expect(@view.$('.git-sha').attr('href')).toEqual(expected)
55 |
56 | describe 'referenced', ->
57 | beforeEach ->
58 | @event.set event: 'referenced'
59 |
60 | # TODO: item references (currently missing from GitHub API)
61 |
62 | describe 'a commit', ->
63 | # TODO: link to commit
64 |
65 | it 'renders', ->
66 | @view.render()
67 | expect(text(@view.$el)).toMatch(/referenced this issue from commit 6dcb09b5/)
68 |
69 | it 'links to commit', ->
70 | @view.render()
71 | expected = 'https://github.com/bkeepers/github-notifications/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e'
72 | expect(@view.$('.git-sha').attr('href')).toEqual(expected)
73 |
74 | payload = {
75 | "id": 1,
76 | "url": "https://api.github.com/repos/octocat/Hello-World/issues/events/1",
77 | "actor": {
78 | "login": "octocat",
79 | "id": 1,
80 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
81 | "gravatar_id": "somehexcode",
82 | "url": "https://api.github.com/users/octocat",
83 | "html_url": "https://github.com/octocat",
84 | "type": "User",
85 | },
86 | "event": "closed",
87 | "commit_id": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
88 | "created_at": "2011-04-14T16:00:49Z"
89 | }
90 |
--------------------------------------------------------------------------------
/tasks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bkeepers/github-notifications/6055b9ff198214802f7500a4862bcbf4ac86b59a/tasks/.gitkeep
--------------------------------------------------------------------------------
/vendor/css/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bkeepers/github-notifications/6055b9ff198214802f7500a4862bcbf4ac86b59a/vendor/css/.gitkeep
--------------------------------------------------------------------------------
/vendor/css/primer.css:
--------------------------------------------------------------------------------
1 | .flash-messages {
2 | margin-top: 15px;
3 | margin-bottom: 15px; }
4 |
5 | .flash,
6 | .flash-global {
7 | position: relative;
8 | border: 1px solid #97c1da;
9 | color: #264c72;
10 | background-color: #d0e3ef;
11 | background-image: -moz-linear-gradient(#d8ebf8, #d0e3ef);
12 | background-image: -webkit-linear-gradient(#d8ebf8, #d0e3ef);
13 | background-image: linear-gradient(#d8ebf8, #d0e3ef);
14 | background-repeat: repeat-x;
15 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); }
16 | .flash.flash-warn,
17 | .flash-global.flash-warn {
18 | color: #613A00;
19 | background-color: #f5dac0;
20 | background-image: -moz-linear-gradient(#ffe3c8, #f5dac0);
21 | background-image: -webkit-linear-gradient(#ffe3c8, #f5dac0);
22 | background-image: linear-gradient(#ffe3c8, #f5dac0);
23 | background-repeat: repeat-x;
24 | border-color: #dca874; }
25 | .flash.flash-error,
26 | .flash-global.flash-error {
27 | color: #911;
28 | background-color: #efd0d0;
29 | background-image: -moz-linear-gradient(#f8d8d8, #efd0d0);
30 | background-image: -webkit-linear-gradient(#f8d8d8, #efd0d0);
31 | background-image: linear-gradient(#f8d8d8, #efd0d0);
32 | background-repeat: repeat-x;
33 | border-color: #da9797; }
34 | .flash:hover,
35 | .flash-global:hover {
36 | border-color: #5f9fc6; }
37 | .flash.flash-warn:hover,
38 | .flash-global.flash-warn:hover {
39 | border-color: #cd8237; }
40 | .flash.flash-error:hover,
41 | .flash-global.flash-error:hover {
42 | border-color: #c65f5f; }
43 | .flash .flash-action,
44 | .flash-global .flash-action {
45 | float: right;
46 | margin-top: -4px;
47 | margin-left: 20px; }
48 |
49 | .flash {
50 | padding: 15px;
51 | border-radius: 3px;
52 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); }
53 | .flash + .flash {
54 | margin-top: 5px; }
55 | .flash .close {
56 | float: right;
57 | cursor: pointer;
58 | opacity: 0.6;
59 | text-decoration: none;
60 | margin-top: 1px; }
61 | .flash:hover .close {
62 | opacity: 1; }
63 |
64 | .flash-global {
65 | padding: 10px;
66 | top: -1px;
67 | border-width: 1px 0;
68 | z-index: 5; }
69 | .flash-global h2, .flash-global p {
70 | font-size: 13px;
71 | margin-top: 0;
72 | margin-bottom: 0;
73 | line-height: 1.4; }
74 | .flash-global .flash-action {
75 | margin-top: 5px; }
76 |
77 | .css-truncate.css-truncate-target, .css-truncate .css-truncate-target {
78 | max-width: 125px;
79 | display: inline-block;
80 | overflow: hidden;
81 | text-overflow: ellipsis;
82 | vertical-align: top;
83 | white-space: nowrap; }
84 | .css-truncate.expandable.zeroclipboard-is-hover .css-truncate-target, .css-truncate.expandable.zeroclipboard-is-hover.css-truncate-target, .css-truncate.expandable:hover .css-truncate-target, .css-truncate.expandable:hover.css-truncate-target {
85 | max-width: 10000px !important; }
86 |
87 | .button,
88 | .minibutton {
89 | position: relative;
90 | display: inline-block;
91 | padding: 7px 12px;
92 | font-size: 13px;
93 | font-weight: bold;
94 | color: #333;
95 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9);
96 | white-space: nowrap;
97 | background-color: #eeeeee;
98 | background-image: -moz-linear-gradient(#fcfcfc, #eeeeee);
99 | background-image: -webkit-linear-gradient(#fcfcfc, #eeeeee);
100 | background-image: linear-gradient(#fcfcfc, #eeeeee);
101 | background-repeat: repeat-x;
102 | border-radius: 3px;
103 | border: 1px solid #d5d5d5;
104 | vertical-align: middle;
105 | cursor: pointer;
106 | -webkit-touch-callout: none;
107 | -webkit-user-select: none;
108 | -khtml-user-select: none;
109 | -moz-user-select: none;
110 | -ms-user-select: none;
111 | user-select: none;
112 | -webkit-appearance: none; }
113 | .button:focus,
114 | .minibutton:focus {
115 | outline: none;
116 | text-decoration: none;
117 | border-color: #51a7e8;
118 | box-shadow: 0 0 5px rgba(81, 167, 232, 0.5); }
119 | .button:hover, .button:active, .button.zeroclipboard-is-hover, .button.zeroclipboard-is-active,
120 | .minibutton:hover,
121 | .minibutton:active,
122 | .minibutton.zeroclipboard-is-hover,
123 | .minibutton.zeroclipboard-is-active {
124 | text-decoration: none;
125 | background-color: #dddddd;
126 | background-image: -moz-linear-gradient(#eeeeee, #dddddd);
127 | background-image: -webkit-linear-gradient(#eeeeee, #dddddd);
128 | background-image: linear-gradient(#eeeeee, #dddddd);
129 | background-repeat: repeat-x;
130 | border-color: #ccc; }
131 | .button:active, .button.selected, .button.selected:hover, .button.zeroclipboard-is-active,
132 | .minibutton:active,
133 | .minibutton.selected,
134 | .minibutton.selected:hover,
135 | .minibutton.zeroclipboard-is-active {
136 | background-color: #dcdcdc;
137 | background-image: none;
138 | border-color: #b5b5b5;
139 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15); }
140 | .button:disabled, .button:disabled:hover, .button.disabled, .button.disabled:hover,
141 | .minibutton:disabled,
142 | .minibutton:disabled:hover,
143 | .minibutton.disabled,
144 | .minibutton.disabled:hover {
145 | opacity: .5;
146 | color: #666;
147 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9);
148 | background-image: none;
149 | background-color: #e5e5e5;
150 | border-color: #c5c5c5;
151 | cursor: default;
152 | box-shadow: none; }
153 | .button.primary,
154 | .minibutton.primary {
155 | color: #fff;
156 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
157 | background-color: #60b044;
158 | background-image: -moz-linear-gradient(#8add6d, #60b044);
159 | background-image: -webkit-linear-gradient(#8add6d, #60b044);
160 | background-image: linear-gradient(#8add6d, #60b044);
161 | background-repeat: repeat-x;
162 | border-color: #5ca941; }
163 | .button.primary:hover,
164 | .minibutton.primary:hover {
165 | color: #fff;
166 | background-color: #569e3d;
167 | background-image: -moz-linear-gradient(#79d858, #569e3d);
168 | background-image: -webkit-linear-gradient(#79d858, #569e3d);
169 | background-image: linear-gradient(#79d858, #569e3d);
170 | background-repeat: repeat-x;
171 | border-color: #4a993e; }
172 | .button.primary:active, .button.primary.selected,
173 | .minibutton.primary:active,
174 | .minibutton.primary.selected {
175 | background-color: #569e3d;
176 | background-image: none;
177 | border-color: #418737; }
178 | .button.primary:disabled, .button.primary:disabled:hover, .button.primary.disabled, .button.primary.disabled:hover,
179 | .minibutton.primary:disabled,
180 | .minibutton.primary:disabled:hover,
181 | .minibutton.primary.disabled,
182 | .minibutton.primary.disabled:hover {
183 | color: #fff;
184 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
185 | background-color: #60b044;
186 | background-image: -moz-linear-gradient(#8add6d, #60b044);
187 | background-image: -webkit-linear-gradient(#8add6d, #60b044);
188 | background-image: linear-gradient(#8add6d, #60b044);
189 | background-repeat: repeat-x;
190 | border-color: #74bb5a #74bb5a #509338; }
191 | .button.danger,
192 | .minibutton.danger {
193 | color: #900; }
194 | .button.danger:hover,
195 | .minibutton.danger:hover {
196 | color: #fff;
197 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.3);
198 | background-color: #b33630;
199 | background-image: -moz-linear-gradient(#dc5f59, #b33630);
200 | background-image: -webkit-linear-gradient(#dc5f59, #b33630);
201 | background-image: linear-gradient(#dc5f59, #b33630);
202 | background-repeat: repeat-x;
203 | border-color: #cd504a; }
204 | .button.danger:active, .button.danger.selected,
205 | .minibutton.danger:active,
206 | .minibutton.danger.selected {
207 | color: #fff;
208 | background-color: #b33630;
209 | background-image: none;
210 | border-color: #9f312c; }
211 | .button.danger:disabled, .button.danger:disabled:hover, .button.danger.disabled, .button.danger.disabled:hover,
212 | .minibutton.danger:disabled,
213 | .minibutton.danger:disabled:hover,
214 | .minibutton.danger.disabled,
215 | .minibutton.danger.disabled:hover {
216 | color: #900;
217 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.9);
218 | background-color: #e1e1e1;
219 | background-image: -moz-linear-gradient(white, #e1e1e1);
220 | background-image: -webkit-linear-gradient(white, #e1e1e1);
221 | background-image: linear-gradient(white, #e1e1e1);
222 | background-repeat: repeat-x;
223 | border-color: #c5c5c5; }
224 | .button.blue, .button.blue:hover,
225 | .minibutton.blue,
226 | .minibutton.blue:hover {
227 | color: #fff;
228 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
229 | background-color: #3072b3;
230 | background-image: -moz-linear-gradient(#599bcd, #3072b3);
231 | background-image: -webkit-linear-gradient(#599bcd, #3072b3);
232 | background-image: linear-gradient(#599bcd, #3072b3);
233 | background-repeat: repeat-x;
234 | border-color: #2a65a0; }
235 | .button.blue:hover, .button.blue:active,
236 | .minibutton.blue:hover,
237 | .minibutton.blue:active {
238 | border-color: #2a65a0; }
239 | .button.blue:active, .button.blue.selected,
240 | .minibutton.blue:active,
241 | .minibutton.blue.selected {
242 | background-color: #3072b3;
243 | background-image: none;
244 | border-color: #25588c;
245 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.15); }
246 | .button.blue:disabled, .button.blue.disabled,
247 | .minibutton.blue:disabled,
248 | .minibutton.blue.disabled {
249 | background-position: 0 0; }
250 | .button.dark-grey, .button.dark-grey:hover,
251 | .minibutton.dark-grey,
252 | .minibutton.dark-grey:hover {
253 | color: #fff;
254 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
255 | background-color: #6d6d6d;
256 | background-image: -moz-linear-gradient(#8c8c8c, #6d6d6d);
257 | background-image: -webkit-linear-gradient(#8c8c8c, #6d6d6d);
258 | background-image: linear-gradient(#8c8c8c, #6d6d6d);
259 | background-repeat: repeat-x;
260 | border: 1px solid #707070;
261 | border-bottom-color: #595959; }
262 | .button.dark-grey:hover, .button.dark-grey:active, .button.dark-grey.selected,
263 | .minibutton.dark-grey:hover,
264 | .minibutton.dark-grey:active,
265 | .minibutton.dark-grey.selected {
266 | border-color: #585858; }
267 | .button.dark-grey:active, .button.dark-grey.selected,
268 | .minibutton.dark-grey:active,
269 | .minibutton.dark-grey.selected {
270 | background-color: #545454;
271 | background-image: none;
272 | border-color: #474747;
273 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.15); }
274 | .button.with-count,
275 | .minibutton.with-count {
276 | border-top-right-radius: 0;
277 | border-bottom-right-radius: 0;
278 | float: left; }
279 |
280 | .button img {
281 | position: relative;
282 | top: -1px;
283 | margin-right: 3px;
284 | vertical-align: middle; }
285 |
286 | .button > .octicon {
287 | vertical-align: middle;
288 | margin-top: -1px; }
289 |
290 | .minibutton {
291 | padding: 0 10px;
292 | line-height: 24px;
293 | box-shadow: none; }
294 | .minibutton:hover .octicon-device-desktop:before {
295 | background-position: -18px 0; }
296 | .minibutton i {
297 | font-weight: 500;
298 | font-style: normal;
299 | opacity: .6; }
300 | .minibutton code {
301 | line-height: 22px; }
302 |
303 | .button-block {
304 | display: block;
305 | width: 100%;
306 | text-align: center;
307 | -moz-box-sizing: border-box;
308 | box-sizing: border-box; }
309 |
310 | .button-link {
311 | display: inline;
312 | padding: 0;
313 | font-size: inherit;
314 | color: #4183c4;
315 | white-space: nowrap;
316 | background-color: transparent;
317 | border: 0;
318 | cursor: pointer;
319 | -webkit-touch-callout: none;
320 | -webkit-user-select: none;
321 | -khtml-user-select: none;
322 | -moz-user-select: none;
323 | -ms-user-select: none;
324 | user-select: none;
325 | -webkit-appearance: none; }
326 | .button-link:hover {
327 | text-decoration: underline; }
328 |
329 | input[type=text] + .minibutton {
330 | margin-left: 5px; }
331 |
332 | .minibutton .octicon {
333 | vertical-align: middle;
334 | margin-top: -1px;
335 | margin-right: 6px;
336 | -moz-transition: none;
337 | -webkit-transition: none;
338 | -o-transition: color 0 ease-in;
339 | transition: none; }
340 | .minibutton.zeroclipboard-button .octicon {
341 | margin-right: 0; }
342 | .minibutton.empty-icon .octicon {
343 | margin-right: 0; }
344 | .minibutton .octicon-arrow-right {
345 | float: right;
346 | margin-right: 0;
347 | margin-left: 5px;
348 | margin-top: 4px; }
349 |
350 | .hidden-text-expander {
351 | display: block; }
352 | .hidden-text-expander.inline {
353 | display: inline-block;
354 | line-height: 0;
355 | margin-left: 5px;
356 | position: relative;
357 | top: -1px; }
358 | .hidden-text-expander a {
359 | background: #ddd;
360 | color: #555;
361 | padding: 0 5px;
362 | line-height: 6px;
363 | height: 12px;
364 | font-size: 12px;
365 | font-weight: bold;
366 | vertical-align: middle;
367 | display: inline-block;
368 | border-radius: 1px;
369 | text-decoration: none; }
370 | .hidden-text-expander a:hover {
371 | background-color: #ccc;
372 | text-decoration: none; }
373 | .hidden-text-expander a:active {
374 | background-color: #4183C4;
375 | color: #fff; }
376 |
377 | .social-count {
378 | padding: 0 7px 0;
379 | font-size: 11px;
380 | font-weight: bold;
381 | float: left;
382 | background-color: #fff;
383 | line-height: 24px;
384 | color: #333333;
385 | vertical-align: middle;
386 | border: 1px solid #ddd;
387 | border-left: 0;
388 | border-top-right-radius: 3px;
389 | border-bottom-right-radius: 3px; }
390 | .social-count:hover {
391 | color: #4183c4;
392 | cursor: pointer;
393 | text-decoration: none; }
394 |
395 | .button-group {
396 | display: inline-block;
397 | vertical-align: middle; }
398 | .button-group:before, .button-group:after {
399 | content: " ";
400 | display: table; }
401 | .button-group:after {
402 | clear: both; }
403 | .button-group .button,
404 | .button-group .minibutton {
405 | position: relative;
406 | float: left;
407 | border-radius: 0; }
408 | .button-group .button:first-child,
409 | .button-group .minibutton:first-child {
410 | border-top-left-radius: 3px;
411 | border-bottom-left-radius: 3px; }
412 | .button-group .button:last-child,
413 | .button-group .minibutton:last-child {
414 | border-top-right-radius: 3px;
415 | border-bottom-right-radius: 3px; }
416 | .button-group .button:hover, .button-group .button:focus, .button-group .button:active, .button-group .button.selected,
417 | .button-group .minibutton:hover,
418 | .button-group .minibutton:focus,
419 | .button-group .minibutton:active,
420 | .button-group .minibutton.selected {
421 | z-index: 2; }
422 | .button-group .button + .button,
423 | .button-group .minibutton + .minibutton {
424 | margin-left: -1px;
425 | box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.2); }
426 | .button-group .button + .button:hover,
427 | .button-group .minibutton + .minibutton:hover {
428 | box-shadow: none; }
429 | .button-group .button + .button:active, .button-group .button + .button.selected,
430 | .button-group .minibutton + .minibutton:active,
431 | .button-group .minibutton + .minibutton.selected {
432 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.15); }
433 |
434 | .button-group + .button-group,
435 | .button-group + .button,
436 | .button-group + .minibutton {
437 | margin-left: 5px; }
438 |
439 | .markdown-body {
440 | font-size: 15px;
441 | line-height: 1.7;
442 | overflow: hidden;
443 | word-wrap: break-word; }
444 | .markdown-body > *:first-child {
445 | margin-top: 0 !important; }
446 | .markdown-body > *:last-child {
447 | margin-bottom: 0 !important; }
448 | .markdown-body a.absent {
449 | color: #c00; }
450 | .markdown-body a.anchor {
451 | display: block;
452 | padding-right: 6px;
453 | padding-left: 30px;
454 | margin-left: -30px;
455 | cursor: pointer;
456 | position: absolute;
457 | top: 0;
458 | left: 0;
459 | bottom: 0; }
460 | .markdown-body a.anchor:focus {
461 | outline: none; }
462 | .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
463 | margin: 1em 0 15px;
464 | padding: 0;
465 | font-weight: bold;
466 | line-height: 1.7;
467 | cursor: text;
468 | position: relative; }
469 | .markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link {
470 | display: none;
471 | color: #000; }
472 | .markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor {
473 | text-decoration: none;
474 | line-height: 1;
475 | padding-left: 8px;
476 | margin-left: -30px;
477 | top: 15%; }
478 | .markdown-body h1:hover a.anchor .octicon-link, .markdown-body h2:hover a.anchor .octicon-link, .markdown-body h3:hover a.anchor .octicon-link, .markdown-body h4:hover a.anchor .octicon-link, .markdown-body h5:hover a.anchor .octicon-link, .markdown-body h6:hover a.anchor .octicon-link {
479 | display: inline-block; }
480 | .markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code {
481 | font-size: inherit; }
482 | .markdown-body h1 {
483 | font-size: 2.5em;
484 | border-bottom: 1px solid #ddd; }
485 | .markdown-body h2 {
486 | font-size: 2em;
487 | border-bottom: 1px solid #eee; }
488 | .markdown-body h3 {
489 | font-size: 1.5em; }
490 | .markdown-body h4 {
491 | font-size: 1.2em; }
492 | .markdown-body h5 {
493 | font-size: 1em; }
494 | .markdown-body h6 {
495 | color: #777;
496 | font-size: 1em; }
497 | .markdown-body p,
498 | .markdown-body blockquote,
499 | .markdown-body ul, .markdown-body ol, .markdown-body dl,
500 | .markdown-body table,
501 | .markdown-body pre {
502 | margin: 15px 0; }
503 | .markdown-body hr {
504 | background: transparent image-url("primer/markdown/dirty-shade.png") repeat-x 0 0;
505 | border: 0 none;
506 | color: #ccc;
507 | height: 4px;
508 | padding: 0;
509 | margin: 15px 0; }
510 | .markdown-body ul, .markdown-body ol {
511 | padding-left: 30px; }
512 | .markdown-body ul.no-list, .markdown-body ol.no-list {
513 | list-style-type: none;
514 | padding: 0; }
515 | .markdown-body ul ul,
516 | .markdown-body ul ol,
517 | .markdown-body ol ol,
518 | .markdown-body ol ul {
519 | margin-top: 0;
520 | margin-bottom: 0; }
521 | .markdown-body dl {
522 | padding: 0; }
523 | .markdown-body dl dt {
524 | font-size: 14px;
525 | font-weight: bold;
526 | font-style: italic;
527 | padding: 0;
528 | margin-top: 15px; }
529 | .markdown-body dl dd {
530 | margin-bottom: 15px;
531 | padding: 0 15px; }
532 | .markdown-body blockquote {
533 | border-left: 4px solid #DDD;
534 | padding: 0 15px;
535 | color: #777; }
536 | .markdown-body blockquote > :first-child {
537 | margin-top: 0px; }
538 | .markdown-body blockquote > :last-child {
539 | margin-bottom: 0px; }
540 | .markdown-body table {
541 | width: 100%;
542 | overflow: auto;
543 | display: block; }
544 | .markdown-body table th {
545 | font-weight: bold; }
546 | .markdown-body table th, .markdown-body table td {
547 | border: 1px solid #ddd;
548 | padding: 6px 13px; }
549 | .markdown-body table tr {
550 | border-top: 1px solid #ccc;
551 | background-color: #fff; }
552 | .markdown-body table tr:nth-child(2n) {
553 | background-color: #f8f8f8; }
554 | .markdown-body img {
555 | max-width: 100%;
556 | -moz-box-sizing: border-box;
557 | box-sizing: border-box; }
558 | .markdown-body span.frame {
559 | display: block;
560 | overflow: hidden; }
561 | .markdown-body span.frame > span {
562 | border: 1px solid #ddd;
563 | display: block;
564 | float: left;
565 | overflow: hidden;
566 | margin: 13px 0 0;
567 | padding: 7px;
568 | width: auto; }
569 | .markdown-body span.frame span img {
570 | display: block;
571 | float: left; }
572 | .markdown-body span.frame span span {
573 | clear: both;
574 | color: #333;
575 | display: block;
576 | padding: 5px 0 0; }
577 | .markdown-body span.align-center {
578 | display: block;
579 | overflow: hidden;
580 | clear: both; }
581 | .markdown-body span.align-center > span {
582 | display: block;
583 | overflow: hidden;
584 | margin: 13px auto 0;
585 | text-align: center; }
586 | .markdown-body span.align-center span img {
587 | margin: 0 auto;
588 | text-align: center; }
589 | .markdown-body span.align-right {
590 | display: block;
591 | overflow: hidden;
592 | clear: both; }
593 | .markdown-body span.align-right > span {
594 | display: block;
595 | overflow: hidden;
596 | margin: 13px 0 0;
597 | text-align: right; }
598 | .markdown-body span.align-right span img {
599 | margin: 0;
600 | text-align: right; }
601 | .markdown-body span.float-left {
602 | display: block;
603 | margin-right: 13px;
604 | overflow: hidden;
605 | float: left; }
606 | .markdown-body span.float-left span {
607 | margin: 13px 0 0; }
608 | .markdown-body span.float-right {
609 | display: block;
610 | margin-left: 13px;
611 | overflow: hidden;
612 | float: right; }
613 | .markdown-body span.float-right > span {
614 | display: block;
615 | overflow: hidden;
616 | margin: 13px auto 0;
617 | text-align: right; }
618 | .markdown-body code, .markdown-body tt {
619 | margin: 0 2px;
620 | padding: 0px 5px;
621 | border: 1px solid #ddd;
622 | background-color: #f8f8f8;
623 | border-radius: 3px; }
624 | .markdown-body code {
625 | white-space: nowrap; }
626 | .markdown-body pre > code {
627 | margin: 0;
628 | padding: 0;
629 | white-space: pre;
630 | border: none;
631 | background: transparent; }
632 | .markdown-body .highlight pre, .markdown-body pre {
633 | background-color: #f8f8f8;
634 | border: 1px solid #ddd;
635 | font-size: 13px;
636 | line-height: 19px;
637 | overflow: auto;
638 | padding: 6px 10px;
639 | border-radius: 3px; }
640 | .markdown-body pre {
641 | word-wrap: normal; }
642 | .markdown-body pre code, .markdown-body pre tt {
643 | margin: 0;
644 | padding: 0;
645 | background-color: transparent;
646 | border: none;
647 | word-wrap: normal; }
648 |
649 | .menu-container {
650 | float: left;
651 | width: 200px;
652 | padding: 3px;
653 | background: #efefef;
654 | border-radius: 2px; }
655 |
656 | .menu {
657 | background: #fafafb;
658 | border-radius: 2px;
659 | border: 1px solid #d8d8d8;
660 | list-style: none; }
661 | .menu a:hover {
662 | text-decoration: none; }
663 | .menu li {
664 | border-bottom: 1px solid #eee;
665 | border-top: 1px solid #fff; }
666 | .menu li:last-child {
667 | border-bottom: none; }
668 | .menu li:first-child {
669 | border-top: none; }
670 | .menu a {
671 | display: block;
672 | padding: 8px 10px 8px 8px;
673 | text-shadow: 0 1px 0 #fff;
674 | border-left: 2px solid #fafafb; }
675 | .menu a:hover {
676 | background: #fdfdfe; }
677 | .menu a .octicon {
678 | color: #333333;
679 | width: 16px;
680 | text-align: center; }
681 | .menu a.selected {
682 | background: #fff;
683 | border-left: 2px solid #d26911;
684 | font-weight: bold;
685 | color: #222;
686 | cursor: default;
687 | box-shadow: inset 0 0px 1px rgba(0, 0, 0, 0.1); }
688 | .menu a .counter {
689 | float: right;
690 | margin: 0 0 0 5px;
691 | padding: 2px 5px;
692 | font-size: 11px;
693 | font-weight: bold;
694 | color: #999;
695 | background: #eee;
696 | border-radius: 2px; }
697 | .menu .menu-warning {
698 | color: #D26911;
699 | float: right; }
700 |
701 | .accordion {
702 | background: #fafafb;
703 | list-style: none; }
704 | .accordion .section {
705 | border-top: 1px solid #d8d8d8;
706 | border-top: none; }
707 | .accordion .section:first-child {
708 | border-top: none; }
709 | .accordion .section a.section-head {
710 | background-color: #e0e0e0;
711 | background-image: -moz-linear-gradient(#fafafa, #e0e0e0);
712 | background-image: -webkit-linear-gradient(#fafafa, #e0e0e0);
713 | background-image: linear-gradient(#fafafa, #e0e0e0);
714 | background-repeat: repeat-x;
715 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
716 | display: block;
717 | padding: 10px 10px;
718 | border-bottom: 1px solid #ccc;
719 | color: #222;
720 | font-weight: bold;
721 | font-size: 14px;
722 | line-height: 20px;
723 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
724 | border-left: 0 none; }
725 | .accordion .section a.section-head img {
726 | float: left;
727 | margin: 0 10px 0 0;
728 | border-radius: 2px; }
729 | .accordion .section .section-nav {
730 | list-style: none;
731 | display: none; }
732 | .accordion .section .section-nav.expanded {
733 | display: block; }
734 |
735 | .highlight {
736 | background: #ffffff; }
737 | .highlight .c {
738 | color: #999988;
739 | font-style: italic; }
740 | .highlight .err {
741 | color: #a61717;
742 | background-color: #e3d2d2; }
743 | .highlight .k {
744 | font-weight: bold; }
745 | .highlight .o {
746 | font-weight: bold; }
747 | .highlight .cm {
748 | color: #999988;
749 | font-style: italic; }
750 | .highlight .cp {
751 | color: #999999;
752 | font-weight: bold; }
753 | .highlight .c1 {
754 | color: #999988;
755 | font-style: italic; }
756 | .highlight .cs {
757 | color: #999999;
758 | font-weight: bold;
759 | font-style: italic; }
760 | .highlight .gd {
761 | color: #000000;
762 | background-color: #ffdddd; }
763 | .highlight .gd .x {
764 | color: #000000;
765 | background-color: #ffaaaa; }
766 | .highlight .ge {
767 | font-style: italic; }
768 | .highlight .gr {
769 | color: #aa0000; }
770 | .highlight .gh {
771 | color: #999999; }
772 | .highlight .gi {
773 | color: #000000;
774 | background-color: #ddffdd; }
775 | .highlight .gi .x {
776 | color: #000000;
777 | background-color: #aaffaa; }
778 | .highlight .go {
779 | color: #888888; }
780 | .highlight .gp {
781 | color: #555555; }
782 | .highlight .gs {
783 | font-weight: bold; }
784 | .highlight .gu {
785 | color: #800080;
786 | font-weight: bold; }
787 | .highlight .gt {
788 | color: #aa0000; }
789 | .highlight .kc {
790 | font-weight: bold; }
791 | .highlight .kd {
792 | font-weight: bold; }
793 | .highlight .kn {
794 | font-weight: bold; }
795 | .highlight .kp {
796 | font-weight: bold; }
797 | .highlight .kr {
798 | font-weight: bold; }
799 | .highlight .kt {
800 | color: #445588;
801 | font-weight: bold; }
802 | .highlight .m {
803 | color: #009999; }
804 | .highlight .s {
805 | color: #dd1144; }
806 | .highlight .n {
807 | color: #333333; }
808 | .highlight .na {
809 | color: teal; }
810 | .highlight .nb {
811 | color: #0086b3; }
812 | .highlight .nc {
813 | color: #445588;
814 | font-weight: bold; }
815 | .highlight .no {
816 | color: teal; }
817 | .highlight .ni {
818 | color: purple; }
819 | .highlight .ne {
820 | color: #990000;
821 | font-weight: bold; }
822 | .highlight .nf {
823 | color: #990000;
824 | font-weight: bold; }
825 | .highlight .nn {
826 | color: #555555; }
827 | .highlight .nt {
828 | color: navy; }
829 | .highlight .nv {
830 | color: teal; }
831 | .highlight .ow {
832 | font-weight: bold; }
833 | .highlight .w {
834 | color: #bbbbbb; }
835 | .highlight .mf {
836 | color: #009999; }
837 | .highlight .mh {
838 | color: #009999; }
839 | .highlight .mi {
840 | color: #009999; }
841 | .highlight .mo {
842 | color: #009999; }
843 | .highlight .sb {
844 | color: #dd1144; }
845 | .highlight .sc {
846 | color: #dd1144; }
847 | .highlight .sd {
848 | color: #dd1144; }
849 | .highlight .s2 {
850 | color: #dd1144; }
851 | .highlight .se {
852 | color: #dd1144; }
853 | .highlight .sh {
854 | color: #dd1144; }
855 | .highlight .si {
856 | color: #dd1144; }
857 | .highlight .sx {
858 | color: #dd1144; }
859 | .highlight .sr {
860 | color: #009926; }
861 | .highlight .s1 {
862 | color: #dd1144; }
863 | .highlight .ss {
864 | color: #990073; }
865 | .highlight .bp {
866 | color: #999999; }
867 | .highlight .vc {
868 | color: teal; }
869 | .highlight .vg {
870 | color: teal; }
871 | .highlight .vi {
872 | color: teal; }
873 | .highlight .il {
874 | color: #009999; }
875 | .highlight .gc {
876 | color: #999;
877 | background-color: #EAF2F5; }
878 |
879 | .type-csharp .highlight .k {
880 | color: blue; }
881 | .type-csharp .highlight .kt {
882 | color: blue; }
883 | .type-csharp .highlight .nf {
884 | color: #000000;
885 | font-weight: normal; }
886 | .type-csharp .highlight .nc {
887 | color: #2b91af; }
888 | .type-csharp .highlight .nn {
889 | color: black; }
890 | .type-csharp .highlight .s {
891 | color: #a31515; }
892 | .type-csharp .highlight .sc {
893 | color: #a31515; }
894 |
--------------------------------------------------------------------------------
/vendor/img/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bkeepers/github-notifications/6055b9ff198214802f7500a4862bcbf4ac86b59a/vendor/img/.gitkeep
--------------------------------------------------------------------------------