├── .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 | [](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 |
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 |
51 |
52 |
53 | github.com/lanceli
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/templates/messages.html:
--------------------------------------------------------------------------------
1 |
2 |
38 |
39 |
--------------------------------------------------------------------------------
/app/templates/newTopic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 发布话题
7 |
8 |
9 |
10 |
11 |
12 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/templates/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
55 |
56 |
--------------------------------------------------------------------------------
/app/templates/topic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
54 |
55 |
58 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/templates/topics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
34 |
35 |
--------------------------------------------------------------------------------
/app/templates/user.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
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 |
--------------------------------------------------------------------------------