├── .gitignore ├── README.md ├── app ├── css │ ├── .gitkeep │ └── app.css ├── img │ └── .gitkeep ├── index-async.html ├── index.html ├── js │ ├── app.js │ ├── controllers.js │ ├── directives.js │ ├── filters.js │ └── services.js ├── lib │ ├── angular │ │ ├── angular-cookies.js │ │ ├── angular-cookies.min.js │ │ ├── angular-loader.js │ │ ├── angular-loader.min.js │ │ ├── angular-resource.js │ │ ├── angular-resource.min.js │ │ ├── angular-sanitize.js │ │ ├── angular-sanitize.min.js │ │ ├── angular.js │ │ ├── angular.min.js │ │ └── version.txt │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ └── vendor │ │ ├── jquery-1.10.2.min.js │ │ └── jquery-1.10.2.min.map └── partials │ ├── .gitkeep │ ├── partial1.html │ ├── partial2.html │ └── stars.html ├── config ├── karma-e2e.conf.js └── karma.conf.js ├── feature.md ├── ground ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── activities │ ├── accu_q_star_assoc.rb │ ├── build_inverted_star_index.rb │ ├── insert_star_from_github.rb │ ├── query_stars_by_word.rb │ └── search_stars.rb ├── config.ru ├── config │ ├── database.yml │ ├── github.example.yml │ ├── routes.rb │ ├── schedule.rb │ └── sets.rb ├── db │ └── migrations │ │ ├── 001_create_stars.rb │ │ ├── 002_create_words.rb │ │ ├── 003_create_word_stars.rb │ │ ├── 004_create_q_star_assos.rb │ │ ├── 005_create_reindex_stars.rb │ │ └── 006_add_index.rb ├── gstar.rb ├── lib │ ├── github_api.rb │ └── github_api │ │ └── list_starred_repos.rb ├── logs │ └── keep.txt ├── states │ ├── home.rb │ ├── star │ │ └── save_description.rb │ └── stars │ │ └── index.rb └── tmp │ └── .gitkeep ├── images ├── Snip20131003_1.png ├── Snip20131003_2.png ├── Snip20131004_3.png └── Snip20131004_5.png ├── package.json ├── scripts ├── e2e-test.bat ├── e2e-test.sh ├── test.bat ├── test.sh ├── watchr.rb └── web-server.js └── test ├── e2e ├── runner.html └── scenarios.js ├── lib └── angular │ ├── angular-mocks.js │ ├── angular-scenario.js │ └── version.txt └── unit ├── controllersSpec.js ├── directivesSpec.js ├── filtersSpec.js └── servicesSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | *.sqlite3 29 | *.db 30 | 31 | # OS generated files # 32 | ###################### 33 | .DS_Store 34 | .DS_Store? 35 | ._* 36 | .Spotlight-V100 37 | .Trashes 38 | ehthumbs.db 39 | Thumbs.db 40 | 41 | github.yml 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gstar 2 | gstar, 帮助我们搜索在github上star过的项目 3 | 4 | ## 安装 5 | 6 | 1. git clone https://github.com/baya/Gstar.git 7 | 8 | 2. cd Gstar/ground 9 | 10 | 3. bundle install 11 | 12 | 4. rake db:migrate 13 | 14 | 5. cp config/github.example.yml config/github.yml 15 | 16 | 在config/github.yml里有三个配置,其中login为必填项,access_token和password选一个填写即可,login和password是你用来登录github的用户名和密码,access_token可以通过下面的步骤得到, 17 | - 点击Account Settings,进入帐号设置页面 18 | - 选择左侧栏的Applications导航,可以看到Personal Access Tokens这个设置,点击右上角的Create new token生成新的access_token即可。 19 | ![](/images/Snip20131004_5.png) 20 | 配置完以后,运行下面的rake任务, 21 | 22 | 6. rake maintain:pull_stars_from_github #将你star过的项目从github拉到本地数据库 23 | 24 | 7. whenever --update # 启动定时任务,每分钟检查一次你是否有新的star项目 25 | 26 | 8. rackup -p 9292 27 | 28 | 9. 使用你喜爱的浏览器访问 http://localhost:9292 29 | 30 | 注意操作3到8都是在Gstar/ground目录下进行的。 31 | 32 | ## 功能特点 33 | 34 | ### 1.准确快速的搜索 35 | 36 | github自带的搜星功能不是特别好用,比如说我想找出我收藏的(github的标星功能对我来说就是收藏)与markdown有关的项目,使用github自带的搜星功能只能找到一个叫miclle/Markdown-Editor的项目,因为github是通过项目的名字进行搜索,这导致很多与markdwon有关的项目被忽略了,但是使用Gstar搜索markdwon能找到11个与markdown有关的项目,因为Gstar对项目名和项目描述都进行搜索,这样比github更准确些。 37 | 38 | github的搜星: 39 | 40 | ![](/images/Snip20131003_1.png) 41 | 42 | gstar的搜星: 43 | ![](/images/Snip20131003_2.png) 44 | 45 | ### 2.可以修改项目的描述(description),实现类似于打标签的功能 46 | 47 | 比如说我给karmi/tire这个项目的描述加上"搜索",那么我就可以通过搜索"搜索"找到tire这个项目了。 48 | 49 | ![](/images/Snip20131004_3.png) 50 | 51 | ### 3.自动更新starred projects 52 | Gstar自带一个定时任务,每一分钟会去github那检查是否有新的stars,如果有就会拉到本地,建立索引,方便你以后搜索使用。 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/app/css/.gitkeep -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | font-size:13px; 34 | line-height:1.231; 35 | } 36 | ol, ul { 37 | list-style: none; 38 | } 39 | blockquote, q { 40 | quotes: none; 41 | } 42 | blockquote:before, blockquote:after, 43 | q:before, q:after { 44 | content: ''; 45 | content: none; 46 | } 47 | table { 48 | border-collapse: collapse; 49 | border-spacing: 0; 50 | } 51 | 52 | /* app css stylesheet */ 53 | body{ 54 | font-family: Arial, sans-serif; 55 | } 56 | a:link, a:visited{ 57 | color:#333; 58 | } 59 | a{ 60 | text-decoration:none; 61 | } 62 | .header{ 63 | padding-top:35px; 64 | margin-left:55px; 65 | } 66 | 67 | .logo{ 68 | font-size:25px; 69 | float:left; 70 | margin-right:5px; 71 | text-align:center; 72 | padding: 0 3px; 73 | } 74 | 75 | .search-bar{ 76 | float:left; 77 | } 78 | 79 | .btn{ 80 | display:inline-block; 81 | padding:6px 12px; 82 | margin-bottom:0; 83 | font-weight:normal; 84 | text-align:center; 85 | vertical-align:center; 86 | cursor:pointer; 87 | border:1px solid transparent; 88 | } 89 | 90 | .btn:hover, .btn:focus{ 91 | color: #333333; 92 | text-decoration:none; 93 | } 94 | 95 | .btn-default:hover{ 96 | color: #333333; 97 | background-color: #ebebeb; 98 | border-color:#adadad; 99 | } 100 | 101 | .btn-default{ 102 | background-color: #ffffff; 103 | border-color:#cccccc; 104 | height: 34px; 105 | margin-bottom: 3px; 106 | } 107 | 108 | .clear{clear:both;} 109 | 110 | .content{ 111 | margin-top: 20px; 112 | margin-left: 20px; 113 | } 114 | 115 | ol { 116 | list-style:none; 117 | counter-reset:li; 118 | } 119 | 120 | 121 | #main-input{ 122 | width:560px; 123 | height:32px; 124 | font:18px arial,sans-serif; 125 | } 126 | 127 | ol#stars{ 128 | padding: 18px 0 0; 129 | list-style-type:none; 130 | } 131 | 132 | ol#stars li{ 133 | margin-bottom:25px; 134 | } 135 | 136 | ol#stars article{ 137 | z-index:0; 138 | padding-top: 3px; 139 | width:100%; 140 | } 141 | 142 | ol#stars h1{ 143 | font-size:19px; 144 | line-height:1em; 145 | display:inline; 146 | } 147 | 148 | ol#stars header{ 149 | margin-left: 77px; 150 | } 151 | 152 | ol#stars p, .edit-in-place textarea{ 153 | margin-left: 77px; 154 | width:31.5%; 155 | font-size: 13px; 156 | padding-top: 2px; 157 | } 158 | 159 | ol#stars a{ 160 | text-decoration:underline; 161 | color:#1b00a6; 162 | } 163 | 164 | .edit-in-place p { 165 | cursor:default; 166 | } 167 | 168 | .edit-in-place textarea{ 169 | display: none; 170 | } 171 | 172 | .edit-in-place.active p { 173 | display: none; 174 | } 175 | 176 | .edit-in-place.active textarea { 177 | display: inline-block; 178 | } 179 | 180 | em{ 181 | color: #dd4b39; 182 | } 183 | 184 | .pagination{ 185 | margin-left: 76px; 186 | margin-top: 5px; 187 | margin-bottom: 20; 188 | } 189 | 190 | -------------------------------------------------------------------------------- /app/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/app/img/.gitkeep -------------------------------------------------------------------------------- /app/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 44 | My AngularJS App 45 | 46 | 47 | 48 | 52 | 53 |
54 | 55 |
Angular seed app: v
56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gstar 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 19 |
20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | // Declare app level module which depends on filters, and services 5 | angular.module('gStar', ['ngSanitize', 'gStar.filters', 'gStar.services', 'gStar.directives', 'gStar.controllers']). 6 | config(['$routeProvider', function($routeProvider) { 7 | $routeProvider.when('/stars', {templateUrl: 'partials/stars.html', controller: 'StarsCtrl'}); 8 | }]); 9 | -------------------------------------------------------------------------------- /app/js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | angular.module('gStar.controllers', []). 6 | controller('SearchFormCtrl', ['$scope', '$location', function($scope, $location){ 7 | $scope.q = $location.search().q; 8 | $scope.submit = function(){ 9 | if(this.q){ 10 | $location.path('stars').search({q: this.q}); 11 | } 12 | }; 13 | }]). 14 | controller('StarsCtrl', ['$scope', '$routeParams', 'SearchStars', function($scope, $routeParams, SearchStars){ 15 | var q = $routeParams.q; 16 | var ws = q.split(" ").join("|"); 17 | var baseHref = '/#/stars?' + 'q=' + q; 18 | var res = SearchStars.get($routeParams, function(){ 19 | $scope.stars = res.stars; 20 | $scope.getPages = function(){ 21 | var pages = []; 22 | var pageCount = res.total / res.limit; 23 | if(res.total % res.limit > 0){ 24 | pageCount = pageCount + 1; 25 | } 26 | pageCount = Math.floor(pageCount); 27 | 28 | 29 | for(var i = 0; i < pageCount; i++){ 30 | var num = i + 1; 31 | var start = i * res.limit; 32 | var current = res.start == start ? 'current' : '' 33 | if(current){ 34 | $scope.currentPage = num; 35 | } 36 | var page = {num: num, href: '#', current: current}; 37 | page.href = baseHref + '&start=' + start; 38 | 39 | pages[i] = page; 40 | } 41 | 42 | return pages; 43 | }; 44 | 45 | $scope.pages = $scope.getPages(); 46 | if($scope.currentPage == 1){ 47 | $scope.prePageHref = 'nonlink'; 48 | } else { 49 | $scope.prePageHref = baseHref + '&start=' + ($scope.currentPage - 2) * res.limit; 50 | } 51 | if($scope.currentPage == $scope.pages.length){ 52 | $scope.nextPageHref = 'nonlink'; 53 | } else { 54 | $scope.nextPageHref = baseHref + '&start=' + $scope.currentPage * res.limit; 55 | } 56 | 57 | if($scope.pages.length > 1){ 58 | $scope.pagination = 'pagination'; 59 | } 60 | 61 | $scope.hide = false; 62 | }); 63 | 64 | $scope.newRegex = function(){ 65 | return new RegExp("("+ ws +")","ig"); 66 | }; 67 | 68 | $scope.regex = $scope.newRegex(); 69 | $scope.hit = '$1'; 70 | $scope.hide = true; 71 | 72 | }]); 73 | -------------------------------------------------------------------------------- /app/js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | angular.module('gStar.directives', []). 6 | directive('appVersion', ['version', function(version) { 7 | return function(scope, elm, attrs) { 8 | elm.text(version); 9 | }; 10 | }]). 11 | directive('editInPlace', ['$compile', 'SaveStarDescription', function($compile, SaveStarDescription){ 12 | return { 13 | restrict: 'E', 14 | scope: {descriptionhtml: '@', starid: '@', description: '@', new_regex: '&newRegex', hit: '@'}, 15 | template: '

