├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintignore ├── .jshintrc ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── app ├── css │ ├── app │ │ ├── _header.scss │ │ ├── _topic.scss │ │ ├── _topics.scss │ │ └── _user.scss │ ├── base │ │ └── _variables.scss │ ├── main.scss │ └── modules │ │ ├── _bar.scss │ │ ├── _items.scss │ │ ├── _markdown.scss │ │ └── _scaffolding.scss ├── fonts │ ├── ionicons.eot │ ├── ionicons.svg │ ├── ionicons.ttf │ └── ionicons.woff ├── img │ ├── cnodejs_light.svg │ └── ionic.png ├── index.html ├── js │ ├── app.js │ ├── controllers │ │ ├── app.js │ │ ├── messages.js │ │ ├── settings.js │ │ ├── topic.js │ │ ├── topics.js │ │ └── user.js │ ├── directives │ │ └── resetImg.js │ ├── filters │ │ └── topic.js │ └── services │ │ ├── messages.js │ │ ├── push.js │ │ ├── settings.js │ │ ├── storage.js │ │ ├── tabs.js │ │ ├── topic.js │ │ ├── topics.js │ │ └── user.js └── templates │ ├── menu.html │ ├── messages.html │ ├── newTopic.html │ ├── settings.html │ ├── topic.html │ ├── topics.html │ └── user.html ├── bower.json ├── config.xml ├── demo ├── demo.html ├── deploy.sh └── index.html ├── hooks ├── README.md ├── after_platform_add │ └── install_plugins.js ├── after_plugin_add │ └── register_plugins.js ├── after_plugin_rm │ └── deregister_plugins.js ├── after_prepare │ ├── 010_add_platform_class.js │ ├── custom_app_display_name.js │ └── icons_and_splashscreens.js └── before_platform_add │ └── init_directories.js ├── package.json ├── resources ├── Logo.png ├── android │ ├── drawable-hdpi │ │ ├── ic_action_next_item.png │ │ ├── ic_action_previous_item.png │ │ ├── ic_action_remove.png │ │ ├── icon.png │ │ ├── launcher_icon.png │ │ └── shopper_icon.png │ ├── drawable-land-hdpi │ │ └── screen.png │ ├── drawable-land-ldpi │ │ └── screen.png │ ├── drawable-land-mdpi │ │ └── screen.png │ ├── drawable-land-xhdpi │ │ └── screen.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ ├── ic_action_next_item.png │ │ ├── ic_action_previous_item.png │ │ ├── ic_action_remove.png │ │ └── icon.png │ ├── drawable-port-hdpi │ │ └── screen.png │ ├── drawable-port-ldpi │ │ └── screen.png │ ├── drawable-port-mdpi │ │ └── screen.png │ ├── drawable-port-xhdpi │ │ └── screen.png │ ├── drawable-xhdpi │ │ ├── ic_action_next_item.png │ │ ├── ic_action_previous_item.png │ │ ├── ic_action_remove.png │ │ ├── icon.png │ │ └── launcher_icon.png │ ├── drawable-xxhdpi │ │ ├── ic_action_next_item.png │ │ ├── ic_action_previous_item.png │ │ ├── ic_action_remove.png │ │ └── launcher_icon.png │ ├── drawable │ │ ├── icon.png │ │ ├── launcher_icon.png │ │ ├── share_via_barcode.png │ │ └── shopper_icon.png │ └── icon.png └── ios │ ├── icons │ ├── Icon-40.png │ ├── Icon-40@2x.png │ ├── Icon-72.png │ ├── Icon-72@2x.png │ ├── Icon-76.png │ ├── Icon-76@2x.png │ ├── Icon-Small.png │ ├── Icon-Small@2x.png │ ├── Icon.png │ ├── Icon@2x.png │ ├── icon-50.png │ ├── icon-50@2x.png │ ├── icon-60.png │ ├── icon-60@2x.png │ └── icon-60@3x.png │ └── splash │ ├── Default-568h@2x~iphone.png │ ├── Default-667h.png │ ├── Default-736h.png │ ├── Default-Landscape-736h.png │ ├── Default-Landscape@2x~ipad.png │ ├── Default-Landscape~ipad.png │ ├── Default-Portrait@2x~ipad.png │ ├── Default-Portrait~ipad.png │ ├── Default@2x~iphone.png │ └── Default~iphone.png └── test ├── .jshintrc └── spec └── controllers.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/lib" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | www 3 | .tmp 4 | .sass-cache 5 | app/lib 6 | coverage 7 | platforms 8 | plugins 9 | *.swp 10 | *.swo 11 | *.log 12 | *.DS_Store 13 | 14 | app/js/config.js 15 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | app/scripts/config.js 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false, 23 | "cordova": false, 24 | "StatusBar": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.0.0 2 | * 增加原生分享功能,包括微信、pocket等(感谢[@pluswave](https://github.com/pluswave)) 3 | * 支持手势滑动后退 4 | * 更新ionic至v1.1.1 5 | 6 | ## v1.2.2 7 | * 增加了收藏功能 8 | * 解决头像显示问题 9 | * 优化下邮件反馈正文 10 | * 评论默认不带小尾巴 11 | * 收到推送消息默认进入消息列表 12 | 13 | # v1.2.1 14 | * 解决android下splash screen不现实的问题 15 | * 修正已登陆用户注册jush的问题 16 | 17 | ## v1.2.0 18 | * 更名为:CNode社区 19 | * 增加推送功能 20 | * 适配了iPhone 6、iPhone 6 Plus和iPad 21 | 22 | ## v1.1.2 23 | #### 2015-01-18 24 | * 增加了如何扫描登录的提示信息 25 | 26 | #### 2015-01-15 27 | * 大屏下使用split view,如iPad,6 plus横屏 28 | 29 | ### 2015-01-12 30 | * 支持iPhone6、6+ 31 | 32 | ## v1.1.1 33 | 34 | #### 2015-01-05 35 | * 增加了Android的icon、splash image 36 | 37 | ## v1.1.0 38 | 39 | #### 2014-12-30 40 | * 增加了app store评分功能 41 | 42 | #### 2014-12-29 43 | * 修复了头像显示的问题 44 | 45 | #### 2014-12-18 46 | * 更新ionic到beta14 47 | 48 | #### 2014-12-10 49 | * 区分用户已点赞的评论 refs: cnodejs/nodeclub/issues/464 50 | 51 | ## v1.0.0 52 | 53 | #### 2014-12-08 54 | * 😘: For my lovely JJ 55 | 56 | #### 2014-12-06 57 | * 在navicon上显示有未读消息标记 58 | * 帖子详情页面增加下拉刷新功能 59 | 60 | #### 2014-12-04 61 | * 当浏览器访问时可以输入token登录 62 | 63 | #### 2014-12-03 64 | * 显示回复被点赞数量 65 | * 增加小尾巴 66 | 67 | #### 2014-12-01 68 | * 增加GA统计 69 | 70 | #### 2014-11-28 71 | * 增加意见与反馈 by email 72 | * 增加localStorage存储 73 | * 用户登录信息 74 | * 用户设置 75 | 76 | #### 2014-11-27 77 | * 增加省流量模式 78 | * 增加设置页面 79 | * 关于作者 80 | * 关于CNodeJs 81 | 82 | #### 2014-11-24 83 | * 增加用户退出功能 84 | 85 | #### 2014-11-11 86 | * 使用inappbrowser打开外部链接 87 | * 增加app badge标识未读消息数量 88 | 89 | #### 2014-11-10 90 | * 增加了消息中心,查看消息历史 91 | 92 | #### 2014-11-08 93 | * UI 94 | * 延时隐藏启动画面,优化体验 95 | * 增加未读消息提示 96 | 97 | #### 2014-11-07 98 | * 登录 99 | * 长按“登录”时,读取剪贴板内accesstoken登录 100 | * 更新UI 101 | * 主题列表 102 | * 主题详情 103 | * logo(同 cnodejs_light.svg ) 104 | * 解决了一个刷新导致的分页问题 105 | * 增加了moment.js的本地化文件 106 | 107 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-11-04 using generator-ionic 0.6.1 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | var path = require('path'); 6 | var cordovaCli = require('cordova'); 7 | var spawn = require('child_process').spawn; 8 | var exec = require('child_process').exec; 9 | 10 | module.exports = function (grunt) { 11 | 12 | // Load grunt tasks automatically 13 | require('load-grunt-tasks')(grunt); 14 | 15 | // Time how long tasks take. Can help when optimizing build times 16 | require('time-grunt')(grunt); 17 | 18 | // Define the configuration for all the tasks 19 | grunt.initConfig({ 20 | 21 | // Project settings 22 | pkg: grunt.file.readJSON('package.json'), 23 | yeoman: { 24 | // configurable paths 25 | app: 'app', 26 | scripts: 'js', 27 | styles: 'css', 28 | images: 'img' 29 | }, 30 | 31 | // Environment Variables for Angular App 32 | // This creates an Angular Module that can be injected via ENV 33 | // Add any desired constants to the ENV objects below. 34 | // https://github.com/diegonetto/generator-ionic#environment-specific-configuration 35 | ngconstant: { 36 | options: { 37 | space: ' ', 38 | wrap: '"use strict";\n\n {%= __ngModule %}', 39 | name: 'cnodejs.config', 40 | dest: '<%= yeoman.app %>/<%= yeoman.scripts %>/config.js', 41 | constants: { 42 | '$ionicLoadingConfig': { 43 | template: '请求中...' 44 | } 45 | } 46 | }, 47 | development: { 48 | constants: { 49 | ENV: { 50 | version: '<%= pkg.version %>', 51 | name: 'development', 52 | debug: true, 53 | // Test user access token 54 | accessToken: 'f6d0dc46-d66f-45f9-a7e7-4c1be175a08d', 55 | domain: 'http://dev.cnodejs.org', 56 | api: '/api/v1' 57 | } 58 | } 59 | }, 60 | production: { 61 | constants: { 62 | ENV: { 63 | version: '<%= pkg.version %>', 64 | name: 'production', 65 | debug: false, 66 | domain: 'https://cnodejs.org', 67 | api: '/api/v1' 68 | } 69 | } 70 | } 71 | }, 72 | 73 | // Watches files for changes and runs tasks based on the changed files 74 | watch: { 75 | bower: { 76 | files: ['bower.json'], 77 | tasks: ['wiredep', 'newer:copy:app'] 78 | }, 79 | html: { 80 | files: ['<%= yeoman.app %>/**/*.html'], 81 | tasks: ['newer:copy:app'] 82 | }, 83 | js: { 84 | files: ['<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js'], 85 | tasks: ['newer:copy:app', 'newer:jshint:all'] 86 | }, 87 | compass: { 88 | files: ['<%= yeoman.app %>/<%= yeoman.styles %>/**/*.{scss,sass}'], 89 | tasks: ['compass:server', 'autoprefixer', 'newer:copy:tmp'] 90 | }, 91 | gruntfile: { 92 | files: ['Gruntfile.js'], 93 | tasks: ['ngconstant:development', 'newer:copy:app'] 94 | } 95 | }, 96 | 97 | // The actual grunt server settings 98 | connect: { 99 | options: { 100 | port: 9000, 101 | // Change this to '0.0.0.0' to access the server from outside. 102 | hostname: 'localhost' 103 | }, 104 | dist: { 105 | options: { 106 | base: 'www' 107 | } 108 | }, 109 | coverage: { 110 | options: { 111 | port: 9002, 112 | open: true, 113 | base: ['coverage'] 114 | } 115 | } 116 | }, 117 | 118 | // Make sure code styles are up to par and there are no obvious mistakes 119 | jshint: { 120 | options: { 121 | jshintrc: '.jshintrc', 122 | reporter: require('jshint-stylish') 123 | }, 124 | all: [ 125 | 'Gruntfile.js', 126 | '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js' 127 | ], 128 | test: { 129 | options: { 130 | jshintrc: 'test/.jshintrc' 131 | }, 132 | src: ['test/unit/**/*.js'] 133 | } 134 | }, 135 | 136 | // Empties folders to start fresh 137 | clean: { 138 | dist: { 139 | files: [{ 140 | dot: true, 141 | src: [ 142 | '.tmp', 143 | 'www/*', 144 | '!www/.git*' 145 | ] 146 | }] 147 | }, 148 | server: '.tmp' 149 | }, 150 | 151 | autoprefixer: { 152 | options: { 153 | browsers: ['last 1 version'] 154 | }, 155 | dist: { 156 | files: [{ 157 | expand: true, 158 | cwd: '.tmp/<%= yeoman.styles %>/', 159 | src: '{,*/}*.css', 160 | dest: '.tmp/<%= yeoman.styles %>/' 161 | }] 162 | } 163 | }, 164 | 165 | // Automatically inject Bower components into the app 166 | wiredep: { 167 | app: { 168 | src: ['<%= yeoman.app %>/index.html'], 169 | ignorePath: /\.\.\//, 170 | overrides: { 171 | 'moment': { 172 | 'main': 'min/moment-with-locales.js' 173 | } 174 | } 175 | }, 176 | sass: { 177 | src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 178 | ignorePath: /(\.\.\/){1,2}lib\// 179 | } 180 | }, 181 | 182 | 183 | // Compiles Sass to CSS and generates necessary files if requested 184 | compass: { 185 | options: { 186 | sassDir: '<%= yeoman.app %>/<%= yeoman.styles %>', 187 | cssDir: '.tmp/<%= yeoman.styles %>', 188 | generatedImagesDir: '.tmp/<%= yeoman.images %>/generated', 189 | imagesDir: '<%= yeoman.app %>/<%= yeoman.images %>', 190 | javascriptsDir: '<%= yeoman.app %>/<%= yeoman.scripts %>', 191 | fontsDir: '<%= yeoman.app %>/<%= yeoman.styles %>/fonts', 192 | importPath: '<%= yeoman.app %>/lib', 193 | httpImagesPath: '/<%= yeoman.images %>', 194 | httpGeneratedImagesPath: '/<%= yeoman.images %>/generated', 195 | httpFontsPath: '/<%= yeoman.styles %>/fonts', 196 | relativeAssets: false, 197 | assetCacheBuster: false, 198 | raw: 'Sass::Script::Number.precision = 10\n' 199 | }, 200 | dist: { 201 | options: { 202 | generatedImagesDir: 'www/<%= yeoman.images %>/generated' 203 | } 204 | }, 205 | server: { 206 | options: { 207 | debugInfo: true 208 | } 209 | } 210 | }, 211 | 212 | 213 | // Reads HTML for usemin blocks to enable smart builds that automatically 214 | // concat, minify and revision files. Creates configurations in memory so 215 | // additional tasks can operate on them 216 | useminPrepare: { 217 | html: '<%= yeoman.app %>/index.html', 218 | options: { 219 | dest: 'www', 220 | flow: { 221 | html: { 222 | steps: { 223 | js: ['concat', 'uglifyjs'], 224 | css: ['cssmin'] 225 | }, 226 | post: {} 227 | } 228 | } 229 | } 230 | }, 231 | 232 | // Performs rewrites based on the useminPrepare configuration 233 | usemin: { 234 | html: ['www/**/*.html'], 235 | css: ['www/<%= yeoman.styles %>/**/*.css'], 236 | options: { 237 | assetsDirs: ['www'] 238 | } 239 | }, 240 | 241 | // The following *-min tasks produce minified files in the dist folder 242 | cssmin: { 243 | options: { 244 | root: '<%= yeoman.app %>', 245 | noRebase: true 246 | } 247 | }, 248 | htmlmin: { 249 | dist: { 250 | options: { 251 | collapseWhitespace: true, 252 | collapseBooleanAttributes: true, 253 | removeCommentsFromCDATA: true, 254 | removeOptionalTags: true 255 | }, 256 | files: [{ 257 | expand: true, 258 | cwd: 'www', 259 | src: ['*.html', 'templates/**/*.html'], 260 | dest: 'www' 261 | }] 262 | } 263 | }, 264 | 265 | // Copies remaining files to places other tasks can use 266 | copy: { 267 | dist: { 268 | files: [{ 269 | expand: true, 270 | dot: true, 271 | cwd: '<%= yeoman.app %>', 272 | dest: 'www', 273 | src: [ 274 | '<%= yeoman.images %>/**/*.{png,jpg,jpeg,gif,webp,svg}', 275 | '*.html', 276 | 'templates/**/*.html', 277 | 'fonts/*' 278 | ] 279 | }, { 280 | expand: true, 281 | cwd: '.tmp/<%= yeoman.images %>', 282 | dest: 'www/<%= yeoman.images %>', 283 | src: ['generated/*'] 284 | }] 285 | }, 286 | styles: { 287 | expand: true, 288 | cwd: '<%= yeoman.app %>/<%= yeoman.styles %>', 289 | dest: '.tmp/<%= yeoman.styles %>/', 290 | src: '{,*/}*.css' 291 | }, 292 | fonts: { 293 | expand: true, 294 | cwd: 'app/lib/ionic/release/fonts/', 295 | dest: '<%= yeoman.app %>/fonts/', 296 | src: '*' 297 | }, 298 | vendor: { 299 | expand: true, 300 | cwd: '<%= yeoman.app %>/vendor', 301 | dest: '.tmp/<%= yeoman.styles %>/', 302 | src: '{,*/}*.css' 303 | }, 304 | app: { 305 | expand: true, 306 | cwd: '<%= yeoman.app %>', 307 | dest: 'www/', 308 | src: [ 309 | '**/*', 310 | '!**/*.(scss,sass,css)', 311 | ] 312 | }, 313 | tmp: { 314 | expand: true, 315 | cwd: '.tmp', 316 | dest: 'www/', 317 | src: '**/*' 318 | } 319 | }, 320 | 321 | concurrent: { 322 | ionic: { 323 | tasks: [], 324 | options: { 325 | logConcurrentOutput: true 326 | } 327 | }, 328 | server: [ 329 | 'compass:server', 330 | 'copy:styles', 331 | 'copy:vendor', 332 | 'copy:fonts' 333 | ], 334 | test: [ 335 | 'compass', 336 | 'copy:styles', 337 | 'copy:vendor', 338 | 'copy:fonts' 339 | ], 340 | dist: [ 341 | 'compass:dist', 342 | 'copy:styles', 343 | 'copy:vendor', 344 | 'copy:fonts' 345 | ] 346 | }, 347 | 348 | // By default, your `index.html`'s will take care of 349 | // minification. These next options are pre-configured if you do not wish 350 | // to use the Usemin blocks. 351 | // cssmin: { 352 | // dist: { 353 | // files: { 354 | // 'www/<%= yeoman.styles %>/main.css': [ 355 | // '.tmp/<%= yeoman.styles %>/**/*.css', 356 | // '<%= yeoman.app %>/<%= yeoman.styles %>/**/*.css' 357 | // ] 358 | // } 359 | // } 360 | // }, 361 | // uglify: { 362 | // dist: { 363 | // files: { 364 | // 'www/<%= yeoman.scripts %>/scripts.js': [ 365 | // 'www/<%= yeoman.scripts %>/scripts.js' 366 | // ] 367 | // } 368 | // } 369 | // }, 370 | // concat: { 371 | // dist: {} 372 | // }, 373 | 374 | // Test settings 375 | // These will override any config options in karma.conf.js if you create it. 376 | karma: { 377 | options: { 378 | basePath: '', 379 | frameworks: ['mocha', 'chai'], 380 | files: [ 381 | '<%= yeoman.app %>/lib/angular/angular.js', 382 | '<%= yeoman.app %>/lib/angular-animate/angular-animate.js', 383 | '<%= yeoman.app %>/lib/angular-sanitize/angular-sanitize.js', 384 | '<%= yeoman.app %>/lib/angular-ui-router/release/angular-ui-router.js', 385 | '<%= yeoman.app %>/lib/ionic/release/js/ionic.js', 386 | '<%= yeoman.app %>/lib/ionic/release/js/ionic-angular.js', 387 | '<%= yeoman.app %>/lib/angular-mocks/angular-mocks.js', 388 | '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js', 389 | 'test/mock/**/*.js', 390 | 'test/spec/**/*.js' 391 | ], 392 | autoWatch: false, 393 | reporters: ['dots', 'coverage'], 394 | port: 8080, 395 | singleRun: false, 396 | preprocessors: { 397 | // Update this if you change the yeoman config path 398 | 'app/js/**/*.js': ['coverage'] 399 | }, 400 | coverageReporter: { 401 | reporters: [ 402 | { type: 'html', dir: 'coverage/' }, 403 | { type: 'text-summary' } 404 | ] 405 | } 406 | }, 407 | unit: { 408 | // Change this to 'Chrome', 'Firefox', etc. Note that you will need 409 | // to install a karma launcher plugin for browsers other than Chrome. 410 | browsers: ['PhantomJS'], 411 | background: true 412 | }, 413 | continuous: { 414 | browsers: ['PhantomJS'], 415 | singleRun: true, 416 | } 417 | }, 418 | 419 | // ngAnnotate tries to make the code safe for minification automatically by 420 | // using the Angular long form for dependency injection. 421 | ngAnnotate: { 422 | dist: { 423 | files: [{ 424 | expand: true, 425 | cwd: '.tmp/concat/<%= yeoman.scripts %>', 426 | src: '*.js', 427 | dest: '.tmp/concat/<%= yeoman.scripts %>' 428 | }] 429 | } 430 | } 431 | 432 | }); 433 | 434 | // Register tasks for all Cordova commands 435 | _.functions(cordovaCli).forEach(function (name) { 436 | grunt.registerTask(name, function () { 437 | this.args.unshift(name.replace('cordova:', '')); 438 | // Handle URL's being split up by Grunt because of `:` characters 439 | if (_.contains(this.args, 'http') || _.contains(this.args, 'https')) { 440 | this.args = this.args.slice(0, -2).concat(_.last(this.args, 2).join(':')); 441 | } 442 | var done = this.async(); 443 | var exec = process.platform === 'win32' ? 'cordova.cmd' : 'cordova'; 444 | var cmd = path.resolve('./node_modules/cordova/bin', exec); 445 | var flags = process.argv.splice(3); 446 | var child = spawn(cmd, this.args.concat(flags)); 447 | child.stdout.on('data', function (data) { 448 | grunt.log.writeln(data); 449 | }); 450 | child.stderr.on('data', function (data) { 451 | grunt.log.error(data); 452 | }); 453 | child.on('close', function (code) { 454 | code = code ? false : true; 455 | done(code); 456 | }); 457 | }); 458 | }); 459 | 460 | // Since Apache Ripple serves assets directly out of their respective platform 461 | // directories, we watch all registered files and then copy all un-built assets 462 | // over to www/. Last step is running cordova prepare so we can refresh the ripple 463 | // browser tab to see the changes. Technically ripple runs `cordova prepare` on browser 464 | // refreshes, but at this time you would need to re-run the emulator to see changes. 465 | grunt.registerTask('ripple', ['wiredep', 'newer:copy:app', 'ripple-emulator']); 466 | grunt.registerTask('ripple-emulator', function () { 467 | grunt.config.set('watch', { 468 | all: { 469 | files: _.flatten(_.pluck(grunt.config.get('watch'), 'files')), 470 | tasks: ['newer:copy:app', 'prepare'] 471 | } 472 | }); 473 | 474 | var cmd = path.resolve('./node_modules/ripple-emulator/bin', 'ripple'); 475 | var child = spawn(cmd, ['emulate']); 476 | child.stdout.on('data', function (data) { 477 | grunt.log.writeln(data); 478 | }); 479 | child.stderr.on('data', function (data) { 480 | grunt.log.error(data); 481 | }); 482 | process.on('exit', function (code) { 483 | child.kill('SIGINT'); 484 | process.exit(code); 485 | }); 486 | 487 | return grunt.task.run(['watch']); 488 | }); 489 | 490 | // Dynamically configure `karma` target of `watch` task so that 491 | // we don't have to run the karma test server as part of `grunt serve` 492 | grunt.registerTask('watch:karma', function () { 493 | var karma = { 494 | files: ['<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js', 'test/spec/**/*.js'], 495 | tasks: ['newer:jshint:test', 'karma:unit:run'] 496 | }; 497 | grunt.config.set('watch', karma); 498 | return grunt.task.run(['watch']); 499 | }); 500 | 501 | // Wrap ionic-cli commands 502 | grunt.registerTask('ionic', function() { 503 | var done = this.async(); 504 | var script = path.resolve('./node_modules/ionic/bin/', 'ionic'); 505 | var flags = process.argv.splice(3); 506 | var child = spawn(script, this.args.concat(flags), { stdio: 'inherit' }); 507 | child.on('close', function (code) { 508 | code = code ? false : true; 509 | done(code); 510 | }); 511 | }); 512 | 513 | grunt.registerTask('test', [ 514 | 'clean', 515 | 'concurrent:test', 516 | 'autoprefixer', 517 | 'karma:unit:start', 518 | 'watch:karma' 519 | ]); 520 | 521 | grunt.registerTask('serve', function (target) { 522 | if (target === 'compress') { 523 | return grunt.task.run(['compress', 'ionic:serve']); 524 | } 525 | 526 | grunt.config('concurrent.ionic.tasks', ['ionic:serve', 'watch']); 527 | grunt.task.run(['init', 'concurrent:ionic']); 528 | }); 529 | grunt.registerTask('emulate', function() { 530 | grunt.config('concurrent.ionic.tasks', ['ionic:emulate:' + this.args.join(), 'watch']); 531 | return grunt.task.run(['init', 'concurrent:ionic']); 532 | }); 533 | grunt.registerTask('run', function() { 534 | grunt.config('concurrent.ionic.tasks', ['ionic:run:' + this.args.join(), 'watch']); 535 | return grunt.task.run(['init', 'concurrent:ionic']); 536 | }); 537 | grunt.registerTask('build', function() { 538 | return grunt.task.run(['compress', 'ionic:build:' + this.args.join()]); 539 | }); 540 | 541 | // Bumping bundle version 542 | grunt.registerTask('bumpingBundleVersion', function() { 543 | var done = this.async(); 544 | exec('git rev-list HEAD | wc -l | awk \'{print $1}\'', function(error, stdout, stderr) { 545 | if (error === null) { 546 | var config = grunt.file.read('config.xml'); 547 | config = config.replace(/CFBundleVersion="(\S+)"/gi, 'CFBundleVersion="' + stdout.trim() + '"') 548 | .replace(/versionCode="(\S+)"/gi, 'versionCode="' + stdout.trim() + '"'); 549 | grunt.file.write('config.xml', config); 550 | done(); 551 | } 552 | }); 553 | }); 554 | 555 | grunt.registerTask('init', [ 556 | 'clean', 557 | 'ngconstant:development', 558 | 'wiredep', 559 | 'concurrent:server', 560 | 'autoprefixer', 561 | 'newer:copy:app', 562 | 'newer:copy:tmp' 563 | ]); 564 | 565 | 566 | grunt.registerTask('compress', [ 567 | 'clean', 568 | 'ngconstant:production', 569 | 'wiredep', 570 | 'useminPrepare', 571 | 'concurrent:dist', 572 | 'autoprefixer', 573 | 'concat', 574 | 'ngAnnotate', 575 | 'copy:dist', 576 | 'cssmin', 577 | 'uglify', 578 | 'usemin', 579 | 'htmlmin' 580 | ]); 581 | 582 | grunt.registerTask('coverage', ['karma:continuous', 'connect:coverage:keepalive']); 583 | 584 | grunt.registerTask('default', [ 585 | 'newer:jshint', 586 | 'karma:continuous', 587 | 'compress' 588 | ]); 589 | }; 590 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Lance Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CNodejs Ionic app 2 | 3 | > [https://cnodejs.org](http://cnodejs.org) hybird mobile application powered by [Ionic Framework](http://ionicframework.com) 1.x using AngularJS 1.x and Cordova. The development stage powered by [Ionic Framework generator](https://github.com/diegonetto/generator-ionic). 4 | 5 | [Demo](http://lanceli.com/cnodejs-ionic) 6 | 7 | [![Download on the app store](https://devimages.apple.com.edgekey.net/app-store/marketing/guidelines/images/badge-download-on-the-app-store.svg)](https://itunes.apple.com/cn/app/id954734793) 8 | 9 | There is another one made by Ionic 3, check it out here https://github.com/lanceli/cnodejs-ionic3 10 | 11 | ## Developing 12 | 13 | If you'd like to run it locally, and modify something, you can do so by cloning this repo and running the following commands (assuming that you have Node, NPM, Ionic, Cordova, Grunt and Bower installed). 14 | 15 | ```bash 16 | # Clone and Install dependencies 17 | $ git clone git://github.com/lanceli/cnodejs-ionic.git 18 | $ npm install 19 | $ bower install 20 | 21 | # Config api url on development mode 22 | # At line 54 in Gruntfile.js 23 | $ vim Gruntfile.js 24 | 25 | # Start the server on localhost:8010 on development mode 26 | # Watches for changes, automatically recompiles files and refreshes the browser 27 | $ grunt serve 28 | 29 | # Start the server on production mode 30 | $ grunt serve:compress 31 | 32 | # Add platform target 33 | $ grunt platform:add:ios 34 | $ grunt platform:add:android 35 | 36 | # Run on platform target on development mode 37 | $ grunt run:ios 38 | $ grunt run:android 39 | 40 | # Run on platform target on production mode 41 | $ grunt build:ios 42 | $ ionic run ios 43 | $ grunt build:android 44 | $ ionic run android 45 | ``` 46 | 47 | Need more detail? Please chekout [Ionic Framework](http://ionicframework.com) and [Ionic Framework generator](https://github.com/diegonetto/generator-ionic). 48 | 49 | ### Question 50 | if you have some problem with window system, please follow the blow step may help you fixed it. 51 | ```js 52 | grunt-contrib-compass/node_modules/tmp/lib/tmp.js:261 53 | throw err; 54 | ^ 55 | Error: cannot read property 'stdout' of undefined 56 | at compile 57 | ``` 58 | 59 | see issue: [Run grunt serve error](https://github.com/lanceli/cnodejs-ionic/issues/11) 60 | 61 | * Make sure you have installed [Ruby](http://rubyinstaller.org/downloads/) tools 62 | * After you install ruby, use gem to install sass and compass(in cmd): 63 | > 1. gem install sass 64 | > 2. gem install compass 65 | 66 | * use npm to install modules(in cmd), choose one to install: 67 | > 1. npm install cordova ionic 68 | > 2. npm install -g cordova ionic 69 | 70 | After install all the modules, you may face the child_process error. This is a windows system bug. you can fixed it like this: 71 | ```js 72 | grunt-contrib-compass/node_modules/tmp/lib/tmp.js:261 73 | throw err; 74 | ^ 75 | Error: spawn ENOENT 76 | at errnoException (child_process.js:1001:11) 77 | at Process.ChildProcess._handle.onexit (child_process.js:802:34) 78 | ``` 79 | A solution would be to replace spawn by win-spawn: 80 | 81 | 1. npm install win-spawn 82 | 2. Replace the line in the Gruntfile.js: 83 | ```js 84 | replace child_process to win-spawn 85 | var spawn = require('child_process').spawn; 86 | to 87 | var spawn = require('win-spawn'); 88 | ``` 89 | 90 | more information about this defect,please see: 91 | 92 | * [child_process error solution1](https://cnodejs.org/topic/54b4db04edf686411e1b9d7f#54b51ac3edf686411e1b9dcf) 93 | * [child_process error solution2](https://github.com/diegonetto/generator-ionic/issues/15#issuecomment-38075095) 94 | 95 | have try, it should work now. 96 | 97 | ### Cross-Origin 98 | When you run it locally in browser, CORS is a problem. 99 | 100 | **Disable web security of chrome** 101 | 102 | ``` 103 | open -a /Applications/Google\ Chrome.app --args --disable-web-security --allow-file-access-from-files 104 | ``` 105 | OR **Allow cross origin access in nginx** 106 | 107 | ``` 108 | add_header Access-Control-Allow-Origin *; 109 | ``` 110 | Checkout this: [How do I add Access-Control-Allow-Origin in NGINX?](http://serverfault.com/questions/162429/how-do-i-add-access-control-allow-origin-in-nginx/) 111 | 112 | ## Release History 113 | See the [CHANGELOG](CHANGELOG.md). 114 | 115 | ## Contribute 116 | You are welcome to contribute. 🎉 117 | 118 | ## License 119 | [MIT](LICENSE) 120 | 121 | -------------------------------------------------------------------------------- /app/css/app/_header.scss: -------------------------------------------------------------------------------- 1 | a { 2 | color: #08c; 3 | text-decoration: none; 4 | } 5 | #logo { 6 | width: 128px; 7 | height: 30px; 8 | margin-left: 10px; 9 | } 10 | #devTag { 11 | position: relative; 12 | font-size: 14px; 13 | } 14 | -------------------------------------------------------------------------------- /app/css/app/_topic.scss: -------------------------------------------------------------------------------- 1 | .topic { 2 | .title { 3 | margin: 0; 4 | } 5 | .summary { 6 | margin: 8px 0; 7 | } 8 | .avatar { 9 | width: 16px; 10 | border-radius: 8px; 11 | vertical-align: middle; 12 | } 13 | } 14 | .replies { 15 | .item { 16 | padding-top: 10px; 17 | padding-bottom: 10px; 18 | white-space: normal; 19 | } 20 | .reply-content { 21 | img { 22 | max-width: 100%; 23 | } 24 | p { 25 | overflow: visible !important; 26 | white-space: normal !important; 27 | } 28 | } 29 | } 30 | .topic-create { 31 | .topic-tabs { 32 | position: relative; 33 | padding-top: 8px; 34 | padding-bottom: 8px; 35 | padding-left: 0px; 36 | } 37 | } 38 | .reply-new { 39 | input { 40 | background: rgba(0, 0, 0, 0); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/css/app/_topics.scss: -------------------------------------------------------------------------------- 1 | .topics { 2 | .item-content { 3 | padding: 14px 16px 18px 72px; 4 | > img { 5 | &:not(:first-child) { 6 | -webkit-transform: translate3d(-2000px, -2000px, 0px); 7 | position: absolute; 8 | } 9 | } 10 | } 11 | .activated { 12 | .tab { 13 | &:not(.hl) { 14 | background-color: #aeaeae; 15 | color: #fff; 16 | } 17 | } 18 | } 19 | .ago, 20 | .summary { 21 | font-size: 12px; 22 | } 23 | .tab { 24 | background-color: #E5E5E5; 25 | color: #999; 26 | border-radius: 2px; 27 | padding: 2px 4px; 28 | } 29 | .hl { 30 | background-color: $node-green; 31 | color: #fff; 32 | } 33 | } 34 | .topic-content { 35 | img { 36 | max-width: 100%; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/css/app/_user.scss: -------------------------------------------------------------------------------- 1 | .user { 2 | .item-content { 3 | padding-right: 16px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/css/base/_variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | // ------------------------------- 3 | 4 | $node-green: #80bd01; 5 | $node-black: #444; 6 | -------------------------------------------------------------------------------- /app/css/main.scss: -------------------------------------------------------------------------------- 1 | // base 2 | @import "base/_variables.scss"; 3 | 4 | // modules 5 | @import "modules/_scaffolding.scss"; 6 | @import "modules/_bar.scss"; 7 | @import "modules/_items.scss"; 8 | @import "modules/_markdown.scss"; 9 | 10 | // app 11 | @import "app/_header.scss"; 12 | @import "app/_topics.scss"; 13 | @import "app/_topic.scss"; 14 | @import "app/_user.scss"; 15 | -------------------------------------------------------------------------------- /app/css/modules/_bar.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/app/css/modules/_bar.scss -------------------------------------------------------------------------------- /app/css/modules/_items.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | left: 0; 3 | right: 0; 4 | } 5 | .item-divider { 6 | color: #555; 7 | font-weight: normal; 8 | font-size: 14px; 9 | } 10 | .item-gap { 11 | border: 0 none; 12 | min-height: 0px; 13 | } 14 | .item-loading { 15 | text-align: center; 16 | color: #aaa; 17 | } 18 | .saverMode { 19 | .item-avatar { 20 | padding-left: 14px; 21 | &.item-complex { 22 | padding-left: 0; 23 | } 24 | > img { 25 | &:first-child { 26 | -webkit-transform: translate3d(-2000px, -2000px, 0px); 27 | } 28 | } 29 | .item-content { 30 | padding-left: 14px; 31 | padding-right: 16px; 32 | > img { 33 | &:first-child { 34 | -webkit-transform: translate3d(-2000px, -2000px, 0px); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/css/modules/_markdown.scss: -------------------------------------------------------------------------------- 1 | .markdown-text { 2 | a { 3 | color: #778087; 4 | } 5 | ul { 6 | list-style-type: disc; 7 | margin-bottom: 5px; 8 | margin-left: 15px; 9 | ul { 10 | list-style-type: circle; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/css/modules/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | #copyright { 2 | margin: 20px 0 10px; 3 | text-align: center; 4 | color: #aaa; 5 | text-shadow: 0 1px 0 #fff; 6 | } 7 | .notifyBadge { 8 | position: absolute; 9 | top: 2px; 10 | right: 2px; 11 | background: #EF4E3A; 12 | border-radius: 10px; 13 | width: 10px; 14 | height: 10px; 15 | } 16 | .badge { 17 | &.messagesCount { 18 | border-radius: 20px; 19 | width: 20px; 20 | height: 20px; 21 | display: inline-block; 22 | padding: 0; 23 | text-align: center; 24 | line-height: 20px; 25 | font-size: 12px; 26 | } 27 | } 28 | 29 | .bold { 30 | font-weight: bold; 31 | } 32 | 33 | .pane { 34 | background-color: #f5f5f5; 35 | } 36 | 37 | .cnode { 38 | color: $node-green; 39 | } 40 | 41 | .cnode-bg { 42 | background-color: $node-green; 43 | } 44 | -------------------------------------------------------------------------------- /app/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/app/fonts/ionicons.eot -------------------------------------------------------------------------------- /app/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/app/fonts/ionicons.ttf -------------------------------------------------------------------------------- /app/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/app/fonts/ionicons.woff -------------------------------------------------------------------------------- /app/img/cnodejs_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 18 | 25 | 28 | 36 | 42 | 45 | 54 | 55 | -------------------------------------------------------------------------------- /app/img/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/app/img/ionic.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Ionic cnodejs App 4 | angular.module('cnodejs', [ 5 | 'ionic', 6 | 'angularMoment', 7 | 'cnodejs.controllers', 8 | 'cnodejs.filters', 9 | 'cnodejs.directives', 10 | 'cnodejs.config'] 11 | ) 12 | 13 | .run(function($ionicPlatform, $log, $timeout, $state, $rootScope, amMoment, ENV, Push, User) { 14 | 15 | // set moment locale 16 | amMoment.changeLocale('zh-cn'); 17 | 18 | // notify 19 | if (!navigator.notification) { 20 | navigator.notification = { 21 | alert: function(message) { 22 | window.alert(message); 23 | } 24 | }; 25 | } 26 | 27 | // push notification callback 28 | var notificationCallback = function(data, isActive) { 29 | $log.debug(data); 30 | var notif = angular.fromJson(data); 31 | if (notif.extras) { 32 | // android 33 | if (notif.extras['cn.jpush.android.EXTRA']['topicId']) { 34 | $state.go('app.topic', { 35 | id: notif.extras['cn.jpush.android.EXTRA']['topicId'] 36 | }); 37 | } else { 38 | $state.go('app.messages'); 39 | } 40 | } else { 41 | // ios 42 | if (notif.topicId) { 43 | if (isActive) { 44 | $rootScope.getMessageCount(); 45 | } else { 46 | $state.go('app.topic', { 47 | id: notif.topicId 48 | }); 49 | } 50 | } else { 51 | $state.go('app.messages'); 52 | } 53 | } 54 | }; 55 | $ionicPlatform.ready(function() { 56 | if(window.cordova) { 57 | 58 | // setup google analytics 59 | if (window.analytics && ENV.name === 'production') { 60 | window.analytics.startTrackerWithId('UA-57246029-1'); 61 | } 62 | 63 | // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard 64 | // for form inputs) 65 | if (window.cordova.plugins.Keyboard) { 66 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 67 | cordova.plugins.Keyboard.disableScroll(true); 68 | } 69 | window.InAppBrowser = window.cordova.InAppBrowser; 70 | } else { 71 | window.InAppBrowser = { 72 | open: function(url, target, params) { 73 | window.open(url); 74 | } 75 | }; 76 | } 77 | 78 | // push handler 79 | Push.init(notificationCallback); 80 | 81 | // detect current user have not set alias of jpush 82 | var currentUser = User.getCurrentUser(); 83 | if (currentUser.id) { 84 | Push.setAlias(currentUser.id); 85 | } 86 | 87 | if (navigator.splashscreen) { 88 | $timeout(function() { 89 | navigator.splashscreen.hide(); 90 | 91 | // check if have push after app launch 92 | Push.check(); 93 | }, 100); 94 | } else { 95 | $log.debug('no splash screen plugin'); 96 | } 97 | 98 | }); 99 | 100 | }) 101 | .config(function(ENV, $stateProvider, $urlRouterProvider, $logProvider) { 102 | 103 | $logProvider.debugEnabled(ENV.debug); 104 | $stateProvider 105 | .state('app', { 106 | url: '', 107 | abstract: true, 108 | templateUrl: 'templates/menu.html', 109 | controller: 'AppCtrl' 110 | }) 111 | .state('app.user', { 112 | url: '/user/:loginname', 113 | views: { 114 | 'menuContent': { 115 | templateUrl: 'templates/user.html', 116 | controller: 'UserCtrl' 117 | } 118 | } 119 | }) 120 | .state('app.messages', { 121 | url: '/my/messages', 122 | views: { 123 | 'menuContent': { 124 | templateUrl: 'templates/messages.html', 125 | controller: 'MessagesCtrl' 126 | } 127 | } 128 | }) 129 | .state('app.topics', { 130 | url: '/topics/:tab', 131 | views: { 132 | 'menuContent': { 133 | templateUrl: 'templates/topics.html', 134 | controller: 'TopicsCtrl' 135 | } 136 | } 137 | }) 138 | .state('app.topic', { 139 | url: '/topic/:id', 140 | views: { 141 | 'menuContent': { 142 | templateUrl: 'templates/topic.html', 143 | controller: 'TopicCtrl' 144 | } 145 | } 146 | }) 147 | .state('app.settings', { 148 | url: '/settings', 149 | views: { 150 | 'menuContent': { 151 | templateUrl: 'templates/settings.html', 152 | controller: 'SettingsCtrl' 153 | } 154 | } 155 | }); 156 | $urlRouterProvider.otherwise('/topics/all'); 157 | }); 158 | 159 | angular.module('cnodejs.controllers', ['cnodejs.services']); 160 | 161 | angular.module('cnodejs.services', ['ngResource', 'cnodejs.config']); 162 | 163 | angular.module('cnodejs.filters', ['cnodejs.services']); 164 | 165 | angular.module('cnodejs.directives', []); 166 | -------------------------------------------------------------------------------- /app/js/controllers/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.controllers:AppCtrl 6 | * @description 7 | * # AppCtrl 8 | * Main Controller of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.controllers') 12 | .controller('AppCtrl', function(ENV, $scope, $log, $timeout, $rootScope, $ionicPopup, $ionicLoading, Tabs, User, Messages, Settings, Push) { 13 | $log.log('app ctrl'); 14 | 15 | // get message count 16 | $rootScope.getMessageCount = function() { 17 | Messages.getMessageCount().$promise.then(function(response) { 18 | $scope.messagesCount = response.data; 19 | setBadge($scope.messagesCount); 20 | }, function(response) { 21 | $log.log('get messages count fail', response); 22 | }); 23 | }; 24 | 25 | // environment config 26 | $scope.ENV = ENV; 27 | 28 | // ionic platform 29 | $scope.platform = ionic.Platform; 30 | 31 | // get current user 32 | var currentUser = User.getCurrentUser(); 33 | $scope.loginName = currentUser.loginname || null; 34 | if ($scope.loginName !== null) { 35 | $rootScope.getMessageCount(); 36 | } 37 | 38 | // get user settings 39 | $scope.settings = Settings.getSettings(); 40 | 41 | // error handler 42 | var errorMsg = { 43 | 0: '网络出错啦,请再试一下', 44 | 'wrong accessToken': '授权失败' 45 | }; 46 | $rootScope.requestErrorHandler = function(options, callback) { 47 | return function(response) { 48 | var error; 49 | if (response.data && response.data.error_msg) { 50 | error = errorMsg[response.data.error_msg]; 51 | } else { 52 | error = errorMsg[response.status] || 'Error: ' + response.status + ' ' + response.statusText; 53 | } 54 | var o = options || {}; 55 | angular.extend(o, { 56 | template: error, 57 | duration: 1000 58 | }); 59 | $ionicLoading.show(o); 60 | return callback && callback(); 61 | }; 62 | }; 63 | 64 | // set badge of app icon 65 | var setBadge = function(num) { 66 | // Promot permission request to show badge notifications 67 | if (window.cordova && window.cordova.plugins && window.cordova.plugins.notification.badge) { 68 | cordova.plugins.notification.badge.hasPermission(function (granted) { 69 | $log.debug('Permission has been granted: ' + granted); 70 | if (granted) { 71 | $log.debug('set badge as', num); 72 | cordova.plugins.notification.badge.set(num); 73 | } 74 | }); 75 | } 76 | }; 77 | 78 | // app resume event 79 | document.addEventListener('resume', function onResume() { 80 | $log.log('app on resume'); 81 | if ($scope.loginName !== null) { 82 | $rootScope.getMessageCount(); 83 | } 84 | }, false); 85 | 86 | // logout 87 | $rootScope.$on('logout', function() { 88 | $log.debug('logout broadcast handle'); 89 | $scope.loginName = null; 90 | $scope.messagesCount = 0; 91 | setBadge(0); 92 | }); 93 | 94 | // update unread messages count 95 | $rootScope.$on('messagesMarkedAsRead', function() { 96 | $log.debug('message marked as read broadcast handle'); 97 | $scope.messagesCount = 0; 98 | setBadge($scope.messagesCount); 99 | // reset badge 100 | Push.setBadge($scope.messagesCount); 101 | }); 102 | 103 | // login action callback 104 | var loginCallback = function(response) { 105 | $ionicLoading.hide(); 106 | $scope.loginName = response.loginname; 107 | $rootScope.getMessageCount(); 108 | }; 109 | 110 | // on hold login action 111 | $scope.onHoldLogin = function() { 112 | $scope.processing = true; 113 | if(window.cordova && window.cordova.plugins.clipboard) { 114 | cordova.plugins.clipboard.paste(function (text) { 115 | $scope.processing = false; 116 | if (text) { 117 | $log.log('get Access Token', text); 118 | $ionicLoading.show(); 119 | User.login(text).$promise.then(loginCallback, $rootScope.requestErrorHandler()); 120 | } else { 121 | $ionicLoading.show({ 122 | noBackdrop: true, 123 | template: '粘贴板无内容', 124 | duration: 1000 125 | }); 126 | } 127 | }); 128 | 129 | // track event 130 | if (window.analytics) { 131 | window.analytics.trackEvent('User', 'clipboard login'); 132 | } 133 | } else { 134 | $log.debug('no clipboad plugin'); 135 | } 136 | }; 137 | 138 | // assign tabs 139 | $scope.tabs = Tabs; 140 | 141 | // do login 142 | $scope.login = function() { 143 | if ($scope.processing) { 144 | return; 145 | } 146 | if(window.cordova && window.cordova.plugins.barcodeScanner) { 147 | var loginPrompt = $ionicPopup.show({ 148 | template: 'PC端登录cnodejs.org后,扫描设置页面的Access Token二维码即可完成登录', 149 | title: '扫码登录', 150 | scope: $scope, 151 | buttons: [ 152 | { 153 | text: '我知道了', 154 | type: 'button-positive', 155 | onTap: function(e) { 156 | e.preventDefault(); 157 | loginPrompt.close(); 158 | dologin(); 159 | } 160 | } 161 | ] 162 | }); 163 | } else { 164 | // auto login if in debug mode 165 | if (ENV.debug) { 166 | $ionicLoading.show(); 167 | User.login(ENV.accessToken).$promise.then(loginCallback, $rootScope.requestErrorHandler()); 168 | } else { 169 | $scope.data = {}; 170 | // show login popup if no barcodeScanner in pc browser 171 | var loginPopup = $ionicPopup.show({ 172 | template: '', 173 | title: '输入Access Token', 174 | subTitle: 'PC端登录cnodejs.org后,在设置页可以找到Access Token', 175 | scope: $scope, 176 | buttons: [ 177 | { text: '取消' }, 178 | { 179 | text: '提交', 180 | type: 'button-positive', 181 | onTap: function(e) { 182 | e.preventDefault(); 183 | if ($scope.data.token) { 184 | User.login($scope.data.token).$promise.then(function(response) { 185 | loginPopup.close(); 186 | loginCallback(response); 187 | }, $rootScope.requestErrorHandler()); 188 | } 189 | } 190 | } 191 | ] 192 | }); 193 | } 194 | } 195 | }; 196 | var dologin = function() { 197 | $scope.processing = true; 198 | $timeout(function() { 199 | $scope.processing = false; 200 | }, 500); 201 | cordova.plugins.barcodeScanner.scan( 202 | function (result) { 203 | $scope.processing = false; 204 | if (!result.cancelled) { 205 | $log.log('get Access Token', result.text); 206 | $ionicLoading.show(); 207 | User.login(result.text).$promise.then(loginCallback, $rootScope.requestErrorHandler()); 208 | } 209 | }, 210 | function (error) { 211 | $scope.processing = false; 212 | $ionicLoading.show({ 213 | noBackdrop: true, 214 | template: 'Scanning failed: ' + error, 215 | duration: 1000 216 | }); 217 | } 218 | ); 219 | 220 | // track event 221 | if (window.analytics) { 222 | window.analytics.trackEvent('User', 'scan login'); 223 | } 224 | }; 225 | }); 226 | -------------------------------------------------------------------------------- /app/js/controllers/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.controllers:MessagesCtrl 6 | * @description 7 | * # MessagesCtrl 8 | * Main Controller of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.controllers') 12 | .controller('MessagesCtrl', function($scope, $log, $stateParams, $rootScope, Messages) { 13 | $log.log('messages ctrl'); 14 | 15 | // before enter view event 16 | $scope.$on('$ionicView.beforeEnter', function() { 17 | // track view 18 | if (window.analytics) { 19 | window.analytics.trackView('messages view'); 20 | } 21 | 22 | // load messages 23 | loadMessages(); 24 | }); 25 | 26 | var loadMessages = function() { 27 | Messages.getMessages().$promise.then(function(response) { 28 | $scope.messages = response.data; 29 | if ($scope.messages.hasnot_read_messages.length === 0) { 30 | $rootScope.$broadcast('messagesMarkedAsRead'); 31 | } else { 32 | Messages.markAll().$promise.then(function(response) { 33 | $log.debug('mark all response:', response); 34 | if (response.success) { 35 | $rootScope.$broadcast('messagesMarkedAsRead'); 36 | } 37 | }, function(response) { 38 | $log.debug('mark all response error:', response); 39 | }); 40 | } 41 | }, function(response) { 42 | $log.debug('get messages response error:', response); 43 | }); 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /app/js/controllers/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.controllers:SettingsCtrl 6 | * @description 7 | * # SettingsCtrl 8 | * Main Controller of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.controllers') 12 | .controller('SettingsCtrl', function($scope, $log, ENV, Settings) { 13 | $log.log('settings ctrl'); 14 | 15 | // before enter view event 16 | $scope.$on('$ionicView.beforeEnter', function() { 17 | // track view 18 | if (window.analytics) { 19 | window.analytics.trackView('settings view'); 20 | } 21 | }); 22 | 23 | $scope.now = new Date(); 24 | 25 | // mail feedback 26 | var feedbackMailAddr = 'hi@lanceli.com'; 27 | var feedbackMailSubject = 'CNode社区 Feedback v' + ENV.version; 28 | var device = ionic.Platform.device(); 29 | var feedbackMailBody = device.platform + ' ' + device.version + ' | ' + device.model; 30 | $scope.feedback = function() { 31 | if (window.cordova && window.cordova.plugins.email) { 32 | window.cordova.plugins.email.open({ 33 | to: feedbackMailAddr, 34 | subject: feedbackMailSubject, 35 | body: feedbackMailBody 36 | }); 37 | } else { 38 | window.open('mailto:' + feedbackMailAddr + '?subject=' + feedbackMailSubject); 39 | } 40 | }; 41 | 42 | // save settings on destroy 43 | $scope.$on('$stateChangeStart', function(){ 44 | $log.debug('settings controller on $stateChangeStart'); 45 | Settings.save(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /app/js/controllers/topic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.controllers:TopicCtrl 6 | * @description 7 | * # TopicCtrl 8 | * Topic Controller of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.controllers') 12 | .controller('TopicCtrl', function(ENV, $scope, $rootScope, $stateParams, $timeout, $ionicLoading, $ionicActionSheet, $ionicScrollDelegate, $log, Topics, Topic, User) { 13 | $log.debug('topic ctrl', $stateParams); 14 | var id = $stateParams.id; 15 | var topic = Topics.getById(id); 16 | $scope.topic = topic; 17 | 18 | // before enter view event 19 | $scope.$on('$ionicView.beforeEnter', function() { 20 | // track view 21 | if (window.analytics) { 22 | window.analytics.trackView('topic view'); 23 | } 24 | }); 25 | 26 | // load topic data 27 | $scope.loadTopic = function(reload) { 28 | var topicResource; 29 | if (reload === true) { 30 | topicResource = Topic.get(id); 31 | } else { 32 | topicResource = Topic.getById(id); 33 | } 34 | return topicResource.$promise.then(function(response) { 35 | $scope.topic = response.data; 36 | }, $rootScope.requestErrorHandler({ 37 | noBackdrop: true 38 | }, function() { 39 | $scope.loadError = true; 40 | }) 41 | ); 42 | }; 43 | $scope.loadTopic(); 44 | 45 | // do refresh 46 | $scope.doRefresh = function() { 47 | return $scope.loadTopic(true).then(function(response) { 48 | $log.debug('do refresh complete'); 49 | }, function() { 50 | }).finally(function() { 51 | $scope.$broadcast('scroll.refreshComplete'); 52 | }); 53 | }; 54 | 55 | $scope.replyData = { 56 | content: '' 57 | }; 58 | 59 | // save reply 60 | $scope.saveReply = function() { 61 | $log.debug('new reply data:', $scope.replyData); 62 | $ionicLoading.show(); 63 | Topic.saveReply(id, $scope.replyData).$promise.then(function(response) { 64 | $ionicLoading.hide(); 65 | $scope.replyData.content = ''; 66 | $log.debug('post reply response:', response); 67 | $scope.loadTopic(true).then(function() { 68 | $ionicScrollDelegate.scrollBottom(); 69 | }); 70 | }, $rootScope.requestErrorHandler); 71 | }; 72 | 73 | // show actions 74 | $scope.showActions = function(reply) { 75 | var currentUser = User.getCurrentUser(); 76 | if (currentUser.loginname === undefined || currentUser.loginname === reply.author.loginname) { 77 | return; 78 | } 79 | $log.debug('action reply:', reply); 80 | var upLabel = '赞'; 81 | // detect if current user already do up 82 | if (reply.ups.indexOf(currentUser.id) !== -1) { 83 | upLabel = '已赞'; 84 | } 85 | var replyContent = '@' + reply.author.loginname; 86 | $ionicActionSheet.show({ 87 | buttons: [ 88 | {text: '回复'}, 89 | {text: upLabel} 90 | ], 91 | titleText: replyContent, 92 | cancel: function() { 93 | }, 94 | buttonClicked: function(index) { 95 | 96 | // reply to someone 97 | if (index === 0) { 98 | $scope.replyData.content = replyContent + ' '; 99 | $scope.replyData.reply_id = reply.id; 100 | $timeout(function() { 101 | document.querySelector('.reply-new input').focus(); 102 | }, 1); 103 | } 104 | 105 | // up reply 106 | if (index === 1) { 107 | Topic.upReply(reply.id).$promise.then(function(response) { 108 | $log.debug('up reply response:', response); 109 | $ionicLoading.show({ 110 | noBackdrop: true, 111 | template: response.action === 'up' ? '点赞成功' : '点赞已取消', 112 | duration: 1000 113 | }); 114 | }, $rootScope.requestErrorHandler({ 115 | noBackdrop: true, 116 | })); 117 | } 118 | return true; 119 | } 120 | }); 121 | }; 122 | 123 | // share topic 124 | $scope.shareTopic = function() { 125 | if (window.plugins && window.plugins.socialsharing) { 126 | window.plugins.socialsharing.available(function(isAvailable) { 127 | if (isAvailable) { 128 | window.plugins.socialsharing.share(topic.title, 'CNode社区话题分享', 'http://ww2.sinaimg.cn/large/658e3191gw1eyw4vrjbkaj2040040mx4.jpg', ENV.domain + '/topic/' + topic.id); 129 | } else { 130 | alert('分享失败'); 131 | } 132 | }); 133 | } else { 134 | window.open(ENV.domain + '/topic/' + topic.id); 135 | } 136 | }; 137 | }); 138 | -------------------------------------------------------------------------------- /app/js/controllers/topics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.controllers:TopicsCtrl 6 | * @description 7 | * # TopicsCtrl 8 | * Topics Controller of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.controllers') 12 | .controller('TopicsCtrl', function($scope, $rootScope, $stateParams, $ionicLoading, $ionicModal, $timeout, $state, $location, $log, Topics, Tabs) { 13 | $log.debug('topics ctrl', $stateParams); 14 | 15 | // before enter view event 16 | $scope.$on('$ionicView.beforeEnter', function() { 17 | // track view 18 | if (window.analytics) { 19 | window.analytics.trackView('topics view'); 20 | } 21 | }); 22 | 23 | $scope.currentTab = Topics.currentTab(); 24 | 25 | // check if tab is changed 26 | if ($stateParams.tab !== Topics.currentTab()) { 27 | $scope.currentTab = Topics.currentTab($stateParams.tab); 28 | // reset data if tab is changed 29 | Topics.resetData(); 30 | } 31 | 32 | $scope.topics = Topics.getTopics(); 33 | 34 | // pagination 35 | $scope.hasNextPage = Topics.hasNextPage(); 36 | $scope.loadError = false; 37 | $log.debug('page load, has next page ? ', $scope.hasNextPage); 38 | $scope.doRefresh = function() { 39 | Topics.currentTab($stateParams.tab); 40 | $log.debug('do refresh'); 41 | Topics.refresh().$promise.then(function(response) { 42 | $log.debug('do refresh complete'); 43 | $scope.topics = response.data; 44 | $scope.hasNextPage = true; 45 | $scope.loadError = false; 46 | }, $rootScope.requestErrorHandler({ 47 | noBackdrop: true 48 | }, function() { 49 | $scope.loadError = true; 50 | }) 51 | ).finally(function() { 52 | $scope.$broadcast('scroll.refreshComplete'); 53 | }); 54 | }; 55 | $scope.loadMore = function() { 56 | $log.debug('load more'); 57 | Topics.pagination().$promise.then(function(response) { 58 | $log.debug('load more complete'); 59 | $scope.hasNextPage = false; 60 | $scope.loadError = false; 61 | $timeout(function() { 62 | $scope.hasNextPage = Topics.hasNextPage(); 63 | $log.debug('has next page ? ', $scope.hasNextPage); 64 | }, 100); 65 | $scope.topics = $scope.topics.concat(response.data); 66 | }, $rootScope.requestErrorHandler({ 67 | noBackdrop: true 68 | }, function() { 69 | $scope.loadError = true; 70 | }) 71 | ).finally(function() { 72 | $scope.$broadcast('scroll.infiniteScrollComplete'); 73 | }); 74 | }; 75 | 76 | // Create the new topic modal that we will use later 77 | $ionicModal.fromTemplateUrl('templates/newTopic.html', { 78 | tabs: Tabs, 79 | scope: $scope 80 | }).then(function(modal) { 81 | $scope.newTopicModal = modal; 82 | }); 83 | 84 | $scope.newTopicData = { 85 | tab: 'share', 86 | title: '', 87 | content: '' 88 | }; 89 | $scope.newTopicId; 90 | 91 | // save new topic 92 | $scope.saveNewTopic = function() { 93 | $log.debug('new topic data:', $scope.newTopicData); 94 | $ionicLoading.show(); 95 | Topics.saveNewTopic($scope.newTopicData).$promise.then(function(response) { 96 | $ionicLoading.hide(); 97 | $scope.newTopicId = response['topic_id']; 98 | $scope.closeNewTopicModal(); 99 | $timeout(function() { 100 | $state.go('app.topic', { 101 | id: $scope.newTopicId 102 | }); 103 | $timeout(function() { 104 | $scope.doRefresh(); 105 | }, 300); 106 | }, 300); 107 | }, $rootScope.requestErrorHandler); 108 | }; 109 | $scope.$on('modal.hidden', function() { 110 | // Execute action 111 | if ($scope.newTopicId) { 112 | $timeout(function() { 113 | $location.path('/app/topic/' + $scope.newTopicId); 114 | }, 300); 115 | } 116 | }); 117 | // show new topic modal 118 | $scope.showNewTopicModal = function() { 119 | 120 | // track view 121 | if (window.analytics) { 122 | window.analytics.trackView('new topic view'); 123 | } 124 | 125 | if(window.StatusBar) { 126 | StatusBar.styleDefault(); 127 | } 128 | $scope.newTopicModal.show(); 129 | }; 130 | 131 | // close new topic modal 132 | $scope.closeNewTopicModal = function() { 133 | if(window.StatusBar) { 134 | StatusBar.styleLightContent(); 135 | } 136 | $scope.newTopicModal.hide(); 137 | }; 138 | }); 139 | -------------------------------------------------------------------------------- /app/js/controllers/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.controllers:UserCtrl 6 | * @description 7 | * # UserCtrl 8 | * Main Controller of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.controllers') 12 | .controller('UserCtrl', function($scope, $rootScope, $log, $stateParams, $state, User) { 13 | $log.log('user ctrl'); 14 | var loginName = $stateParams.loginname; 15 | 16 | // before enter view event 17 | $scope.$on('$ionicView.beforeEnter', function() { 18 | // track view 19 | if (window.analytics) { 20 | window.analytics.trackView('user view'); 21 | } 22 | 23 | // load user data 24 | $scope.loadUser(true); 25 | }); 26 | 27 | // load user data 28 | $scope.loadUser = function(reload) { 29 | var userResource; 30 | if (reload === true) { 31 | userResource = User.get(loginName); 32 | } else { 33 | userResource = User.getByLoginName(loginName); 34 | } 35 | return userResource.$promise.then(function(response) { 36 | $scope.user = response.data; 37 | }); 38 | }; 39 | 40 | // do refresh 41 | $scope.doRefresh = function() { 42 | return $scope.loadUser(true).then(function(response) { 43 | $log.debug('do refresh complete'); 44 | }, function() { 45 | }).finally(function() { 46 | $scope.$broadcast('scroll.refreshComplete'); 47 | }); 48 | }; 49 | 50 | // reload user info from server if is current user view 51 | var currentUser = User.getCurrentUser(); 52 | if (loginName === currentUser.loginname) { 53 | User.get(loginName).$promise.then(function(response) { 54 | $scope.user = response.data; 55 | }); 56 | } 57 | 58 | // logout action 59 | $scope.logout = function() { 60 | $log.debug('logout button action'); 61 | User.logout(); 62 | $rootScope.$broadcast('logout'); 63 | 64 | // track event 65 | if (window.analytics) { 66 | window.analytics.trackEvent('User', 'logout'); 67 | } 68 | }; 69 | }); 70 | -------------------------------------------------------------------------------- /app/js/directives/resetImg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('cnodejs.directives').directive( 4 | // Collection-repeat image recycling while loading 5 | // https://github.com/driftyco/ionic/issues/1742 6 | 'resetImg', function ($document) { 7 | return { 8 | restrict: 'A', 9 | link: function($scope, $element, $attributes) { 10 | var applyNewSrc = function (src) { 11 | var newImg = $element.clone(true); 12 | 13 | newImg.attr('src', src); 14 | $element.replaceWith(newImg); 15 | $element = newImg; 16 | }; 17 | 18 | $attributes.$observe('src', applyNewSrc); 19 | $attributes.$observe('ngSrc', applyNewSrc); 20 | } 21 | }; 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /app/js/filters/topic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.filters:tabName 6 | * @description 7 | * # tabName 8 | * tab name filter of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.filters') 12 | .filter('link', function($sce) { 13 | return function(content) { 14 | if (typeof content === 'string') { 15 | var userLinkRegex = /href="\/user\/([\S]+)"/gi; 16 | var noProtocolSrcRegex = /src="\/\/([\S]+)"/gi; 17 | var externalLinkRegex = /href="((?!#\/user\/)[\S]+)"/gi; 18 | return $sce.trustAsHtml( 19 | content 20 | .replace(userLinkRegex, 'href="#/user/$1"') 21 | .replace(noProtocolSrcRegex, 'src="https://$1"') 22 | .replace(externalLinkRegex, "onClick=\"InAppBrowser.open('$1', '_blank', 'location=yes')\"") 23 | ); 24 | } 25 | return content; 26 | }; 27 | }) 28 | .filter('tabName', function(Tabs) { 29 | return function(tab) { 30 | for (var i in Tabs) { 31 | if (Tabs[i].value === tab) { 32 | return Tabs[i].label; 33 | } 34 | } 35 | }; 36 | }) 37 | .filter('protocol', function(ENV) { 38 | return function(src) { 39 | // filter avatar link 40 | if (/^\/agent\?/gi.test(src)) { 41 | return 'https://cnodejs.org' + src; 42 | } 43 | // add https protocol 44 | if (/^\/\//gi.test(src)) { 45 | return 'https:' + src; 46 | } else { 47 | return src; 48 | } 49 | }; 50 | }); 51 | -------------------------------------------------------------------------------- /app/js/services/messages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:MessagesService 6 | * @description 7 | * # MessagesService 8 | * Message Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Messages', function(ENV, $resource, $log, User) { 13 | var api = ENV.domain + ENV.api; 14 | var messages = {}; 15 | var messagesCount = 0; 16 | var resource = $resource(api + '/messages', null, { 17 | count: { 18 | method: 'get', 19 | url: api + '/message/count' 20 | }, 21 | markAll: { 22 | method: 'post', 23 | url: api + '/message/mark_all' 24 | } 25 | }); 26 | return { 27 | currentMessageCount: function() { 28 | return messagesCount; 29 | }, 30 | getMessageCount: function() { 31 | $log.debug('get messages count'); 32 | var currentUser = User.getCurrentUser(); 33 | return resource.count({ 34 | accesstoken: currentUser.accesstoken 35 | }); 36 | }, 37 | getMessages: function() { 38 | $log.debug('get messages'); 39 | var currentUser = User.getCurrentUser(); 40 | return resource.get({ 41 | accesstoken: currentUser.accesstoken 42 | }); 43 | return messages; 44 | }, 45 | markAll: function() { 46 | $log.debug('mark all as read'); 47 | var currentUser = User.getCurrentUser(); 48 | return resource.markAll({ 49 | accesstoken: currentUser.accesstoken 50 | }, function(response) { 51 | $log.debug('marked messages as read:', response); 52 | messagesCount = 0; 53 | }); 54 | } 55 | }; 56 | }); 57 | -------------------------------------------------------------------------------- /app/js/services/push.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:PushService 6 | * @description 7 | * # PushService 8 | * Push Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Push', function(ENV, $log) { 13 | var push; 14 | return { 15 | setBadge: function(badge) { 16 | if (push) { 17 | $log.debug('jpush: set badge', badge); 18 | plugins.jPushPlugin.setBadge(badge); 19 | } 20 | }, 21 | setAlias: function(alias) { 22 | if (push) { 23 | $log.debug('jpush: set alias', alias); 24 | plugins.jPushPlugin.setAlias(alias); 25 | } 26 | }, 27 | check: function() { 28 | if (window.jpush && push) { 29 | plugins.jPushPlugin.receiveNotificationIniOSCallback(window.jpush); 30 | window.jpush = null; 31 | } 32 | }, 33 | init: function(notificationCallback) { 34 | push = window.plugins && window.plugins.jPushPlugin; 35 | if (push) { 36 | $log.debug('jpush: init'); 37 | plugins.jPushPlugin.init(); 38 | plugins.jPushPlugin.setDebugMode(ENV.debug); 39 | plugins.jPushPlugin.openNotificationInAndroidCallback = notificationCallback; 40 | plugins.jPushPlugin.receiveNotificationIniOSCallback = notificationCallback; 41 | } 42 | } 43 | }; 44 | }); 45 | -------------------------------------------------------------------------------- /app/js/services/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:SettingsService 6 | * @description 7 | * # SettingsService 8 | * Message Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Settings', function(ENV, $resource, $log, Storage) { 13 | var storageKey = 'settings'; 14 | var settings = Storage.get(storageKey) || { 15 | sendFrom: false, 16 | saverMode: true 17 | }; 18 | return { 19 | getSettings: function() { 20 | $log.debug('get settings', settings); 21 | return settings; 22 | }, 23 | save: function() { 24 | Storage.set(storageKey, settings); 25 | } 26 | }; 27 | }); 28 | -------------------------------------------------------------------------------- /app/js/services/storage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:StorageService 6 | * @description 7 | * # StorageService 8 | * Storage Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Storage', function(ENV, $log) { 13 | 14 | return { 15 | set: function(key, data) { 16 | return window.localStorage.setItem(key, window.JSON.stringify(data)); 17 | }, 18 | get: function(key) { 19 | return window.JSON.parse(window.localStorage.getItem(key)); 20 | }, 21 | remove: function(key) { 22 | return window.localStorage.removeItem(key); 23 | } 24 | }; 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /app/js/services/tabs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:TabsService 6 | * @description 7 | * # TabsService 8 | * Tabs Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Tabs', function() { 13 | return [ 14 | { 15 | value: 'all', 16 | label: '最新' 17 | }, 18 | { 19 | value: 'share', 20 | label: '分享' 21 | }, 22 | { 23 | value: 'ask', 24 | label: '问答' 25 | }, 26 | { 27 | value: 'job', 28 | label: '招聘' 29 | }, 30 | { 31 | value: undefined, 32 | label: '其他' 33 | } 34 | ]; 35 | }); 36 | -------------------------------------------------------------------------------- /app/js/services/topic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:TopicService 6 | * @description 7 | * # TopicService 8 | * Topic Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Topic', function(ENV, $resource, $log, $q, User, Settings) { 13 | var api = ENV.domain + ENV.api; 14 | var topic; 15 | var resource = $resource(api + '/topic/:id', { 16 | id: '@id', 17 | }, { 18 | reply: { 19 | method: 'post', 20 | url: api + '/topic/:topicId/replies' 21 | }, 22 | upReply: { 23 | method: 'post', 24 | url: api + '/reply/:replyId/ups' 25 | } 26 | }); 27 | return { 28 | getById: function(id) { 29 | if (topic !== undefined && topic.id === id) { 30 | var topicDefer = $q.defer(); 31 | topicDefer.resolve({ 32 | data: topic 33 | }); 34 | return { 35 | $promise: topicDefer.promise 36 | }; 37 | } 38 | return this.get(id); 39 | }, 40 | get: function(id) { 41 | return resource.get({id: id}, function(response) { 42 | topic = response.data; 43 | }); 44 | }, 45 | saveReply: function(topicId, replyData) { 46 | var reply = angular.extend({}, replyData); 47 | var currentUser = User.getCurrentUser(); 48 | // add send from 49 | if (Settings.getSettings().sendFrom) { 50 | reply.content = replyData.content + '\n 自豪地采用 [CNodeJS ionic](https://github.com/lanceli/cnodejs-ionic)'; 51 | } 52 | return resource.reply({ 53 | topicId: topicId, 54 | accesstoken: currentUser.accesstoken 55 | }, reply 56 | ); 57 | }, 58 | upReply: function(replyId) { 59 | var currentUser = User.getCurrentUser(); 60 | return resource.upReply({ 61 | replyId: replyId, 62 | accesstoken: currentUser.accesstoken 63 | }, null, function(response) { 64 | if (response.success) { 65 | angular.forEach(topic.replies, function(reply, key) { 66 | if (reply.id === replyId) { 67 | if (response.action === 'up') { 68 | reply.ups.push(currentUser.id); 69 | } else { 70 | reply.ups.pop(); 71 | } 72 | } 73 | }); 74 | } 75 | } 76 | ); 77 | } 78 | }; 79 | }); 80 | -------------------------------------------------------------------------------- /app/js/services/topics.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:TopicsService 6 | * @description 7 | * # TopicsService 8 | * Topics Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('Topics', function(ENV, $resource, $log, User) { 13 | var api = ENV.domain + ENV.api; 14 | var topics = []; 15 | var currentTab = 'all'; 16 | var nextPage = 1; 17 | var hasNextPage = true; 18 | var resource = $resource(api + '/topics', { 19 | }, { 20 | query: { 21 | method: 'get', 22 | params: { 23 | tab: 'all', 24 | page: 1, 25 | limit: 10, 26 | mdrender: true 27 | }, 28 | timeout: 20000 29 | } 30 | }); 31 | var getTopics = function(tab, page, callback) { 32 | return resource.query({ 33 | tab: tab, 34 | page: page 35 | }, function(r) { 36 | $log.debug('get topics tab:', tab, 'page:', page, 'data:', r.data); 37 | return callback && callback(r); 38 | }); 39 | }; 40 | return { 41 | refresh: function() { 42 | return getTopics(currentTab, 1, function(response) { 43 | nextPage = 2; 44 | hasNextPage = true; 45 | topics = response.data; 46 | }); 47 | }, 48 | pagination: function() { 49 | return getTopics(currentTab, nextPage, function(response) { 50 | if (response.data.length < 10) { 51 | $log.debug('response data length', response.data.length); 52 | hasNextPage = false; 53 | } 54 | nextPage++; 55 | topics = topics.concat(response.data); 56 | }); 57 | }, 58 | currentTab: function(newTab) { 59 | if (typeof newTab !== 'undefined') { 60 | currentTab = newTab; 61 | } 62 | return currentTab; 63 | }, 64 | hasNextPage: function(has) { 65 | if (typeof has !== 'undefined') { 66 | hasNextPage = has; 67 | } 68 | return hasNextPage; 69 | }, 70 | resetData: function() { 71 | topics = []; 72 | nextPage = 1; 73 | hasNextPage = true; 74 | }, 75 | getTopics: function() { 76 | return topics; 77 | }, 78 | getById: function(id) { 79 | 80 | if (!!topics) { 81 | for (var i = 0; i < topics.length; i++) { 82 | if (topics[i].id === id) { 83 | return topics[i]; 84 | } 85 | } 86 | } else { 87 | return null; 88 | } 89 | }, 90 | saveNewTopic: function(newTopicData) { 91 | var currentUser = User.getCurrentUser(); 92 | return resource.save({accesstoken: currentUser.accesstoken}, newTopicData); 93 | } 94 | }; 95 | }); 96 | -------------------------------------------------------------------------------- /app/js/services/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc function 5 | * @name cnodejs.services:UserService 6 | * @description 7 | * # UserService 8 | * User Service of the cnodejs app 9 | */ 10 | 11 | angular.module('cnodejs.services') 12 | .factory('User', function(ENV, $resource, $log, $q, Storage, Push) { 13 | var api = ENV.domain + ENV.api; 14 | var storageKey = 'user'; 15 | var resource = $resource(api + '/accesstoken'); 16 | var userResource = $resource(api + '/user/:loginname', { 17 | loginname: '' 18 | }); 19 | var user = Storage.get(storageKey) || {}; 20 | return { 21 | login: function(accesstoken) { 22 | var $this = this; 23 | return resource.save({ 24 | accesstoken: accesstoken 25 | }, null, function(response) { 26 | $log.debug('post accesstoken:', response); 27 | user.accesstoken = accesstoken; 28 | $this.getByLoginName(response.loginname).$promise.then(function(r) { 29 | user = r.data; 30 | user.id = response.id; 31 | user.accesstoken = accesstoken; 32 | 33 | // set alias for jpush 34 | Push.setAlias(user.id); 35 | 36 | Storage.set(storageKey, user); 37 | }); 38 | user.loginname = response.loginname; 39 | }); 40 | }, 41 | logout: function() { 42 | user = {}; 43 | Storage.remove(storageKey); 44 | 45 | // unset alias for jpush 46 | Push.setAlias(''); 47 | }, 48 | getCurrentUser: function() { 49 | $log.debug('current user:', user); 50 | return user; 51 | }, 52 | getByLoginName: function(loginName) { 53 | if (user && loginName === user.loginname) { 54 | var userDefer = $q.defer(); 55 | $log.debug('get user info from storage:', user); 56 | userDefer.resolve({ 57 | data: user 58 | }); 59 | return { 60 | $promise: userDefer.promise 61 | }; 62 | } 63 | return this.get(loginName); 64 | }, 65 | get: function(loginName) { 66 | return userResource.get({ 67 | loginname: loginName 68 | }, function(response) { 69 | $log.debug('get user info:', response); 70 | if (user && user.loginname === loginName) { 71 | angular.extend(user, response.data); 72 | 73 | Storage.set(storageKey, user); 74 | } 75 | }); 76 | } 77 | }; 78 | }); 79 | -------------------------------------------------------------------------------- /app/templates/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

dev

19 |
20 | 21 | 22 | 23 | 登录 24 | 25 | 26 | 27 | 28 | {{loginName}} 29 | 30 | 31 | 32 | 我的消息 33 | 34 | {{messagesCount > 9 ? 'N' : messagesCount}} 35 | 36 | 37 | 38 | 板块 39 | 40 | 41 | {{tab.label}} 42 | 43 | 44 | 其他 45 | 46 | 47 | 设置 48 | 49 | 50 | 51 | 52 | 53 |

github.com/lanceli

54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /app/templates/messages.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 未读消息 6 | 7 | 8 |

9 | {{message.author.loginname}} 10 | 在 11 | {{message.topic.title}} 12 | 中@了你 13 |

14 |

15 | {{message.author.loginname}} 16 | 回复了 17 | {{message.topic.title}} 18 |

19 |
20 | 21 | 已读消息 22 | 23 | 24 |

25 | {{message.author.loginname}} 26 | 在 27 | {{message.topic.title}} 28 | 中@了你 29 |

30 |

31 | {{message.author.loginname}} 32 | 回复了 33 | {{message.topic.title}} 34 |

35 |
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /app/templates/newTopic.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /app/templates/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 小尾巴 8 | 14 | 15 | 16 | 省流量 17 | 23 | 24 | 25 | 关于 26 | 27 | 28 | 当前版本 29 | 30 | {{ENV.version}} 31 | 32 | 33 | 34 | 意见反馈 35 | 36 | Email 37 | 38 | 39 | 40 | 评分鼓励 41 | 42 | 43 | 关于作者 44 | 45 | 46 | 关于CNode社区 47 | 48 | 49 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/templates/topic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |

{{topic.title}}

11 |
12 | 13 | 14 | 15 | {{topic.author.loginname}} 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 加载中... 27 | 28 | 29 | 加载失败 30 | 31 | 32 | 33 | 暂无评论 34 | 35 | 36 | 37 |

38 | 39 | {{reply.author.loginname}} 40 | 41 | 42 | 43 | 44 | 45 | {{reply.ups.length}} 46 |   47 | 48 |

49 |
50 |
51 |
52 |
53 |
54 | 55 | 58 | 61 | 62 |
63 | -------------------------------------------------------------------------------- /app/templates/topics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 |

{{topic.title}}

17 |

18 | {{topic.tab|tabName}} 19 | 置顶 20 | 精华 21 | {{topic.author.loginname}} 22 | 23 | 24 |

25 |
26 |
27 | 31 | 32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /app/templates/user.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{user.loginname}} 14 | {{user.score}}积分 15 |

16 | 注册于:{{user.create_at | amDateFormat:'YYYY-MM-DD hh:mm:ss'}} 17 |

18 |
19 | 20 | 最近主题 21 | 22 | 23 |

{{topic.title}}

24 |

25 | {{topic.author.loginname}} 26 | 27 | 28 |

29 |
30 | 31 | 最近回复 32 | 33 | 34 | 35 |

{{topic.title}}

36 |

37 | {{topic.author.loginname}} 38 | 39 | 40 |

41 |
42 |
43 |
44 |
45 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cnodejs", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "ionic": "driftyco/ionic-bower#v1.1.1", 6 | "angular-moment": "~0.10.3", 7 | "angular-resource": "~1.5.0" 8 | }, 9 | "devDependencies": { 10 | "angular-mocks": "~1.4.0", 11 | "angular-scenario": "~1.5.0" 12 | }, 13 | "resolutions": { 14 | "angular": "1.4.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | CNodeJs 14 | 15 | Ionic app for https://cnodejs.org 16 | 17 | 18 | Lance Li 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CNodeJs 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /demo/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MASTER_DIR=$PWD 4 | PAGES_DIR=$MASTER_DIR/_demo 5 | GH_ORIGIN=git@github.com:lanceli/cnodejs-ionic.git 6 | CODING_ORIGIN=git@coding.net:lanceli/cnodejs-ionic.git 7 | 8 | # delete gh-pages branch if it exists 9 | git branch | grep gh-pages && git branch -D gh-pages 10 | rm -rf $PAGES_DIR 11 | git clone . $PAGES_DIR 12 | cd $PAGES_DIR 13 | git checkout --orphan gh-pages 14 | git rm -rf . 15 | git remote rm origin 16 | git remote rm coding 17 | git remote add origin $GH_ORIGIN 18 | git remote add coding $CODING_ORIGIN 19 | cd $MASTER_DIR 20 | cp $MASTER_DIR/demo/demo.html $MASTER_DIR/app/index.html 21 | cp $MASTER_DIR/demo/index.html $PAGES_DIR/index.html 22 | grunt compress 23 | git checkout $MASTER_DIR/app/index.html 24 | cp -R $MASTER_DIR/www $PAGES_DIR 25 | cp $MASTER_DIR/resources/ios/icons/Icon@2x.png $PAGES_DIR/logo.png 26 | cd $PAGES_DIR 27 | git add --all 28 | git commit -m "Content creation" 29 | 30 | # Push quietly so the token isn't seen in the CI output 31 | git push -fq origin gh-pages 32 | git push -fq coding gh-pages 33 | cd .. 34 | rm -rf _demo 35 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CNodeJS App 6 | 131 | 132 | 133 | 146 |
147 |
148 | 149 | FULLSCREEN 150 |
151 |
152 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /hooks/README.md: -------------------------------------------------------------------------------- 1 | 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /hooks/after_platform_add/install_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Install all plugins listed in package.json 5 | */ 6 | var exec = require('child_process').exec; 7 | var path = require('path'); 8 | var sys = require('sys'); 9 | 10 | var packageJSON = require('../../package.json'); 11 | var cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova'; 12 | var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd); 13 | 14 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 15 | packageJSON.cordovaPlugins.forEach(function (plugin) { 16 | exec(script + ' plugin add ' + plugin, function (error, stdout, stderr) { 17 | sys.puts(stdout); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /hooks/after_plugin_add/register_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Push plugins to cordovaPlugins array after_plugin_add 5 | */ 6 | var fs = require('fs'); 7 | var _ = require('lodash'); 8 | var packageJSON = require('../../package.json'); 9 | 10 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 11 | _.each(process.env.CORDOVA_PLUGINS.split(','), function (plugin) { 12 | if (! _.contains(packageJSON.cordovaPlugins, plugin)) { 13 | packageJSON.cordovaPlugins.push(plugin); 14 | } 15 | }); 16 | 17 | fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2)); 18 | -------------------------------------------------------------------------------- /hooks/after_plugin_rm/deregister_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Remove plugins from cordovaPlugins array after_plugin_rm 5 | */ 6 | var fs = require('fs'); 7 | var _ = require('lodash'); 8 | var packageJSON = require('../../package.json'); 9 | 10 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 11 | _.each(process.env.CORDOVA_PLUGINS.split(','), function (plugin) { 12 | _.remove(packageJSON.cordovaPlugins, function (p) { return p === plugin; }); 13 | }); 14 | 15 | fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2)); 16 | -------------------------------------------------------------------------------- /hooks/after_prepare/010_add_platform_class.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Add Platform Class 4 | // v1.0 5 | // Automatically adds the platform class to the body tag 6 | // after the `prepare` command. By placing the platform CSS classes 7 | // directly in the HTML built for the platform, it speeds up 8 | // rendering the correct layout/style for the specific platform 9 | // instead of waiting for the JS to figure out the correct classes. 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | 14 | var rootdir = process.argv[2]; 15 | 16 | function addPlatformBodyTag(indexPath, platform) { 17 | // add the platform class to the body tag 18 | try { 19 | var platformClass = 'platform-' + platform; 20 | var cordovaClass = 'platform-cordova platform-webview'; 21 | 22 | var html = fs.readFileSync(indexPath, 'utf8'); 23 | 24 | var bodyTag = findBodyTag(html); 25 | if(!bodyTag) return; // no opening body tag, something's wrong 26 | 27 | if(bodyTag.indexOf(platformClass) > -1) return; // already added 28 | 29 | var newBodyTag = bodyTag; 30 | 31 | var classAttr = findClassAttr(bodyTag); 32 | if(classAttr) { 33 | // body tag has existing class attribute, add the classname 34 | var endingQuote = classAttr.substring(classAttr.length-1); 35 | var newClassAttr = classAttr.substring(0, classAttr.length-1); 36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; 37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr); 38 | 39 | } else { 40 | // add class attribute to the body tag 41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); 42 | } 43 | 44 | html = html.replace(bodyTag, newBodyTag); 45 | 46 | fs.writeFileSync(indexPath, html, 'utf8'); 47 | 48 | process.stdout.write('add to body class: ' + platformClass + '\n'); 49 | } catch(e) { 50 | process.stdout.write(e); 51 | } 52 | } 53 | 54 | function findBodyTag(html) { 55 | // get the body tag 56 | try{ 57 | return html.match(/])(.*?)>/gi)[0]; 58 | }catch(e){} 59 | } 60 | 61 | function findClassAttr(bodyTag) { 62 | // get the body tag's class attribute 63 | try{ 64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; 65 | }catch(e){} 66 | } 67 | 68 | if (rootdir) { 69 | 70 | // go through each of the platform directories that have been prepared 71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); 72 | 73 | for(var x=0; x=0.10.0" 47 | }, 48 | "scripts": { 49 | "test": "grunt test" 50 | }, 51 | "cordovaPlugins": [ 52 | "https://github.com/driftyco/ionic-plugins-keyboard.git", 53 | "cordova-plugin-statusbar", 54 | "cordova-plugin-console", 55 | "https://github.com/VersoSolutions/CordovaClipboard", 56 | "cordova-plugin-splashscreen", 57 | "cordova-plugin-inappbrowser", 58 | "https://github.com/katzer/cordova-plugin-badge.git", 59 | "cordova-plugin-dialogs", 60 | "https://github.com/katzer/cordova-plugin-email-composer.git", 61 | "https://github.com/danwilson/google-analytics-plugin.git", 62 | "https://github.com/lanceli/jpush-phonegap-plugin", 63 | "cordova-plugin-device", 64 | "phonegap-plugin-barcodescanner", 65 | "https://github.com/pluswave/SocialSharing-PhoneGap-Plugin" 66 | ], 67 | "cordovaPlatforms": [] 68 | } 69 | -------------------------------------------------------------------------------- /resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/Logo.png -------------------------------------------------------------------------------- /resources/android/drawable-hdpi/ic_action_next_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-hdpi/ic_action_next_item.png -------------------------------------------------------------------------------- /resources/android/drawable-hdpi/ic_action_previous_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-hdpi/ic_action_previous_item.png -------------------------------------------------------------------------------- /resources/android/drawable-hdpi/ic_action_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-hdpi/ic_action_remove.png -------------------------------------------------------------------------------- /resources/android/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /resources/android/drawable-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /resources/android/drawable-hdpi/shopper_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-hdpi/shopper_icon.png -------------------------------------------------------------------------------- /resources/android/drawable-land-hdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-land-hdpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-land-ldpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-land-ldpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-land-mdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-land-mdpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-land-xhdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-land-xhdpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /resources/android/drawable-mdpi/ic_action_next_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-mdpi/ic_action_next_item.png -------------------------------------------------------------------------------- /resources/android/drawable-mdpi/ic_action_previous_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-mdpi/ic_action_previous_item.png -------------------------------------------------------------------------------- /resources/android/drawable-mdpi/ic_action_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-mdpi/ic_action_remove.png -------------------------------------------------------------------------------- /resources/android/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /resources/android/drawable-port-hdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-port-hdpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-port-ldpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-port-ldpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-port-mdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-port-mdpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-port-xhdpi/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-port-xhdpi/screen.png -------------------------------------------------------------------------------- /resources/android/drawable-xhdpi/ic_action_next_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xhdpi/ic_action_next_item.png -------------------------------------------------------------------------------- /resources/android/drawable-xhdpi/ic_action_previous_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xhdpi/ic_action_previous_item.png -------------------------------------------------------------------------------- /resources/android/drawable-xhdpi/ic_action_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xhdpi/ic_action_remove.png -------------------------------------------------------------------------------- /resources/android/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /resources/android/drawable-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /resources/android/drawable-xxhdpi/ic_action_next_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xxhdpi/ic_action_next_item.png -------------------------------------------------------------------------------- /resources/android/drawable-xxhdpi/ic_action_previous_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xxhdpi/ic_action_previous_item.png -------------------------------------------------------------------------------- /resources/android/drawable-xxhdpi/ic_action_remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xxhdpi/ic_action_remove.png -------------------------------------------------------------------------------- /resources/android/drawable-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /resources/android/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable/icon.png -------------------------------------------------------------------------------- /resources/android/drawable/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable/launcher_icon.png -------------------------------------------------------------------------------- /resources/android/drawable/share_via_barcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable/share_via_barcode.png -------------------------------------------------------------------------------- /resources/android/drawable/shopper_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/drawable/shopper_icon.png -------------------------------------------------------------------------------- /resources/android/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/android/icon.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-40.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-72.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-76.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-Small.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon-Small@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon.png -------------------------------------------------------------------------------- /resources/ios/icons/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/Icon@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icons/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/icons/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lanceli/cnodejs-ionic/134bdcb17c4825cc8dd26d2f2b6f4ba5be0e3aee/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/spec/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: AppCtrl', function () { 4 | 5 | var should = chai.should(); 6 | 7 | // load the controller's module 8 | beforeEach(module('cnodejs')); 9 | 10 | var AppCtrl, 11 | scope; 12 | 13 | // Initialize the controller and a mock scope 14 | beforeEach(inject(function ($controller, $rootScope) { 15 | scope = $rootScope.$new(); 16 | AppCtrl = $controller('AppCtrl', { 17 | $scope: scope 18 | }); 19 | })); 20 | 21 | it('should attach a list of tabs to the scope', function () { 22 | scope.tabs.should.have.length(4); 23 | }); 24 | 25 | }); 26 | --------------------------------------------------------------------------------