'+ 16 | '', 17 | link: function( $scope, element, attrs, ctrl ){ 18 | var inputElement = $( element.children()[1] ); 19 | var textElement = $( element.children()[0] ); 20 | var dataCache = {}; 21 | var regex = $scope.new_regex(); 22 | 23 | element.addClass( 'edit-in-place' ); 24 | 25 | $scope.editing = false; 26 | 27 | inputElement.on('focus', function(){ 28 | var txt = $(this).val(); 29 | $(this).val(''); 30 | $(this).val(txt); 31 | }) 32 | 33 | $scope.edit = function(){ 34 | $scope.editing = true; 35 | element.addClass( 'active' ); 36 | var textHeight = textElement.height(); 37 | var patchHeight = 16; 38 | dataCache.origDescription = textElement.text(); 39 | inputElement.height(textHeight + patchHeight); 40 | 41 | inputElement.focus(); 42 | }; 43 | 44 | inputElement.on( 'blur', function(){ 45 | $scope.editing = false; 46 | $(element).removeClass( 'active' ); 47 | if(dataCache.origDescription != $scope.description){ 48 | var data = {id: $scope.starid, description: $scope.description}; 49 | var nr = new SaveStarDescription(data) 50 | nr.$save(); 51 | $scope.descriptionhtml = nr.description.replace(regex, $scope.hit); 52 | } 53 | }); 54 | } 55 | }; 56 | }]) -------------------------------------------------------------------------------- /app/js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('gStar.filters', []). 6 | filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]); 11 | -------------------------------------------------------------------------------- /app/js/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Services */ 4 | 5 | // In this case it is a simple value service. 6 | angular.module('gStar.services', ['ngResource']). 7 | value('version', '0.1'). 8 | factory('SearchStars', ['$resource', function($resource){ 9 | return $resource('/stars') 10 | }]). 11 | factory('SaveStarDescription', ['$resource', function($resource){ 12 | return $resource('/saveStarDescription') 13 | }]) 14 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngCookies 12 | */ 13 | 14 | 15 | angular.module('ngCookies', ['ng']). 16 | /** 17 | * @ngdoc object 18 | * @name ngCookies.$cookies 19 | * @requires $browser 20 | * 21 | * @description 22 | * Provides read/write access to browser's cookies. 23 | * 24 | * Only a simple Object is exposed and by adding or removing properties to/from 25 | * this object, new cookies are created/deleted at the end of current $eval. 26 | * 27 | * @example 28 | 29 | 30 | 38 | 39 | 40 | */ 41 | factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { 42 | var cookies = {}, 43 | lastCookies = {}, 44 | lastBrowserCookies, 45 | runEval = false, 46 | copy = angular.copy, 47 | isUndefined = angular.isUndefined; 48 | 49 | //creates a poller fn that copies all cookies from the $browser to service & inits the service 50 | $browser.addPollFn(function() { 51 | var currentCookies = $browser.cookies(); 52 | if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl 53 | lastBrowserCookies = currentCookies; 54 | copy(currentCookies, lastCookies); 55 | copy(currentCookies, cookies); 56 | if (runEval) $rootScope.$apply(); 57 | } 58 | })(); 59 | 60 | runEval = true; 61 | 62 | //at the end of each eval, push cookies 63 | //TODO: this should happen before the "delayed" watches fire, because if some cookies are not 64 | // strings or browser refuses to store some cookies, we update the model in the push fn. 65 | $rootScope.$watch(push); 66 | 67 | return cookies; 68 | 69 | 70 | /** 71 | * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. 72 | */ 73 | function push() { 74 | var name, 75 | value, 76 | browserCookies, 77 | updated; 78 | 79 | //delete any cookies deleted in $cookies 80 | for (name in lastCookies) { 81 | if (isUndefined(cookies[name])) { 82 | $browser.cookies(name, undefined); 83 | } 84 | } 85 | 86 | //update all cookies updated in $cookies 87 | for(name in cookies) { 88 | value = cookies[name]; 89 | if (!angular.isString(value)) { 90 | if (angular.isDefined(lastCookies[name])) { 91 | cookies[name] = lastCookies[name]; 92 | } else { 93 | delete cookies[name]; 94 | } 95 | } else if (value !== lastCookies[name]) { 96 | $browser.cookies(name, value); 97 | updated = true; 98 | } 99 | } 100 | 101 | //verify what was actually stored 102 | if (updated){ 103 | updated = false; 104 | browserCookies = $browser.cookies(); 105 | 106 | for (name in cookies) { 107 | if (cookies[name] !== browserCookies[name]) { 108 | //delete or reset all cookies that the browser dropped from $cookies 109 | if (isUndefined(browserCookies[name])) { 110 | delete cookies[name]; 111 | } else { 112 | cookies[name] = browserCookies[name]; 113 | } 114 | updated = true; 115 | } 116 | } 117 | } 118 | } 119 | }]). 120 | 121 | 122 | /** 123 | * @ngdoc object 124 | * @name ngCookies.$cookieStore 125 | * @requires $cookies 126 | * 127 | * @description 128 | * Provides a key-value (string-object) storage, that is backed by session cookies. 129 | * Objects put or retrieved from this storage are automatically serialized or 130 | * deserialized by angular's toJson/fromJson. 131 | * @example 132 | */ 133 | factory('$cookieStore', ['$cookies', function($cookies) { 134 | 135 | return { 136 | /** 137 | * @ngdoc method 138 | * @name ngCookies.$cookieStore#get 139 | * @methodOf ngCookies.$cookieStore 140 | * 141 | * @description 142 | * Returns the value of given cookie key 143 | * 144 | * @param {string} key Id to use for lookup. 145 | * @returns {Object} Deserialized cookie value. 146 | */ 147 | get: function(key) { 148 | var value = $cookies[key]; 149 | return value ? angular.fromJson(value) : value; 150 | }, 151 | 152 | /** 153 | * @ngdoc method 154 | * @name ngCookies.$cookieStore#put 155 | * @methodOf ngCookies.$cookieStore 156 | * 157 | * @description 158 | * Sets a value for given cookie key 159 | * 160 | * @param {string} key Id for the `value`. 161 | * @param {Object} value Value to be stored. 162 | */ 163 | put: function(key, value) { 164 | $cookies[key] = angular.toJson(value); 165 | }, 166 | 167 | /** 168 | * @ngdoc method 169 | * @name ngCookies.$cookieStore#remove 170 | * @methodOf ngCookies.$cookieStore 171 | * 172 | * @description 173 | * Remove given cookie 174 | * 175 | * @param {string} key Id of the key-value pair to delete. 176 | */ 177 | remove: function(key) { 178 | delete $cookies[key]; 179 | } 180 | }; 181 | 182 | }]); 183 | 184 | 185 | })(window, window.angular); 186 | -------------------------------------------------------------------------------- /app/lib/angular/angular-cookies.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,i=!1,j=f.copy,k=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,j(a,g),j(a,c),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(c[a])&&b.cookies(a,l);for(a in c)e=c[a],f.isString(e)?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(k(e[a])?delete c[a]:c[a]=e[a])});return c}]).factory("$cookieStore", 7 | ["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular); 8 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | 7 | ( 8 | 9 | /** 10 | * @ngdoc interface 11 | * @name angular.Module 12 | * @description 13 | * 14 | * Interface for configuring angular {@link angular.module modules}. 15 | */ 16 | 17 | function setupModuleLoader(window) { 18 | 19 | function ensure(obj, name, factory) { 20 | return obj[name] || (obj[name] = factory()); 21 | } 22 | 23 | return ensure(ensure(window, 'angular', Object), 'module', function() { 24 | /** @type {Object.} */ 25 | var modules = {}; 26 | 27 | /** 28 | * @ngdoc function 29 | * @name angular.module 30 | * @description 31 | * 32 | * The `angular.module` is a global place for creating and registering Angular modules. All 33 | * modules (angular core or 3rd party) that should be available to an application must be 34 | * registered using this mechanism. 35 | * 36 | * 37 | * # Module 38 | * 39 | * A module is a collocation of services, directives, filters, and configuration information. Module 40 | * is used to configure the {@link AUTO.$injector $injector}. 41 | * 42 | *
 43 |      * // Create a new module
 44 |      * var myModule = angular.module('myModule', []);
 45 |      *
 46 |      * // register a new service
 47 |      * myModule.value('appName', 'MyCoolApp');
 48 |      *
 49 |      * // configure existing services inside initialization blocks.
 50 |      * myModule.config(function($locationProvider) {
 51 | 'use strict';
 52 |      *   // Configure existing providers
 53 |      *   $locationProvider.hashPrefix('!');
 54 |      * });
 55 |      * 
56 | * 57 | * Then you can create an injector and load your modules like this: 58 | * 59 | *
 60 |      * var injector = angular.injector(['ng', 'MyModule'])
 61 |      * 
62 | * 63 | * However it's more likely that you'll just use 64 | * {@link ng.directive:ngApp ngApp} or 65 | * {@link angular.bootstrap} to simplify this process for you. 66 | * 67 | * @param {!string} name The name of the module to create or retrieve. 68 | * @param {Array.=} requires If specified then new module is being created. If unspecified then the 69 | * the module is being retrieved for further configuration. 70 | * @param {Function} configFn Optional configuration function for the module. Same as 71 | * {@link angular.Module#config Module#config()}. 72 | * @returns {module} new module with the {@link angular.Module} api. 73 | */ 74 | return function module(name, requires, configFn) { 75 | if (requires && modules.hasOwnProperty(name)) { 76 | modules[name] = null; 77 | } 78 | return ensure(modules, name, function() { 79 | if (!requires) { 80 | throw Error('No module: ' + name); 81 | } 82 | 83 | /** @type {!Array.>} */ 84 | var invokeQueue = []; 85 | 86 | /** @type {!Array.} */ 87 | var runBlocks = []; 88 | 89 | var config = invokeLater('$injector', 'invoke'); 90 | 91 | /** @type {angular.Module} */ 92 | var moduleInstance = { 93 | // Private state 94 | _invokeQueue: invokeQueue, 95 | _runBlocks: runBlocks, 96 | 97 | /** 98 | * @ngdoc property 99 | * @name angular.Module#requires 100 | * @propertyOf angular.Module 101 | * @returns {Array.} List of module names which must be loaded before this module. 102 | * @description 103 | * Holds the list of modules which the injector will load before the current module is loaded. 104 | */ 105 | requires: requires, 106 | 107 | /** 108 | * @ngdoc property 109 | * @name angular.Module#name 110 | * @propertyOf angular.Module 111 | * @returns {string} Name of the module. 112 | * @description 113 | */ 114 | name: name, 115 | 116 | 117 | /** 118 | * @ngdoc method 119 | * @name angular.Module#provider 120 | * @methodOf angular.Module 121 | * @param {string} name service name 122 | * @param {Function} providerType Construction function for creating new instance of the service. 123 | * @description 124 | * See {@link AUTO.$provide#provider $provide.provider()}. 125 | */ 126 | provider: invokeLater('$provide', 'provider'), 127 | 128 | /** 129 | * @ngdoc method 130 | * @name angular.Module#factory 131 | * @methodOf angular.Module 132 | * @param {string} name service name 133 | * @param {Function} providerFunction Function for creating new instance of the service. 134 | * @description 135 | * See {@link AUTO.$provide#factory $provide.factory()}. 136 | */ 137 | factory: invokeLater('$provide', 'factory'), 138 | 139 | /** 140 | * @ngdoc method 141 | * @name angular.Module#service 142 | * @methodOf angular.Module 143 | * @param {string} name service name 144 | * @param {Function} constructor A constructor function that will be instantiated. 145 | * @description 146 | * See {@link AUTO.$provide#service $provide.service()}. 147 | */ 148 | service: invokeLater('$provide', 'service'), 149 | 150 | /** 151 | * @ngdoc method 152 | * @name angular.Module#value 153 | * @methodOf angular.Module 154 | * @param {string} name service name 155 | * @param {*} object Service instance object. 156 | * @description 157 | * See {@link AUTO.$provide#value $provide.value()}. 158 | */ 159 | value: invokeLater('$provide', 'value'), 160 | 161 | /** 162 | * @ngdoc method 163 | * @name angular.Module#constant 164 | * @methodOf angular.Module 165 | * @param {string} name constant name 166 | * @param {*} object Constant value. 167 | * @description 168 | * Because the constant are fixed, they get applied before other provide methods. 169 | * See {@link AUTO.$provide#constant $provide.constant()}. 170 | */ 171 | constant: invokeLater('$provide', 'constant', 'unshift'), 172 | 173 | /** 174 | * @ngdoc method 175 | * @name angular.Module#filter 176 | * @methodOf angular.Module 177 | * @param {string} name Filter name. 178 | * @param {Function} filterFactory Factory function for creating new instance of filter. 179 | * @description 180 | * See {@link ng.$filterProvider#register $filterProvider.register()}. 181 | */ 182 | filter: invokeLater('$filterProvider', 'register'), 183 | 184 | /** 185 | * @ngdoc method 186 | * @name angular.Module#controller 187 | * @methodOf angular.Module 188 | * @param {string} name Controller name. 189 | * @param {Function} constructor Controller constructor function. 190 | * @description 191 | * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 192 | */ 193 | controller: invokeLater('$controllerProvider', 'register'), 194 | 195 | /** 196 | * @ngdoc method 197 | * @name angular.Module#directive 198 | * @methodOf angular.Module 199 | * @param {string} name directive name 200 | * @param {Function} directiveFactory Factory function for creating new instance of 201 | * directives. 202 | * @description 203 | * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 204 | */ 205 | directive: invokeLater('$compileProvider', 'directive'), 206 | 207 | /** 208 | * @ngdoc method 209 | * @name angular.Module#config 210 | * @methodOf angular.Module 211 | * @param {Function} configFn Execute this function on module load. Useful for service 212 | * configuration. 213 | * @description 214 | * Use this method to register work which needs to be performed on module loading. 215 | */ 216 | config: config, 217 | 218 | /** 219 | * @ngdoc method 220 | * @name angular.Module#run 221 | * @methodOf angular.Module 222 | * @param {Function} initializationFn Execute this function after injector creation. 223 | * Useful for application initialization. 224 | * @description 225 | * Use this method to register work which should be performed when the injector is done 226 | * loading all modules. 227 | */ 228 | run: function(block) { 229 | runBlocks.push(block); 230 | return this; 231 | } 232 | }; 233 | 234 | if (configFn) { 235 | config(configFn); 236 | } 237 | 238 | return moduleInstance; 239 | 240 | /** 241 | * @param {string} provider 242 | * @param {string} method 243 | * @param {String=} insertMethod 244 | * @returns {angular.Module} 245 | */ 246 | function invokeLater(provider, method, insertMethod) { 247 | return function() { 248 | invokeQueue[insertMethod || 'push']([provider, method, arguments]); 249 | return moduleInstance; 250 | } 251 | } 252 | }); 253 | }; 254 | }); 255 | 256 | } 257 | 258 | )(window); 259 | 260 | /** 261 | * Closure compiler type information 262 | * 263 | * @typedef { { 264 | * requires: !Array., 265 | * invokeQueue: !Array.>, 266 | * 267 | * service: function(string, Function):angular.Module, 268 | * factory: function(string, Function):angular.Module, 269 | * value: function(string, *):angular.Module, 270 | * 271 | * filter: function(string, Function):angular.Module, 272 | * 273 | * init: function(Function):angular.Module 274 | * } } 275 | */ 276 | angular.Module; 277 | 278 | -------------------------------------------------------------------------------- /app/lib/angular/angular-loader.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(i){'use strict';function d(c,b,e){return c[b]||(c[b]=e())}return d(d(i,"angular",Object),"module",function(){var c={};return function(b,e,f){e&&c.hasOwnProperty(b)&&(c[b]=null);return d(c,b,function(){function a(a,b,d){return function(){c[d||"push"]([a,b,arguments]);return g}}if(!e)throw Error("No module: "+b);var c=[],d=[],h=a("$injector","invoke"),g={_invokeQueue:c,_runBlocks:d,requires:e,name:b,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"), 7 | value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:h,run:function(a){d.push(a);return this}};f&&h(f);return g})}})})(window); 8 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngResource 12 | * @description 13 | */ 14 | 15 | /** 16 | * @ngdoc object 17 | * @name ngResource.$resource 18 | * @requires $http 19 | * 20 | * @description 21 | * A factory which creates a resource object that lets you interact with 22 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 23 | * 24 | * The returned resource object has action methods which provide high-level behaviors without 25 | * the need to interact with the low level {@link ng.$http $http} service. 26 | * 27 | * # Installation 28 | * To use $resource make sure you have included the `angular-resource.js` that comes in Angular 29 | * package. You can also find this file on Google CDN, bower as well as at 30 | * {@link http://code.angularjs.org/ code.angularjs.org}. 31 | * 32 | * Finally load the module in your application: 33 | * 34 | * angular.module('app', ['ngResource']); 35 | * 36 | * and you are ready to get started! 37 | * 38 | * @param {string} url A parameterized URL template with parameters prefixed by `:` as in 39 | * `/user/:username`. If you are using a URL with a port number (e.g. 40 | * `http://example.com:8080/api`), you'll need to escape the colon character before the port 41 | * number, like this: `$resource('http://example.com\\:8080/api')`. 42 | * 43 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 44 | * `actions` methods. 45 | * 46 | * Each key value in the parameter object is first bound to url template if present and then any 47 | * excess keys are appended to the url search query after the `?`. 48 | * 49 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 50 | * URL `/path/greet?salutation=Hello`. 51 | * 52 | * If the parameter value is prefixed with `@` then the value of that parameter is extracted from 53 | * the data object (useful for non-GET operations). 54 | * 55 | * @param {Object.=} actions Hash with declaration of custom action that should extend the 56 | * default set of resource actions. The declaration should be created in the following format: 57 | * 58 | * {action1: {method:?, params:?, isArray:?}, 59 | * action2: {method:?, params:?, isArray:?}, 60 | * ...} 61 | * 62 | * Where: 63 | * 64 | * - `action` – {string} – The name of action. This name becomes the name of the method on your 65 | * resource object. 66 | * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, 67 | * and `JSONP` 68 | * - `params` – {object=} – Optional set of pre-bound parameters for this action. 69 | * - isArray – {boolean=} – If true then the returned object for this action is an array, see 70 | * `returns` section. 71 | * 72 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 73 | * optionally extended with custom `actions`. The default set contains these actions: 74 | * 75 | * { 'get': {method:'GET'}, 76 | * 'save': {method:'POST'}, 77 | * 'query': {method:'GET', isArray:true}, 78 | * 'remove': {method:'DELETE'}, 79 | * 'delete': {method:'DELETE'} }; 80 | * 81 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 82 | * destination and parameters. When the data is returned from the server then the object is an 83 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 84 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 85 | * read, update, delete) on server-side data like this: 86 | *
 87 |         var User = $resource('/user/:userId', {userId:'@id'});
 88 |         var user = User.get({userId:123}, function() {
 89 |           user.abc = true;
 90 |           user.$save();
 91 |         });
 92 |      
93 | * 94 | * It is important to realize that invoking a $resource object method immediately returns an 95 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 96 | * server the existing reference is populated with the actual data. This is a useful trick since 97 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 98 | * object results in no rendering, once the data arrives from the server then the object is 99 | * populated with the data and the view automatically re-renders itself showing the new data. This 100 | * means that in most case one never has to write a callback function for the action methods. 101 | * 102 | * The action methods on the class object or instance object can be invoked with the following 103 | * parameters: 104 | * 105 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 106 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 107 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 108 | * 109 | * 110 | * @example 111 | * 112 | * # Credit card resource 113 | * 114 | *
115 |      // Define CreditCard class
116 |      var CreditCard = $resource('/user/:userId/card/:cardId',
117 |       {userId:123, cardId:'@id'}, {
118 |        charge: {method:'POST', params:{charge:true}}
119 |       });
120 | 
121 |      // We can retrieve a collection from the server
122 |      var cards = CreditCard.query(function() {
123 |        // GET: /user/123/card
124 |        // server returns: [ {id:456, number:'1234', name:'Smith'} ];
125 | 
126 |        var card = cards[0];
127 |        // each item is an instance of CreditCard
128 |        expect(card instanceof CreditCard).toEqual(true);
129 |        card.name = "J. Smith";
130 |        // non GET methods are mapped onto the instances
131 |        card.$save();
132 |        // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
133 |        // server returns: {id:456, number:'1234', name: 'J. Smith'};
134 | 
135 |        // our custom method is mapped as well.
136 |        card.$charge({amount:9.99});
137 |        // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
138 |      });
139 | 
140 |      // we can create an instance as well
141 |      var newCard = new CreditCard({number:'0123'});
142 |      newCard.name = "Mike Smith";
143 |      newCard.$save();
144 |      // POST: /user/123/card {number:'0123', name:'Mike Smith'}
145 |      // server returns: {id:789, number:'01234', name: 'Mike Smith'};
146 |      expect(newCard.id).toEqual(789);
147 |  * 
148 | * 149 | * The object returned from this function execution is a resource "class" which has "static" method 150 | * for each action in the definition. 151 | * 152 | * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. 153 | * When the data is returned from the server then the object is an instance of the resource type and 154 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 155 | * operations (create, read, update, delete) on server-side data. 156 | 157 |
158 |      var User = $resource('/user/:userId', {userId:'@id'});
159 |      var user = User.get({userId:123}, function() {
160 |        user.abc = true;
161 |        user.$save();
162 |      });
163 |    
164 | * 165 | * It's worth noting that the success callback for `get`, `query` and other method gets passed 166 | * in the response that came from the server as well as $http header getter function, so one 167 | * could rewrite the above example and get access to http headers as: 168 | * 169 |
170 |      var User = $resource('/user/:userId', {userId:'@id'});
171 |      User.get({userId:123}, function(u, getResponseHeaders){
172 |        u.abc = true;
173 |        u.$save(function(u, putResponseHeaders) {
174 |          //u => saved user object
175 |          //putResponseHeaders => $http header getter
176 |        });
177 |      });
178 |    
179 | 180 | * # Buzz client 181 | 182 | Let's look at what a buzz client created with the `$resource` service looks like: 183 | 184 | 185 | 205 | 206 |
207 | 208 | 209 |
210 |
211 |

212 | 213 | {{item.actor.name}} 214 | Expand replies: {{item.links.replies[0].count}} 215 |

216 | {{item.object.content | html}} 217 |
218 | 219 | {{reply.actor.name}}: {{reply.content | html}} 220 |
221 |
222 |
223 |
224 | 225 | 226 |
227 | */ 228 | angular.module('ngResource', ['ng']). 229 | factory('$resource', ['$http', '$parse', function($http, $parse) { 230 | var DEFAULT_ACTIONS = { 231 | 'get': {method:'GET'}, 232 | 'save': {method:'POST'}, 233 | 'query': {method:'GET', isArray:true}, 234 | 'remove': {method:'DELETE'}, 235 | 'delete': {method:'DELETE'} 236 | }; 237 | var noop = angular.noop, 238 | forEach = angular.forEach, 239 | extend = angular.extend, 240 | copy = angular.copy, 241 | isFunction = angular.isFunction, 242 | getter = function(obj, path) { 243 | return $parse(path)(obj); 244 | }; 245 | 246 | /** 247 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 248 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 249 | * segments: 250 | * segment = *pchar 251 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 252 | * pct-encoded = "%" HEXDIG HEXDIG 253 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 254 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 255 | * / "*" / "+" / "," / ";" / "=" 256 | */ 257 | function encodeUriSegment(val) { 258 | return encodeUriQuery(val, true). 259 | replace(/%26/gi, '&'). 260 | replace(/%3D/gi, '='). 261 | replace(/%2B/gi, '+'); 262 | } 263 | 264 | 265 | /** 266 | * This method is intended for encoding *key* or *value* parts of query component. We need a custom 267 | * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be 268 | * encoded per http://tools.ietf.org/html/rfc3986: 269 | * query = *( pchar / "/" / "?" ) 270 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 271 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 272 | * pct-encoded = "%" HEXDIG HEXDIG 273 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 274 | * / "*" / "+" / "," / ";" / "=" 275 | */ 276 | function encodeUriQuery(val, pctEncodeSpaces) { 277 | return encodeURIComponent(val). 278 | replace(/%40/gi, '@'). 279 | replace(/%3A/gi, ':'). 280 | replace(/%24/g, '$'). 281 | replace(/%2C/gi, ','). 282 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 283 | } 284 | 285 | function Route(template, defaults) { 286 | this.template = template = template + '#'; 287 | this.defaults = defaults || {}; 288 | var urlParams = this.urlParams = {}; 289 | forEach(template.split(/\W/), function(param){ 290 | if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) { 291 | urlParams[param] = true; 292 | } 293 | }); 294 | this.template = template.replace(/\\:/g, ':'); 295 | } 296 | 297 | Route.prototype = { 298 | url: function(params) { 299 | var self = this, 300 | url = this.template, 301 | val, 302 | encodedVal; 303 | 304 | params = params || {}; 305 | forEach(this.urlParams, function(_, urlParam){ 306 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 307 | if (angular.isDefined(val) && val !== null) { 308 | encodedVal = encodeUriSegment(val); 309 | url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1"); 310 | } else { 311 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match, 312 | leadingSlashes, tail) { 313 | if (tail.charAt(0) == '/') { 314 | return tail; 315 | } else { 316 | return leadingSlashes + tail; 317 | } 318 | }); 319 | } 320 | }); 321 | url = url.replace(/\/?#$/, ''); 322 | var query = []; 323 | forEach(params, function(value, key){ 324 | if (!self.urlParams[key]) { 325 | query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); 326 | } 327 | }); 328 | query.sort(); 329 | url = url.replace(/\/*$/, ''); 330 | return url + (query.length ? '?' + query.join('&') : ''); 331 | } 332 | }; 333 | 334 | 335 | function ResourceFactory(url, paramDefaults, actions) { 336 | var route = new Route(url); 337 | 338 | actions = extend({}, DEFAULT_ACTIONS, actions); 339 | 340 | function extractParams(data, actionParams){ 341 | var ids = {}; 342 | actionParams = extend({}, paramDefaults, actionParams); 343 | forEach(actionParams, function(value, key){ 344 | ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; 345 | }); 346 | return ids; 347 | } 348 | 349 | function Resource(value){ 350 | copy(value || {}, this); 351 | } 352 | 353 | forEach(actions, function(action, name) { 354 | action.method = angular.uppercase(action.method); 355 | var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; 356 | Resource[name] = function(a1, a2, a3, a4) { 357 | var params = {}; 358 | var data; 359 | var success = noop; 360 | var error = null; 361 | switch(arguments.length) { 362 | case 4: 363 | error = a4; 364 | success = a3; 365 | //fallthrough 366 | case 3: 367 | case 2: 368 | if (isFunction(a2)) { 369 | if (isFunction(a1)) { 370 | success = a1; 371 | error = a2; 372 | break; 373 | } 374 | 375 | success = a2; 376 | error = a3; 377 | //fallthrough 378 | } else { 379 | params = a1; 380 | data = a2; 381 | success = a3; 382 | break; 383 | } 384 | case 1: 385 | if (isFunction(a1)) success = a1; 386 | else if (hasBody) data = a1; 387 | else params = a1; 388 | break; 389 | case 0: break; 390 | default: 391 | throw "Expected between 0-4 arguments [params, data, success, error], got " + 392 | arguments.length + " arguments."; 393 | } 394 | 395 | var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); 396 | $http({ 397 | method: action.method, 398 | url: route.url(extend({}, extractParams(data, action.params || {}), params)), 399 | data: data 400 | }).then(function(response) { 401 | var data = response.data; 402 | 403 | if (data) { 404 | if (action.isArray) { 405 | value.length = 0; 406 | forEach(data, function(item) { 407 | value.push(new Resource(item)); 408 | }); 409 | } else { 410 | copy(data, value); 411 | } 412 | } 413 | (success||noop)(value, response.headers); 414 | }, error); 415 | 416 | return value; 417 | }; 418 | 419 | 420 | Resource.prototype['$' + name] = function(a1, a2, a3) { 421 | var params = extractParams(this), 422 | success = noop, 423 | error; 424 | 425 | switch(arguments.length) { 426 | case 3: params = a1; success = a2; error = a3; break; 427 | case 2: 428 | case 1: 429 | if (isFunction(a1)) { 430 | success = a1; 431 | error = a2; 432 | } else { 433 | params = a1; 434 | success = a2 || noop; 435 | } 436 | case 0: break; 437 | default: 438 | throw "Expected between 1-3 arguments [params, success, error], got " + 439 | arguments.length + " arguments."; 440 | } 441 | var data = hasBody ? this : undefined; 442 | Resource[name].call(this, params, data, success, error); 443 | }; 444 | }); 445 | 446 | Resource.bind = function(additionalParamDefaults){ 447 | return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 448 | }; 449 | 450 | return Resource; 451 | } 452 | 453 | return ResourceFactory; 454 | }]); 455 | 456 | 457 | })(window, window.angular); 458 | -------------------------------------------------------------------------------- /app/lib/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(C,d,w){'use strict';d.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function s(b,e){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,e?"%20":"+")}function t(b,e){this.template=b+="#";this.defaults=e||{};var a=this.urlParams={};h(b.split(/\W/),function(f){f&&RegExp("(^|[^\\\\]):"+f+"\\W").test(b)&&(a[f]=!0)});this.template=b.replace(/\\:/g,":")}function u(b,e,a){function f(m,a){var b= 7 | {},a=o({},e,a);h(a,function(a,z){var c;a.charAt&&a.charAt(0)=="@"?(c=a.substr(1),c=y(c)(m)):c=a;b[z]=c});return b}function g(a){v(a||{},this)}var k=new t(b),a=o({},A,a);h(a,function(a,b){a.method=d.uppercase(a.method);var e=a.method=="POST"||a.method=="PUT"||a.method=="PATCH";g[b]=function(b,c,d,B){var j={},i,l=p,q=null;switch(arguments.length){case 4:q=B,l=d;case 3:case 2:if(r(c)){if(r(b)){l=b;q=c;break}l=c;q=d}else{j=b;i=c;l=d;break}case 1:r(b)?l=b:e?i=b:j=b;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ 8 | arguments.length+" arguments.";}var n=this instanceof g?this:a.isArray?[]:new g(i);x({method:a.method,url:k.url(o({},f(i,a.params||{}),j)),data:i}).then(function(b){var c=b.data;if(c)a.isArray?(n.length=0,h(c,function(a){n.push(new g(a))})):v(c,n);(l||p)(n,b.headers)},q);return n};g.prototype["$"+b]=function(a,d,h){var m=f(this),j=p,i;switch(arguments.length){case 3:m=a;j=d;i=h;break;case 2:case 1:r(a)?(j=a,i=d):(m=a,j=d||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ 9 | arguments.length+" arguments.";}g[b].call(this,m,e?this:w,j,i)}});g.bind=function(d){return u(b,o({},e,d),a)};return g}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=d.noop,h=d.forEach,o=d.extend,v=d.copy,r=d.isFunction;t.prototype={url:function(b){var e=this,a=this.template,f,g,b=b||{};h(this.urlParams,function(h,c){f=b.hasOwnProperty(c)?b[c]:e.defaults[c];d.isDefined(f)&&f!==null?(g=s(f,!0).replace(/%26/gi,"&").replace(/%3D/gi, 10 | "=").replace(/%2B/gi,"+"),a=a.replace(RegExp(":"+c+"(\\W)","g"),g+"$1")):a=a.replace(RegExp("(/?):"+c+"(\\W)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});var a=a.replace(/\/?#$/,""),k=[];h(b,function(a,b){e.urlParams[b]||k.push(s(b)+"="+s(a))});k.sort();a=a.replace(/\/*$/,"");return a+(k.length?"?"+k.join("&"):"")}};return u}])})(window,window.angular); 11 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.0.7 3 | * (c) 2010-2012 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) { 7 | 'use strict'; 8 | 9 | /** 10 | * @ngdoc overview 11 | * @name ngSanitize 12 | * @description 13 | */ 14 | 15 | /* 16 | * HTML Parser By Misko Hevery (misko@hevery.com) 17 | * based on: HTML Parser By John Resig (ejohn.org) 18 | * Original code by Erik Arvidsson, Mozilla Public License 19 | * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 20 | * 21 | * // Use like so: 22 | * htmlParser(htmlString, { 23 | * start: function(tag, attrs, unary) {}, 24 | * end: function(tag) {}, 25 | * chars: function(text) {}, 26 | * comment: function(text) {} 27 | * }); 28 | * 29 | */ 30 | 31 | 32 | /** 33 | * @ngdoc service 34 | * @name ngSanitize.$sanitize 35 | * @function 36 | * 37 | * @description 38 | * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 39 | * then serialized back to properly escaped html string. This means that no unsafe input can make 40 | * it into the returned string, however, since our parser is more strict than a typical browser 41 | * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 42 | * browser, won't make it through the sanitizer. 43 | * 44 | * @param {string} html Html input. 45 | * @returns {string} Sanitized html. 46 | * 47 | * @example 48 | 49 | 50 | 58 |
59 | Snippet: 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |
FilterSourceRendered
html filter 69 |
<div ng-bind-html="snippet">
</div>
70 |
72 |
73 |
no filter
<div ng-bind="snippet">
</div>
unsafe html filter
<div ng-bind-html-unsafe="snippet">
</div>
86 |
87 |
88 | 89 | it('should sanitize the html snippet ', function() { 90 | expect(using('#html-filter').element('div').html()). 91 | toBe('

an html\nclick here\nsnippet

'); 92 | }); 93 | 94 | it('should escape snippet without any filter', function() { 95 | expect(using('#escaped-html').element('div').html()). 96 | toBe("<p style=\"color:blue\">an html\n" + 97 | "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 98 | "snippet</p>"); 99 | }); 100 | 101 | it('should inline raw snippet if filtered as unsafe', function() { 102 | expect(using('#html-unsafe-filter').element("div").html()). 103 | toBe("

an html\n" + 104 | "click here\n" + 105 | "snippet

"); 106 | }); 107 | 108 | it('should update', function() { 109 | input('snippet').enter('new text'); 110 | expect(using('#html-filter').binding('snippet')).toBe('new text'); 111 | expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); 112 | expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text'); 113 | }); 114 |
115 |
116 | */ 117 | var $sanitize = function(html) { 118 | var buf = []; 119 | htmlParser(html, htmlSanitizeWriter(buf)); 120 | return buf.join(''); 121 | }; 122 | 123 | 124 | // Regular Expressions for parsing tags and attributes 125 | var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, 126 | END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, 127 | ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 128 | BEGIN_TAG_REGEXP = /^/g, 131 | CDATA_REGEXP = //g, 132 | URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/, 133 | NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) 134 | 135 | 136 | // Good source of info about elements and attributes 137 | // http://dev.w3.org/html5/spec/Overview.html#semantics 138 | // http://simon.html5.org/html-elements 139 | 140 | // Safe Void Elements - HTML5 141 | // http://dev.w3.org/html5/spec/Overview.html#void-elements 142 | var voidElements = makeMap("area,br,col,hr,img,wbr"); 143 | 144 | // Elements that you can, intentionally, leave open (and which close themselves) 145 | // http://dev.w3.org/html5/spec/Overview.html#optional-tags 146 | var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 147 | optionalEndTagInlineElements = makeMap("rp,rt"), 148 | optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); 149 | 150 | // Safe Block Elements - HTML5 151 | var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," + 152 | "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," + 153 | "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 154 | 155 | // Inline Elements - HTML5 156 | var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," + 157 | "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," + 158 | "span,strike,strong,sub,sup,time,tt,u,var")); 159 | 160 | 161 | // Special Elements (can contain anything) 162 | var specialElements = makeMap("script,style"); 163 | 164 | var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); 165 | 166 | //Attributes that have href and hence need to be sanitized 167 | var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); 168 | var validAttrs = angular.extend({}, uriAttrs, makeMap( 169 | 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ 170 | 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 171 | 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 172 | 'scope,scrolling,shape,span,start,summary,target,title,type,'+ 173 | 'valign,value,vspace,width')); 174 | 175 | function makeMap(str) { 176 | var obj = {}, items = str.split(','), i; 177 | for (i = 0; i < items.length; i++) obj[items[i]] = true; 178 | return obj; 179 | } 180 | 181 | 182 | /** 183 | * @example 184 | * htmlParser(htmlString, { 185 | * start: function(tag, attrs, unary) {}, 186 | * end: function(tag) {}, 187 | * chars: function(text) {}, 188 | * comment: function(text) {} 189 | * }); 190 | * 191 | * @param {string} html string 192 | * @param {object} handler 193 | */ 194 | function htmlParser( html, handler ) { 195 | var index, chars, match, stack = [], last = html; 196 | stack.last = function() { return stack[ stack.length - 1 ]; }; 197 | 198 | while ( html ) { 199 | chars = true; 200 | 201 | // Make sure we're not in a script or style element 202 | if ( !stack.last() || !specialElements[ stack.last() ] ) { 203 | 204 | // Comment 205 | if ( html.indexOf(""); 207 | 208 | if ( index >= 0 ) { 209 | if (handler.comment) handler.comment( html.substring( 4, index ) ); 210 | html = html.substring( index + 3 ); 211 | chars = false; 212 | } 213 | 214 | // end tag 215 | } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 216 | match = html.match( END_TAG_REGEXP ); 217 | 218 | if ( match ) { 219 | html = html.substring( match[0].length ); 220 | match[0].replace( END_TAG_REGEXP, parseEndTag ); 221 | chars = false; 222 | } 223 | 224 | // start tag 225 | } else if ( BEGIN_TAG_REGEXP.test(html) ) { 226 | match = html.match( START_TAG_REGEXP ); 227 | 228 | if ( match ) { 229 | html = html.substring( match[0].length ); 230 | match[0].replace( START_TAG_REGEXP, parseStartTag ); 231 | chars = false; 232 | } 233 | } 234 | 235 | if ( chars ) { 236 | index = html.indexOf("<"); 237 | 238 | var text = index < 0 ? html : html.substring( 0, index ); 239 | html = index < 0 ? "" : html.substring( index ); 240 | 241 | if (handler.chars) handler.chars( decodeEntities(text) ); 242 | } 243 | 244 | } else { 245 | html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ 246 | text = text. 247 | replace(COMMENT_REGEXP, "$1"). 248 | replace(CDATA_REGEXP, "$1"); 249 | 250 | if (handler.chars) handler.chars( decodeEntities(text) ); 251 | 252 | return ""; 253 | }); 254 | 255 | parseEndTag( "", stack.last() ); 256 | } 257 | 258 | if ( html == last ) { 259 | throw "Parse Error: " + html; 260 | } 261 | last = html; 262 | } 263 | 264 | // Clean up any remaining tags 265 | parseEndTag(); 266 | 267 | function parseStartTag( tag, tagName, rest, unary ) { 268 | tagName = angular.lowercase(tagName); 269 | if ( blockElements[ tagName ] ) { 270 | while ( stack.last() && inlineElements[ stack.last() ] ) { 271 | parseEndTag( "", stack.last() ); 272 | } 273 | } 274 | 275 | if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 276 | parseEndTag( "", tagName ); 277 | } 278 | 279 | unary = voidElements[ tagName ] || !!unary; 280 | 281 | if ( !unary ) 282 | stack.push( tagName ); 283 | 284 | var attrs = {}; 285 | 286 | rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { 287 | var value = doubleQuotedValue 288 | || singleQoutedValue 289 | || unqoutedValue 290 | || ''; 291 | 292 | attrs[name] = decodeEntities(value); 293 | }); 294 | if (handler.start) handler.start( tagName, attrs, unary ); 295 | } 296 | 297 | function parseEndTag( tag, tagName ) { 298 | var pos = 0, i; 299 | tagName = angular.lowercase(tagName); 300 | if ( tagName ) 301 | // Find the closest opened tag of the same type 302 | for ( pos = stack.length - 1; pos >= 0; pos-- ) 303 | if ( stack[ pos ] == tagName ) 304 | break; 305 | 306 | if ( pos >= 0 ) { 307 | // Close all the open elements, up the stack 308 | for ( i = stack.length - 1; i >= pos; i-- ) 309 | if (handler.end) handler.end( stack[ i ] ); 310 | 311 | // Remove the open elements from the stack 312 | stack.length = pos; 313 | } 314 | } 315 | } 316 | 317 | /** 318 | * decodes all entities into regular string 319 | * @param value 320 | * @returns {string} A string with decoded entities. 321 | */ 322 | var hiddenPre=document.createElement("pre"); 323 | function decodeEntities(value) { 324 | hiddenPre.innerHTML=value.replace(//g, '>'); 343 | } 344 | 345 | /** 346 | * create an HTML/XML writer which writes to buffer 347 | * @param {Array} buf use buf.jain('') to get out sanitized html string 348 | * @returns {object} in the form of { 349 | * start: function(tag, attrs, unary) {}, 350 | * end: function(tag) {}, 351 | * chars: function(text) {}, 352 | * comment: function(text) {} 353 | * } 354 | */ 355 | function htmlSanitizeWriter(buf){ 356 | var ignore = false; 357 | var out = angular.bind(buf, buf.push); 358 | return { 359 | start: function(tag, attrs, unary){ 360 | tag = angular.lowercase(tag); 361 | if (!ignore && specialElements[tag]) { 362 | ignore = tag; 363 | } 364 | if (!ignore && validElements[tag] == true) { 365 | out('<'); 366 | out(tag); 367 | angular.forEach(attrs, function(value, key){ 368 | var lkey=angular.lowercase(key); 369 | if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { 370 | out(' '); 371 | out(key); 372 | out('="'); 373 | out(encodeEntities(value)); 374 | out('"'); 375 | } 376 | }); 377 | out(unary ? '/>' : '>'); 378 | } 379 | }, 380 | end: function(tag){ 381 | tag = angular.lowercase(tag); 382 | if (!ignore && validElements[tag] == true) { 383 | out(''); 386 | } 387 | if (tag == ignore) { 388 | ignore = false; 389 | } 390 | }, 391 | chars: function(chars){ 392 | if (!ignore) { 393 | out(encodeEntities(chars)); 394 | } 395 | } 396 | }; 397 | } 398 | 399 | 400 | // define ngSanitize module and register $sanitize service 401 | angular.module('ngSanitize', []).value('$sanitize', $sanitize); 402 | 403 | /** 404 | * @ngdoc directive 405 | * @name ngSanitize.directive:ngBindHtml 406 | * 407 | * @description 408 | * Creates a binding that will sanitize the result of evaluating the `expression` with the 409 | * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element. 410 | * 411 | * See {@link ngSanitize.$sanitize $sanitize} docs for examples. 412 | * 413 | * @element ANY 414 | * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 415 | */ 416 | angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) { 417 | return function(scope, element, attr) { 418 | element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 419 | scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) { 420 | value = $sanitize(value); 421 | element.html(value || ''); 422 | }); 423 | }; 424 | }]); 425 | 426 | /** 427 | * @ngdoc filter 428 | * @name ngSanitize.filter:linky 429 | * @function 430 | * 431 | * @description 432 | * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 433 | * plain email address links. 434 | * 435 | * @param {string} text Input text. 436 | * @returns {string} Html-linkified text. 437 | * 438 | * @usage 439 | 440 | * 441 | * @example 442 | 443 | 444 | 454 |
455 | Snippet: 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 467 | 470 | 471 | 472 | 473 | 474 | 475 | 476 |
FilterSourceRendered
linky filter 465 |
<div ng-bind-html="snippet | linky">
</div>
466 |
468 |
469 |
no filter
<div ng-bind="snippet">
</div>
477 | 478 | 479 | it('should linkify the snippet with urls', function() { 480 | expect(using('#linky-filter').binding('snippet | linky')). 481 | toBe('Pretty text with some links: ' + 482 | 'http://angularjs.org/, ' + 483 | 'us@somewhere.org, ' + 484 | 'another@somewhere.org, ' + 485 | 'and one more: ftp://127.0.0.1/.'); 486 | }); 487 | 488 | it ('should not linkify snippet without the linky filter', function() { 489 | expect(using('#escaped-html').binding('snippet')). 490 | toBe("Pretty text with some links:\n" + 491 | "http://angularjs.org/,\n" + 492 | "mailto:us@somewhere.org,\n" + 493 | "another@somewhere.org,\n" + 494 | "and one more: ftp://127.0.0.1/."); 495 | }); 496 | 497 | it('should update', function() { 498 | input('snippet').enter('new http://link.'); 499 | expect(using('#linky-filter').binding('snippet | linky')). 500 | toBe('new http://link.'); 501 | expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 502 | }); 503 | 504 | 505 | */ 506 | angular.module('ngSanitize').filter('linky', function() { 507 | var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 508 | MAILTO_REGEXP = /^mailto:/; 509 | 510 | return function(text) { 511 | if (!text) return text; 512 | var match; 513 | var raw = text; 514 | var html = []; 515 | // TODO(vojta): use $sanitize instead 516 | var writer = htmlSanitizeWriter(html); 517 | var url; 518 | var i; 519 | while ((match = raw.match(LINKY_URL_REGEXP))) { 520 | // We can not end in these as they are sometimes found at the end of the sentence 521 | url = match[0]; 522 | // if we did not match ftp/http/mailto then assume mailto 523 | if (match[2] == match[3]) url = 'mailto:' + url; 524 | i = match.index; 525 | writer.chars(raw.substr(0, i)); 526 | writer.start('a', {href:url}); 527 | writer.chars(match[0].replace(MAILTO_REGEXP, '')); 528 | writer.end('a'); 529 | raw = raw.substring(i + match[0].length); 530 | } 531 | writer.chars(raw); 532 | return html.join(''); 533 | }; 534 | }); 535 | 536 | 537 | })(window, window.angular); 538 | -------------------------------------------------------------------------------- /app/lib/angular/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.0.7 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length= 7 | e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+ 8 | f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]== 9 | !0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g, 10 | E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), 11 | q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[]; 12 | z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index, 13 | h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular); 14 | -------------------------------------------------------------------------------- /app/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.7 2 | -------------------------------------------------------------------------------- /app/lib/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | .btn-default, 2 | .btn-primary, 3 | .btn-success, 4 | .btn-info, 5 | .btn-warning, 6 | .btn-danger { 7 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 8 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 9 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 10 | } 11 | 12 | .btn-default:active, 13 | .btn-primary:active, 14 | .btn-success:active, 15 | .btn-info:active, 16 | .btn-warning:active, 17 | .btn-danger:active, 18 | .btn-default.active, 19 | .btn-primary.active, 20 | .btn-success.active, 21 | .btn-info.active, 22 | .btn-warning.active, 23 | .btn-danger.active { 24 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 25 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 26 | } 27 | 28 | .btn:active, 29 | .btn.active { 30 | background-image: none; 31 | } 32 | 33 | .btn-default { 34 | text-shadow: 0 1px 0 #fff; 35 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); 36 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); 37 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); 38 | background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); 39 | background-repeat: repeat-x; 40 | border-color: #e0e0e0; 41 | border-color: #ccc; 42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); 43 | } 44 | 45 | .btn-default:active, 46 | .btn-default.active { 47 | background-color: #e6e6e6; 48 | border-color: #e0e0e0; 49 | } 50 | 51 | .btn-primary { 52 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 53 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 54 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 55 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 56 | background-repeat: repeat-x; 57 | border-color: #2d6ca2; 58 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 59 | } 60 | 61 | .btn-primary:active, 62 | .btn-primary.active { 63 | background-color: #3071a9; 64 | border-color: #2d6ca2; 65 | } 66 | 67 | .btn-success { 68 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 69 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 70 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 71 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 72 | background-repeat: repeat-x; 73 | border-color: #419641; 74 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 75 | } 76 | 77 | .btn-success:active, 78 | .btn-success.active { 79 | background-color: #449d44; 80 | border-color: #419641; 81 | } 82 | 83 | .btn-warning { 84 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 85 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 86 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 87 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 88 | background-repeat: repeat-x; 89 | border-color: #eb9316; 90 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 91 | } 92 | 93 | .btn-warning:active, 94 | .btn-warning.active { 95 | background-color: #ec971f; 96 | border-color: #eb9316; 97 | } 98 | 99 | .btn-danger { 100 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 101 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 102 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 103 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 104 | background-repeat: repeat-x; 105 | border-color: #c12e2a; 106 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 107 | } 108 | 109 | .btn-danger:active, 110 | .btn-danger.active { 111 | background-color: #c9302c; 112 | border-color: #c12e2a; 113 | } 114 | 115 | .btn-info { 116 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 117 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 118 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 119 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 120 | background-repeat: repeat-x; 121 | border-color: #2aabd2; 122 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 123 | } 124 | 125 | .btn-info:active, 126 | .btn-info.active { 127 | background-color: #31b0d5; 128 | border-color: #2aabd2; 129 | } 130 | 131 | .thumbnail, 132 | .img-thumbnail { 133 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 134 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 135 | } 136 | 137 | .dropdown-menu > li > a:hover, 138 | .dropdown-menu > li > a:focus, 139 | .dropdown-menu > .active > a, 140 | .dropdown-menu > .active > a:hover, 141 | .dropdown-menu > .active > a:focus { 142 | background-color: #357ebd; 143 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 144 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 145 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 146 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 147 | background-repeat: repeat-x; 148 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 149 | } 150 | 151 | .navbar { 152 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 153 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); 154 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 155 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 156 | background-repeat: repeat-x; 157 | border-radius: 4px; 158 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 159 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 160 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 161 | } 162 | 163 | .navbar .navbar-nav > .active > a { 164 | background-color: #f8f8f8; 165 | } 166 | 167 | .navbar-brand, 168 | .navbar-nav > li > a { 169 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 170 | } 171 | 172 | .navbar-inverse { 173 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 174 | background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); 175 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 176 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 177 | background-repeat: repeat-x; 178 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 179 | } 180 | 181 | .navbar-inverse .navbar-nav > .active > a { 182 | background-color: #222222; 183 | } 184 | 185 | .navbar-inverse .navbar-brand, 186 | .navbar-inverse .navbar-nav > li > a { 187 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 188 | } 189 | 190 | .navbar-static-top, 191 | .navbar-fixed-top, 192 | .navbar-fixed-bottom { 193 | border-radius: 0; 194 | } 195 | 196 | .alert { 197 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 198 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 199 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 200 | } 201 | 202 | .alert-success { 203 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 204 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); 205 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 206 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 207 | background-repeat: repeat-x; 208 | border-color: #b2dba1; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 210 | } 211 | 212 | .alert-info { 213 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 214 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); 215 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 216 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 217 | background-repeat: repeat-x; 218 | border-color: #9acfea; 219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 220 | } 221 | 222 | .alert-warning { 223 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 224 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); 225 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 226 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 227 | background-repeat: repeat-x; 228 | border-color: #f5e79e; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 230 | } 231 | 232 | .alert-danger { 233 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 234 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); 235 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 236 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 237 | background-repeat: repeat-x; 238 | border-color: #dca7a7; 239 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 240 | } 241 | 242 | .progress { 243 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 244 | background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); 245 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 246 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 247 | background-repeat: repeat-x; 248 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 249 | } 250 | 251 | .progress-bar { 252 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 253 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 254 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 256 | background-repeat: repeat-x; 257 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 258 | } 259 | 260 | .progress-bar-success { 261 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 262 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 263 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 264 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 265 | background-repeat: repeat-x; 266 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 267 | } 268 | 269 | .progress-bar-info { 270 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 271 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 272 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 273 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 274 | background-repeat: repeat-x; 275 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 276 | } 277 | 278 | .progress-bar-warning { 279 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 280 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 281 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 282 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 283 | background-repeat: repeat-x; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 285 | } 286 | 287 | .progress-bar-danger { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 289 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 290 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 291 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 292 | background-repeat: repeat-x; 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 294 | } 295 | 296 | .list-group { 297 | border-radius: 4px; 298 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 299 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 300 | } 301 | 302 | .list-group-item.active, 303 | .list-group-item.active:hover, 304 | .list-group-item.active:focus { 305 | text-shadow: 0 -1px 0 #3071a9; 306 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 307 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); 308 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 309 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 310 | background-repeat: repeat-x; 311 | border-color: #3278b3; 312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 313 | } 314 | 315 | .panel { 316 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 317 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 318 | } 319 | 320 | .panel-default > .panel-heading { 321 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 322 | background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); 323 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 324 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 325 | background-repeat: repeat-x; 326 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 327 | } 328 | 329 | .panel-primary > .panel-heading { 330 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 331 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 332 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 333 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 334 | background-repeat: repeat-x; 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 336 | } 337 | 338 | .panel-success > .panel-heading { 339 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 340 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); 341 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 342 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 343 | background-repeat: repeat-x; 344 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 345 | } 346 | 347 | .panel-info > .panel-heading { 348 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 349 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); 350 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 351 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 352 | background-repeat: repeat-x; 353 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 354 | } 355 | 356 | .panel-warning > .panel-heading { 357 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 358 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); 359 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 360 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 361 | background-repeat: repeat-x; 362 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 363 | } 364 | 365 | .panel-danger > .panel-heading { 366 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 367 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); 368 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 369 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 370 | background-repeat: repeat-x; 371 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 372 | } 373 | 374 | .well { 375 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 376 | background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); 377 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 378 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 379 | background-repeat: repeat-x; 380 | border-color: #dcdcdc; 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 382 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 383 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 384 | } -------------------------------------------------------------------------------- /app/lib/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0%,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0)}.btn-default:active,.btn-default.active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.btn-primary:active,.btn-primary.active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.btn-success:active,.btn-success.active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.btn-warning:active,.btn-warning.active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.btn-danger:active,.btn-danger.active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.btn-info:active,.btn-info.active{background-color:#31b0d5;border-color:#2aabd2}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0%,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0%,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0%,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0%,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0%,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0%,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /app/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/app/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/app/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/app/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/lib/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap.js v3.0.0 by @fat and @mdo 3 | * Copyright 2013 Twitter Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /app/partials/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/app/partials/.gitkeep -------------------------------------------------------------------------------- /app/partials/partial1.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 1.

2 | -------------------------------------------------------------------------------- /app/partials/partial2.html: -------------------------------------------------------------------------------- 1 |

This is the partial for view 2.

2 |

3 | Showing of 'interpolate' filter: 4 | {{ 'Current version is v%VERSION%.' | interpolate }} 5 |

6 | -------------------------------------------------------------------------------- /app/partials/stars.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  1. 4 |
    5 |

    >

    6 | 11 | 12 |
    13 |
  2. 14 |
15 |
16 |
    17 |
  • 18 | « 19 | « 20 |
  • 21 |
  • 22 |
  • 23 | » 24 | » 25 |
  • 26 |
27 |
28 | 29 |
30 | -------------------------------------------------------------------------------- /config/karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | 4 | 5 | basePath : '../', 6 | 7 | files : [ 8 | 'test/e2e/**/*.js' 9 | ], 10 | 11 | autoWatch : false, 12 | 13 | browsers : ['Chrome'], 14 | 15 | frameworks: ['ng-scenario'], 16 | 17 | singleRun : true, 18 | 19 | proxies : { 20 | '/': 'http://localhost:8000/' 21 | }, 22 | 23 | plugins : [ 24 | 'karma-junit-reporter', 25 | 'karma-chrome-launcher', 26 | 'karma-firefox-launcher', 27 | 'karma-jasmine', 28 | 'karma-ng-scenario' 29 | ], 30 | 31 | junitReporter : { 32 | outputFile: 'test_out/e2e.xml', 33 | suite: 'e2e' 34 | } 35 | 36 | })} 37 | 38 | -------------------------------------------------------------------------------- /config/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config){ 2 | config.set({ 3 | basePath : '../', 4 | 5 | files : [ 6 | 'app/lib/angular/angular.js', 7 | 'app/lib/angular/angular-*.js', 8 | 'test/lib/angular/angular-mocks.js', 9 | 'app/js/**/*.js', 10 | 'test/unit/**/*.js' 11 | ], 12 | 13 | autoWatch : true, 14 | 15 | frameworks: ['jasmine'], 16 | 17 | browsers : ['Chrome'], 18 | 19 | plugins : [ 20 | 'karma-junit-reporter', 21 | 'karma-chrome-launcher', 22 | 'karma-firefox-launcher', 23 | 'karma-jasmine' 24 | ], 25 | 26 | junitReporter : { 27 | outputFile: 'test_out/unit.xml', 28 | suite: 'unit' 29 | } 30 | 31 | })} 32 | -------------------------------------------------------------------------------- /feature.md: -------------------------------------------------------------------------------- 1 | # Gstar 2 | 当我在github上star过的项目超过600个时,我觉得有必要写个工具来帮助我管理这些项目了。 3 | 原因很简单github提供的搜索功能过于鸡肋,大部分的时候我都搜索不到我想要找的项目,比如我想 4 | 搜索ruby的markdown库,通过markdown这个单词我只能搜索到一个叫miclle/Markdown-Editor 5 | 的项目,原因是github只允许通过项目名搜索。使用Gstar,我们可以通过项目名,项目描述搜索代码, 6 | 并且可以根据自己对项目的的理解修改项目描述,这样可以帮助我们更加快速地找到自己需要的代码。 7 | 8 | ## 从github拉项目 9 | 10 | ## angularJS对搜索的支持,类似google搜索的那种页面渲染的效果 11 | 用户在搜索框输入要搜索的关键字,然后点击搜索,页面不需要刷新就能显示出搜索结果 12 | ### 算法 13 | 1.参考资料 14 | - [ngSubmit](http://docs.angularjs.org/api/ng.directive:ngSubmit) 15 | - [AngulaJS search](https://github.com/daha/angularJS-github-contributors) 16 | ## 将项目以列表的方式在页面上展示 17 | 1. 显示项目名 18 | 2. 项目名是一个链接,指向项目在github上的代码库 19 | 3. 显示项目描述 20 | 21 | -------------------------------------------------------------------------------- /ground/Gemfile: -------------------------------------------------------------------------------- 1 | #source 'https://rubygems.org' 2 | source 'http://ruby.taobao.org' 3 | 4 | gem 'rake' 5 | gem 'dun' 6 | gem 'rack' 7 | gem 'json' 8 | gem 'rest-client' 9 | gem 'sequel' 10 | gem 'sqlite3' 11 | gem 'whenever', require: false 12 | gem 'ground', git: 'git://github.com/baya/ground.git', ref: 'bd4f89f0073' 13 | -------------------------------------------------------------------------------- /ground/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/baya/ground.git 3 | revision: bd4f89f0073ccdd71efa518ef847e31320333380 4 | ref: bd4f89f0073 5 | specs: 6 | ground (0.0.1) 7 | dun 8 | rack 9 | 10 | GEM 11 | remote: http://ruby.taobao.org/ 12 | specs: 13 | activesupport (4.0.0) 14 | i18n (~> 0.6, >= 0.6.4) 15 | minitest (~> 4.2) 16 | multi_json (~> 1.3) 17 | thread_safe (~> 0.1) 18 | tzinfo (~> 0.3.37) 19 | atomic (1.1.14) 20 | chronic (0.10.2) 21 | dun (0.1.1) 22 | i18n (0.6.5) 23 | json (1.8.0) 24 | mime-types (1.25) 25 | minitest (4.7.5) 26 | multi_json (1.8.0) 27 | rack (1.5.2) 28 | rake (10.1.0) 29 | rest-client (1.6.7) 30 | mime-types (>= 1.16) 31 | sequel (4.1.1) 32 | sqlite3 (1.3.7) 33 | thread_safe (0.1.3) 34 | atomic 35 | tzinfo (0.3.37) 36 | whenever (0.8.4) 37 | activesupport (>= 2.3.4) 38 | chronic (>= 0.6.3) 39 | 40 | PLATFORMS 41 | ruby 42 | 43 | DEPENDENCIES 44 | dun 45 | ground! 46 | json 47 | rack 48 | rake 49 | rest-client 50 | sequel 51 | sqlite3 52 | whenever 53 | -------------------------------------------------------------------------------- /ground/Rakefile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | task :env do 3 | $: << '.' unless $:.include? '.' 4 | require 'gstar' 5 | require 'pp' 6 | require 'base64' 7 | end 8 | 9 | desc 'Run Console' 10 | task :console => :env do |t, args| 11 | puts "Loading #{Ground.env} environment" 12 | require "irb" 13 | require 'irb/completion' 14 | ARGV.clear 15 | IRB.start 16 | end 17 | 18 | namespace :maintain do 19 | desc 'pull stars from github' 20 | task :pull_stars_from_github => :env do 21 | page = 0 22 | total_size = 0 23 | loop do 24 | page += 1 25 | res = GithubAPI::ListStarredRepos(page: page) 26 | stars = JSON.parse res 27 | total_size += stars.size 28 | stars.each {|star| 29 | pulled_star = Gstar::DB[:stars].where(source_id: "github_#{star['id']}").first 30 | if pulled_star.nil? 31 | star = InsertStarFromGithub star 32 | BuildInvertedStarIndex star: star 33 | end 34 | } 35 | break if stars.size == 0 36 | puts "已经同步#{total_size}个项目..." 37 | end 38 | end 39 | 40 | desc 'sync stars from github, just recently starred projects' 41 | task :sync_stars_from_github => :env do 42 | Ground.logger.info "--Start sync stars from github--#{Time.now}---" 43 | res = GithubAPI::ListStarredRepos() 44 | stars = JSON.parse res 45 | stars.each {|star| 46 | pulled_star = Gstar::DB[:stars].where(source_id: "github_#{star['id']}").first 47 | if pulled_star.nil? 48 | Ground.logger.info "sync the star #{star['full_name']}..." 49 | star = InsertStarFromGithub star 50 | BuildInvertedStarIndex star: star 51 | end 52 | } 53 | Ground.logger.info "--End sync stars from github--#{Time.now}---" 54 | end 55 | 56 | desc 'build full text serach index' 57 | task :build_full_text_index => :env do 58 | db = Gstar::DB 59 | db[:stars].each {|star| 60 | db[:reindex_stars].insert(star_id: star[:id], status: 0) 61 | } 62 | end 63 | 64 | end 65 | 66 | namespace :debug do 67 | task :list_starred_repos => :env do 68 | res = GithubAPI::ListStarredRepos() 69 | res = JSON.parse res 70 | pp res 71 | pp res.size 72 | end 73 | end 74 | 75 | namespace :db do 76 | task :migrate => :env do 77 | Sequel.extension :migration, :core_extensions 78 | Sequel::Migrator.run Gstar::DB, 'db/migrations', use_transactions: true 79 | end 80 | end 81 | 82 | namespace :cron do 83 | task :check_latest_stars => :env do 84 | db = Gstar::DB 85 | Ground.logger.info "--Start check latest stars from github--#{Time.now}---" 86 | if need_sync_stars? 87 | res = GithubAPI::ListStarredRepos(per_page: 100) 88 | stars = JSON.parse res 89 | stars.each {|star| 90 | pulled_star = db[:stars].where(source_id: "github_#{star['id']}").first 91 | if pulled_star.nil? 92 | Ground.logger.info "sync the star #{star['full_name']}..." 93 | star = InsertStarFromGithub star 94 | BuildInvertedStarIndex star: star 95 | end 96 | } 97 | end 98 | Ground.logger.info "--End check latest stars from github--#{Time.now}---" 99 | end 100 | 101 | def need_sync_stars? 102 | res = GithubAPI::ListStarredRepos(per_page: 1) 103 | stars = JSON.parse res 104 | latest_star = stars.first 105 | star = Gstar::DB[:stars].where(source_id: "github_#{latest_star['id']}").first 106 | star.nil? 107 | end 108 | 109 | end 110 | 111 | -------------------------------------------------------------------------------- /ground/activities/accu_q_star_assoc.rb: -------------------------------------------------------------------------------- 1 | class AccQStarAssoc < Dun::Activity 2 | data_reader :q, :star 3 | 4 | set :db, Ground.db 5 | 6 | def call 7 | assoc = db[:q_star_assocs].where(q: q, star_id: star[:id]).first 8 | score = star[:tf].to_i 9 | score = 1 if score == 0 10 | if assoc 11 | acc_assoc_score assoc, score 12 | else 13 | db[:q_star_assocs].insert(q: q, star_id: star[:id], score: score) 14 | end 15 | end 16 | 17 | private 18 | 19 | def acc_assoc_score(assoc, score) 20 | db[:q_star_assocs].where(id: assoc[:id]).update(score: (assoc[:score] + score) * 2) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /ground/activities/build_inverted_star_index.rb: -------------------------------------------------------------------------------- 1 | class BuildInvertedStarIndex < Dun::Activity 2 | data_reader :star 3 | 4 | set :dics_set, Ground.db[:dics] 5 | set :stars_set, Ground.db[:stars] 6 | set :dic_star_set, Ground.db[:dic_stars] 7 | 8 | def call 9 | text = "#{star[:full_name]}\n#{star[:description]}" 10 | index_text text 11 | index_text text.downcase 12 | end 13 | 14 | def index_text(text) 15 | words = text.scan(/\w+/) 16 | words.each {|word| 17 | dic = dics_set.where(word: word).first 18 | if dic.nil? 19 | dic_id = dics_set.insert word: word 20 | else 21 | dic_id = dic[:id] 22 | end 23 | 24 | dic_star = dic_star_set.where(dic_id: dic_id, star_id: star[:id]).first 25 | tf = words.count(word) 26 | if dic_star.nil? 27 | dic_star_set.insert(dic_id: dic_id, star_id: star[:id], tf: tf) 28 | else 29 | dic_star_set.where(dic_id: dic_id, star_id: star[:id]).update(tf: tf) 30 | end 31 | } 32 | 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /ground/activities/insert_star_from_github.rb: -------------------------------------------------------------------------------- 1 | class InsertStarFromGithub < Dun::Activity 2 | data_reader :name, :full_name, :description, :html_url 3 | data_reader :created_at, :updated_at 4 | data_reader :id 5 | 6 | set :source, 'github' 7 | set :data_set, Ground.db[:stars] 8 | 9 | def call 10 | star_id = data_set.insert star 11 | star.merge(id: star_id) 12 | end 13 | 14 | private 15 | 16 | def star 17 | @star ||= { 18 | name: name, 19 | full_name: full_name, 20 | description: description, 21 | html_url: html_url, 22 | source: source, 23 | source_id: "#{source}_#{id}", 24 | source_created_at: created_at, 25 | source_updated_at: updated_at, 26 | created_at: DateTime.now, 27 | updated_at: DateTime.now 28 | } 29 | end 30 | 31 | end 32 | -------------------------------------------------------------------------------- /ground/activities/query_stars_by_word.rb: -------------------------------------------------------------------------------- 1 | class QueryStarsByWord < Dun::Activity 2 | data_reader :word 3 | 4 | set :db, Ground.db 5 | 6 | def call 7 | dic = db[:dics].where(word: word).first 8 | if dic 9 | db[sql, dic[:id]] 10 | else 11 | db[:stars].grep([:name, :description], "%#{word}%") 12 | end 13 | end 14 | 15 | private 16 | 17 | def sql 18 | sql = <<-EOF 19 | select stars.full_name, stars.description, stars.html_url, dic_stars.tf, dics.word, stars.id 20 | from stars 21 | join dic_stars on stars.id = dic_stars.star_id 22 | join dics on dic_stars.dic_id = dics.id 23 | where dics.id = ? 24 | EOF 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /ground/activities/search_stars.rb: -------------------------------------------------------------------------------- 1 | class SearchStars < Dun::Activity 2 | data_reader :q 3 | 4 | set :db, Ground.db 5 | 6 | def call 7 | db[sql, q] 8 | end 9 | 10 | private 11 | 12 | def sql 13 | sql = <<-EOF 14 | select stars.id, stars.full_name, stars.description, stars.html_url, q_star_assocs.score 15 | from stars 16 | join q_star_assocs on stars.id = q_star_assocs.star_id 17 | where q_star_assocs.q = ? 18 | order by q_star_assocs.score desc 19 | EOF 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /ground/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | current_dir = File.expand_path(File.dirname(__FILE__)) 3 | $: << current_dir 4 | 5 | require 'gstar' 6 | run Gstar::App 7 | -------------------------------------------------------------------------------- /ground/config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite 3 | database: db/development.sqlite3 4 | pool: 5 5 | timeout: 5000 6 | 7 | test: 8 | adapter: sqlite 9 | database: db/test.sqlite3 10 | pool: 5 11 | timeout: 5000 12 | 13 | production: 14 | adapter: sqlite 15 | database: db/production.sqlite3 16 | pool: 5 17 | timeout: 5000 18 | -------------------------------------------------------------------------------- /ground/config/github.example.yml: -------------------------------------------------------------------------------- 1 | access_token: 'your github token' 2 | login: 'your github login name' 3 | password: 'your github login password' -------------------------------------------------------------------------------- /ground/config/routes.rb: -------------------------------------------------------------------------------- 1 | Ground do 2 | get '/', Home 3 | get '/stars', Stars::Index 4 | post '/saveStarDescription', Star::SaveDescription 5 | end 6 | -------------------------------------------------------------------------------- /ground/config/schedule.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | job_type :rake_utf8, "export LANG=en_US.UTF-8 && cd :path && :environment_variable=:environment bundle exec rake :task --silent :output" 4 | 5 | set :output, File.expand_path(File.join(File.dirname(__FILE__), '../', 'logs', "cron_log.log")) 6 | every 1.minutes do 7 | rake_utf8 "cron:check_latest_stars" 8 | end 9 | -------------------------------------------------------------------------------- /ground/config/sets.rb: -------------------------------------------------------------------------------- 1 | Ground do 2 | set :root, File.expand_path(File.dirname(File.dirname(File.dirname(__FILE__)))) 3 | set :env, (ENV['RACK_ENV'] || 'development').to_s 4 | db_info = YAML.load_file(File.join(root, 'ground/config', 'database.yml'))[env] 5 | db_info['database'] = File.expand_path(File.join(root, 'ground', db_info['database'])) 6 | set :db_info, db_info 7 | set :db, Sequel.connect(db_info) 8 | set :log_file, File.join(root, "ground/logs", "#{env}.log") 9 | set :logger, ::Logger.new(log_file) 10 | end 11 | -------------------------------------------------------------------------------- /ground/db/migrations/001_create_stars.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | up do 3 | create_table(:stars) do 4 | primary_key :id 5 | String :name 6 | String :full_name 7 | String :html_url 8 | String :description, text: true 9 | DateTime :source_created_at 10 | DateTime :source_updated_at 11 | DateTime :created_at 12 | DateTime :updated_at 13 | String :source 14 | String :source_id 15 | end 16 | end 17 | 18 | down do 19 | drop_table(:stars) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /ground/db/migrations/002_create_words.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | up do 3 | create_table(:dics) do 4 | primary_key :id 5 | String :word 6 | end 7 | end 8 | 9 | down do 10 | drop_table(:dics) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /ground/db/migrations/003_create_word_stars.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | Sequel.migration do 3 | up do 4 | create_table(:dic_stars) do 5 | primary_key :id 6 | Integer :dic_id 7 | Integer :star_id 8 | Integer :tf # 单词在文档中出现的频率 9 | end 10 | end 11 | 12 | down do 13 | drop_table(:dic_stars) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /ground/db/migrations/004_create_q_star_assos.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | up do 3 | create_table(:q_star_assocs) do 4 | primary_key :id 5 | Integer :star_id 6 | Integer :score 7 | String :q 8 | end 9 | end 10 | 11 | down do 12 | drop_table(:q_star_assocs) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ground/db/migrations/005_create_reindex_stars.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | Sequel.migration do 3 | up do 4 | create_table(:reindex_stars) do 5 | primary_key :id 6 | Integer :star_id 7 | Integer :status # 0 defaul, 1 已经完成索引 8 | end 9 | end 10 | 11 | down do 12 | drop_table(:reindex_stars) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ground/db/migrations/006_add_index.rb: -------------------------------------------------------------------------------- 1 | Sequel.migration do 2 | up do 3 | alter_table(:stars) do 4 | add_index :source_id 5 | add_index :name 6 | add_index :description 7 | end 8 | 9 | alter_table(:reindex_stars) do 10 | add_index :status 11 | end 12 | 13 | alter_table(:q_star_assocs) do 14 | add_index :q 15 | add_index :star_id 16 | add_index :score 17 | add_index [:q, :star_id] 18 | end 19 | 20 | alter_table(:dic_stars) do 21 | add_index :star_id 22 | add_index :dic_id 23 | end 24 | 25 | alter_table(:dics) do 26 | add_index :word 27 | end 28 | 29 | end 30 | 31 | down do 32 | alter_table(:stars) do 33 | drop_index :source_id 34 | drop_index :name 35 | drop_index :description 36 | end 37 | 38 | alter_table(:reindex_stars) do 39 | drop_index :status 40 | end 41 | 42 | alter_table(:q_star_assocs) do 43 | drop_index :q 44 | drop_index :star_id 45 | drop_index :score 46 | drop_index [:q, :star_id] 47 | end 48 | 49 | alter_table(:dic_stars) do 50 | drop_index :star_id 51 | drop_index :dic_id 52 | end 53 | 54 | alter_table(:dics) do 55 | drop_index :word 56 | end 57 | 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /ground/gstar.rb: -------------------------------------------------------------------------------- 1 | # ground_lib = '/Users/jim/Projects/ground/lib' 2 | # ground_lib = '/Users/jiangguimin/Projects/ground/lib' 3 | # $: << ground_lib 4 | 5 | require 'bundler' 6 | Bundler.require(:default) 7 | require 'ground' 8 | require 'logger' 9 | require 'yaml' 10 | 11 | require 'config/sets' 12 | require 'states/home' 13 | require 'states/stars/index' 14 | require 'states/star/save_description' 15 | require 'activities/insert_star_from_github' 16 | require 'activities/build_inverted_star_index' 17 | require 'activities/search_stars' 18 | require 'activities/accu_q_star_assoc' 19 | require 'activities/query_stars_by_word' 20 | require 'lib/github_api' 21 | require 'lib/github_api/list_starred_repos' 22 | require 'config/routes' 23 | 24 | module Gstar 25 | DB = Ground.db 26 | DB.loggers << Ground.logger 27 | App = Ground 'gStar' do 28 | use Rack::ShowExceptions 29 | use Rack::Static, urls: ['/js', '/css', '/lib', '/partials', '/img'], root: "#{Ground.root}/app" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /ground/lib/github_api.rb: -------------------------------------------------------------------------------- 1 | module GithubAPI 2 | CONFIG = YAML.load_file("#{Ground.root}/ground/config/github.yml") 3 | 4 | def self.included(base) 5 | base.class_eval do 6 | set :base_uri, 'https://api.github.com' 7 | set :access_token, CONFIG['access_token'] 8 | set :login, CONFIG['login'] 9 | set :password, CONFIG['password'] 10 | set :user, CONFIG['login'] 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /ground/lib/github_api/list_starred_repos.rb: -------------------------------------------------------------------------------- 1 | module GithubAPI 2 | class ListStarredRepos < Dun::Activity 3 | include GithubAPI 4 | 5 | data_reader :page, :per_page 6 | 7 | def initialize(data) 8 | super 9 | default :page, 1 10 | default :per_page, 100 11 | end 12 | 13 | def call 14 | api_get("/users/#{user}/starred?page=#{page}&per_page=#{per_page}") 15 | end 16 | 17 | private 18 | 19 | def api_get(path) 20 | url = "#{base_uri}#{path}" 21 | if access_token 22 | auth = "token #{access_token}" 23 | else 24 | auth = "basic " + Base64.encode64("#{login}:#{password}") 25 | end 26 | RestClient.get url, Authorization: auth 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ground/logs/keep.txt: -------------------------------------------------------------------------------- 1 | in order to save the logs dir 2 | -------------------------------------------------------------------------------- /ground/states/home.rb: -------------------------------------------------------------------------------- 1 | class Home < Ground::State 2 | 3 | def call 4 | html plain('app/index.html') 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /ground/states/star/save_description.rb: -------------------------------------------------------------------------------- 1 | module Star 2 | class SaveDescription < Ground::State 3 | set :db, Ground.db 4 | 5 | def call 6 | db[:stars].where(id: request_payload['id']).update(description: request_payload['description']) 7 | star = db[:stars].where(id: request_payload['id']).first 8 | BuildInvertedStarIndex star: star 9 | json star.to_json 10 | end 11 | 12 | private 13 | 14 | def request_payload 15 | get_or_set :request_payload do 16 | request.body.rewind 17 | JSON.parse request.body.read 18 | end 19 | end 20 | 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ground/states/stars/index.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | module Stars 3 | class Index < Ground::State 4 | 5 | set :db, Ground.db 6 | set :limit, 10 7 | 8 | def call 9 | scan_q_for_search params[:q] 10 | stars = find_stars params[:q] 11 | start = params[:start].to_i 12 | total = stars.count 13 | stars = stars.limit(limit, start).all 14 | clear_q_star_assocs params[:q] 15 | json({stars: stars, limit: limit, start: start, total: total, total: total}.to_json) 16 | end 17 | 18 | private 19 | 20 | def find_stars(q) 21 | SearchStars q: q 22 | end 23 | 24 | def scan_q_for_search(q) 25 | q.scan(/\p{Han}+|\w+/).each_with_index {|word, index| 26 | stars = QueryStarsByWord(word: word) 27 | stars.each {|star| 28 | AccQStarAssoc(star: star, q: q) 29 | } 30 | } 31 | end 32 | 33 | def clear_q_star_assocs(q) 34 | db[:q_star_assocs].where(q: q).delete 35 | end 36 | 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /ground/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/ground/tmp/.gitkeep -------------------------------------------------------------------------------- /images/Snip20131003_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/images/Snip20131003_1.png -------------------------------------------------------------------------------- /images/Snip20131003_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/images/Snip20131003_2.png -------------------------------------------------------------------------------- /images/Snip20131004_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/images/Snip20131004_3.png -------------------------------------------------------------------------------- /images/Snip20131004_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baya/Gstar/b8d07c7ffc7dd86a2a43f041c8994999ae4c3439/images/Snip20131004_5.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angualrjs-seed", 3 | "description": "A starter project for angular js", 4 | "devDependencies": { 5 | "phantomjs" : "*", 6 | "karma" : "*", 7 | "karma-junit-reporter" : "*", 8 | "karma-jasmine" : "*", 9 | "karma-ng-scenario" : "*" 10 | } 11 | } -------------------------------------------------------------------------------- /scripts/e2e-test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Windows script for running e2e tests 4 | REM You have to run server and capture some browser first 5 | REM 6 | REM Requirements: 7 | REM - NodeJS (http://nodejs.org/) 8 | REM - Karma (npm install -g karma) 9 | 10 | set BASE_DIR=%~dp0 11 | karma start "%BASE_DIR%\..\config\karma-e2e.conf.js" %* 12 | -------------------------------------------------------------------------------- /scripts/e2e-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | echo "" 6 | echo "Starting Karma Server (http://karma-runner.github.io)" 7 | echo "-------------------------------------------------------------------" 8 | 9 | karma start $BASE_DIR/../config/karma-e2e.conf.js $* 10 | -------------------------------------------------------------------------------- /scripts/test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Windows script for running unit tests 4 | REM You have to run server and capture some browser first 5 | REM 6 | REM Requirements: 7 | REM - NodeJS (http://nodejs.org/) 8 | REM - Karma (npm install -g karma) 9 | 10 | set BASE_DIR=%~dp0 11 | karma start "%BASE_DIR%\..\config\karma.conf.js" %* 12 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASE_DIR=`dirname $0` 4 | 5 | echo "" 6 | echo "Starting Karma Server (http://karma-runner.github.io)" 7 | echo "-------------------------------------------------------------------" 8 | 9 | karma start $BASE_DIR/../config/karma.conf.js $* 10 | -------------------------------------------------------------------------------- /scripts/watchr.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env watchr 2 | 3 | # config file for watchr http://github.com/mynyml/watchr 4 | # install: gem install watchr 5 | # run: watch watchr.rb 6 | # note: make sure that you have jstd server running (server.sh) and a browser captured 7 | 8 | log_file = File.expand_path(File.dirname(__FILE__) + '/../logs/jstd.log') 9 | 10 | `cd ..` 11 | `touch #{log_file}` 12 | 13 | puts "String watchr... log file: #{log_file}" 14 | 15 | watch( '(app/js|test/unit)' ) do 16 | `echo "\n\ntest run started @ \`date\`" > #{log_file}` 17 | `scripts/test.sh &> #{log_file}` 18 | end 19 | 20 | -------------------------------------------------------------------------------- /scripts/web-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var util = require('util'), 4 | http = require('http'), 5 | fs = require('fs'), 6 | url = require('url'), 7 | events = require('events'); 8 | 9 | var DEFAULT_PORT = 8000; 10 | 11 | function main(argv) { 12 | new HttpServer({ 13 | 'GET': createServlet(StaticServlet), 14 | 'HEAD': createServlet(StaticServlet) 15 | }).start(Number(argv[2]) || DEFAULT_PORT); 16 | } 17 | 18 | function escapeHtml(value) { 19 | return value.toString(). 20 | replace('<', '<'). 21 | replace('>', '>'). 22 | replace('"', '"'); 23 | } 24 | 25 | function createServlet(Class) { 26 | var servlet = new Class(); 27 | return servlet.handleRequest.bind(servlet); 28 | } 29 | 30 | /** 31 | * An Http server implementation that uses a map of methods to decide 32 | * action routing. 33 | * 34 | * @param {Object} Map of method => Handler function 35 | */ 36 | function HttpServer(handlers) { 37 | this.handlers = handlers; 38 | this.server = http.createServer(this.handleRequest_.bind(this)); 39 | } 40 | 41 | HttpServer.prototype.start = function(port) { 42 | this.port = port; 43 | this.server.listen(port); 44 | util.puts('Http Server running at http://localhost:' + port + '/'); 45 | }; 46 | 47 | HttpServer.prototype.parseUrl_ = function(urlString) { 48 | var parsed = url.parse(urlString); 49 | parsed.pathname = url.resolve('/', parsed.pathname); 50 | return url.parse(url.format(parsed), true); 51 | }; 52 | 53 | HttpServer.prototype.handleRequest_ = function(req, res) { 54 | var logEntry = req.method + ' ' + req.url; 55 | if (req.headers['user-agent']) { 56 | logEntry += ' ' + req.headers['user-agent']; 57 | } 58 | util.puts(logEntry); 59 | req.url = this.parseUrl_(req.url); 60 | var handler = this.handlers[req.method]; 61 | if (!handler) { 62 | res.writeHead(501); 63 | res.end(); 64 | } else { 65 | handler.call(this, req, res); 66 | } 67 | }; 68 | 69 | /** 70 | * Handles static content. 71 | */ 72 | function StaticServlet() {} 73 | 74 | StaticServlet.MimeMap = { 75 | 'txt': 'text/plain', 76 | 'html': 'text/html', 77 | 'css': 'text/css', 78 | 'xml': 'application/xml', 79 | 'json': 'application/json', 80 | 'js': 'application/javascript', 81 | 'jpg': 'image/jpeg', 82 | 'jpeg': 'image/jpeg', 83 | 'gif': 'image/gif', 84 | 'png': 'image/png', 85 |   'svg': 'image/svg+xml' 86 | }; 87 | 88 | StaticServlet.prototype.handleRequest = function(req, res) { 89 | var self = this; 90 | var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){ 91 | return String.fromCharCode(parseInt(hex, 16)); 92 | }); 93 | var parts = path.split('/'); 94 | if (parts[parts.length-1].charAt(0) === '.') 95 | return self.sendForbidden_(req, res, path); 96 | fs.stat(path, function(err, stat) { 97 | if (err) 98 | return self.sendMissing_(req, res, path); 99 | if (stat.isDirectory()) 100 | return self.sendDirectory_(req, res, path); 101 | return self.sendFile_(req, res, path); 102 | }); 103 | } 104 | 105 | StaticServlet.prototype.sendError_ = function(req, res, error) { 106 | res.writeHead(500, { 107 | 'Content-Type': 'text/html' 108 | }); 109 | res.write('\n'); 110 | res.write('Internal Server Error\n'); 111 | res.write('

Internal Server Error

'); 112 | res.write('
' + escapeHtml(util.inspect(error)) + '
'); 113 | util.puts('500 Internal Server Error'); 114 | util.puts(util.inspect(error)); 115 | }; 116 | 117 | StaticServlet.prototype.sendMissing_ = function(req, res, path) { 118 | path = path.substring(1); 119 | res.writeHead(404, { 120 | 'Content-Type': 'text/html' 121 | }); 122 | res.write('\n'); 123 | res.write('404 Not Found\n'); 124 | res.write('

Not Found

'); 125 | res.write( 126 | '

The requested URL ' + 127 | escapeHtml(path) + 128 | ' was not found on this server.

' 129 | ); 130 | res.end(); 131 | util.puts('404 Not Found: ' + path); 132 | }; 133 | 134 | StaticServlet.prototype.sendForbidden_ = function(req, res, path) { 135 | path = path.substring(1); 136 | res.writeHead(403, { 137 | 'Content-Type': 'text/html' 138 | }); 139 | res.write('\n'); 140 | res.write('403 Forbidden\n'); 141 | res.write('

Forbidden

'); 142 | res.write( 143 | '

You do not have permission to access ' + 144 | escapeHtml(path) + ' on this server.

' 145 | ); 146 | res.end(); 147 | util.puts('403 Forbidden: ' + path); 148 | }; 149 | 150 | StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) { 151 | res.writeHead(301, { 152 | 'Content-Type': 'text/html', 153 | 'Location': redirectUrl 154 | }); 155 | res.write('\n'); 156 | res.write('301 Moved Permanently\n'); 157 | res.write('

Moved Permanently

'); 158 | res.write( 159 | '

The document has moved here.

' 162 | ); 163 | res.end(); 164 | util.puts('301 Moved Permanently: ' + redirectUrl); 165 | }; 166 | 167 | StaticServlet.prototype.sendFile_ = function(req, res, path) { 168 | var self = this; 169 | var file = fs.createReadStream(path); 170 | res.writeHead(200, { 171 | 'Content-Type': StaticServlet. 172 | MimeMap[path.split('.').pop()] || 'text/plain' 173 | }); 174 | if (req.method === 'HEAD') { 175 | res.end(); 176 | } else { 177 | file.on('data', res.write.bind(res)); 178 | file.on('close', function() { 179 | res.end(); 180 | }); 181 | file.on('error', function(error) { 182 | self.sendError_(req, res, error); 183 | }); 184 | } 185 | }; 186 | 187 | StaticServlet.prototype.sendDirectory_ = function(req, res, path) { 188 | var self = this; 189 | if (path.match(/[^\/]$/)) { 190 | req.url.pathname += '/'; 191 | var redirectUrl = url.format(url.parse(url.format(req.url))); 192 | return self.sendRedirect_(req, res, redirectUrl); 193 | } 194 | fs.readdir(path, function(err, files) { 195 | if (err) 196 | return self.sendError_(req, res, error); 197 | 198 | if (!files.length) 199 | return self.writeDirectoryIndex_(req, res, path, []); 200 | 201 | var remaining = files.length; 202 | files.forEach(function(fileName, index) { 203 | fs.stat(path + '/' + fileName, function(err, stat) { 204 | if (err) 205 | return self.sendError_(req, res, err); 206 | if (stat.isDirectory()) { 207 | files[index] = fileName + '/'; 208 | } 209 | if (!(--remaining)) 210 | return self.writeDirectoryIndex_(req, res, path, files); 211 | }); 212 | }); 213 | }); 214 | }; 215 | 216 | StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) { 217 | path = path.substring(1); 218 | res.writeHead(200, { 219 | 'Content-Type': 'text/html' 220 | }); 221 | if (req.method === 'HEAD') { 222 | res.end(); 223 | return; 224 | } 225 | res.write('\n'); 226 | res.write('' + escapeHtml(path) + '\n'); 227 | res.write('\n'); 230 | res.write('

Directory: ' + escapeHtml(path) + '

'); 231 | res.write('
    '); 232 | files.forEach(function(fileName) { 233 | if (fileName.charAt(0) !== '.') { 234 | res.write('
  1. ' + 236 | escapeHtml(fileName) + '
  2. '); 237 | } 238 | }); 239 | res.write('
'); 240 | res.end(); 241 | }; 242 | 243 | // Must be last, 244 | main(process.argv); 245 | -------------------------------------------------------------------------------- /test/e2e/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/e2e/scenarios.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* http://docs.angularjs.org/guide/dev_guide.e2e-testing */ 4 | 5 | describe('my app', function() { 6 | 7 | beforeEach(function() { 8 | browser().navigateTo('../../app/index.html'); 9 | }); 10 | 11 | 12 | it('should automatically redirect to /view1 when location hash/fragment is empty', function() { 13 | expect(browser().location().url()).toBe("/view1"); 14 | }); 15 | 16 | 17 | describe('view1', function() { 18 | 19 | beforeEach(function() { 20 | browser().navigateTo('#/view1'); 21 | }); 22 | 23 | 24 | it('should render view1 when user navigates to /view1', function() { 25 | expect(element('[ng-view] p:first').text()). 26 | toMatch(/partial for view 1/); 27 | }); 28 | 29 | }); 30 | 31 | 32 | describe('view2', function() { 33 | 34 | beforeEach(function() { 35 | browser().navigateTo('#/view2'); 36 | }); 37 | 38 | 39 | it('should render view2 when user navigates to /view2', function() { 40 | expect(element('[ng-view] p:first').text()). 41 | toMatch(/partial for view 2/); 42 | }); 43 | 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/lib/angular/version.txt: -------------------------------------------------------------------------------- 1 | 1.0.7 2 | -------------------------------------------------------------------------------- /test/unit/controllersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for controllers go here */ 4 | 5 | describe('controllers', function(){ 6 | beforeEach(module('myApp.controllers')); 7 | 8 | 9 | it('should ....', inject(function() { 10 | //spec body 11 | })); 12 | 13 | it('should ....', inject(function() { 14 | //spec body 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /test/unit/directivesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for directives go here */ 4 | 5 | describe('directives', function() { 6 | beforeEach(module('myApp.directives')); 7 | 8 | describe('app-version', function() { 9 | it('should print current version', function() { 10 | module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | }); 13 | inject(function($compile, $rootScope) { 14 | var element = $compile('')($rootScope); 15 | expect(element.text()).toEqual('TEST_VER'); 16 | }); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/filtersSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for filters go here */ 4 | 5 | describe('filter', function() { 6 | beforeEach(module('myApp.filters')); 7 | 8 | 9 | describe('interpolate', function() { 10 | beforeEach(module(function($provide) { 11 | $provide.value('version', 'TEST_VER'); 12 | })); 13 | 14 | 15 | it('should replace VERSION', inject(function(interpolateFilter) { 16 | expect(interpolateFilter('before %VERSION% after')).toEqual('before TEST_VER after'); 17 | })); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/servicesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* jasmine specs for services go here */ 4 | 5 | describe('service', function() { 6 | beforeEach(module('myApp.services')); 7 | 8 | 9 | describe('version', function() { 10 | it('should return current version', inject(function(version) { 11 | expect(version).toEqual('0.1'); 12 | })); 13 | }); 14 | }); 15 | --------------------------------------------------------------------------------