├── .firebase └── hosting.d3d3.cache ├── .firebaserc ├── .gitignore ├── LICENSE ├── README.md ├── SCAFFOLDING.md ├── angular.json ├── browserslist ├── capacitor.config.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── firebase.json ├── ionic-3 ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── config.xml ├── ionic.config.json ├── package.json ├── resources │ ├── android │ │ ├── icon │ │ │ ├── drawable-hdpi-icon.png │ │ │ ├── drawable-ldpi-icon.png │ │ │ ├── drawable-mdpi-icon.png │ │ │ ├── drawable-xhdpi-icon.png │ │ │ ├── drawable-xxhdpi-icon.png │ │ │ └── drawable-xxxhdpi-icon.png │ │ └── splash │ │ │ ├── drawable-land-hdpi-screen.png │ │ │ ├── drawable-land-ldpi-screen.png │ │ │ ├── drawable-land-mdpi-screen.png │ │ │ ├── drawable-land-xhdpi-screen.png │ │ │ ├── drawable-land-xxhdpi-screen.png │ │ │ ├── drawable-land-xxxhdpi-screen.png │ │ │ ├── drawable-port-hdpi-screen.png │ │ │ ├── drawable-port-ldpi-screen.png │ │ │ ├── drawable-port-mdpi-screen.png │ │ │ ├── drawable-port-xhdpi-screen.png │ │ │ ├── drawable-port-xxhdpi-screen.png │ │ │ └── drawable-port-xxxhdpi-screen.png │ ├── icon.png │ ├── icon.png.md5 │ ├── splash.png │ └── splash.png.md5 ├── sdk │ ├── index.ts │ ├── lb.config.ts │ ├── models │ │ ├── Answer.ts │ │ ├── BaseModels.ts │ │ ├── Question.ts │ │ ├── User.ts │ │ └── index.ts │ ├── services │ │ ├── core │ │ │ ├── auth.service.ts │ │ │ ├── base.service.ts │ │ │ ├── error.service.ts │ │ │ ├── index.ts │ │ │ └── search.params.ts │ │ ├── custom │ │ │ ├── Answer.ts │ │ │ ├── Question.ts │ │ │ ├── SDKModels.ts │ │ │ ├── User.ts │ │ │ ├── index.ts │ │ │ └── logger.service.ts │ │ └── index.ts │ └── storage │ │ ├── cookie.browser.ts │ │ ├── storage.browser.ts │ │ └── storage.swaps.ts ├── server.js ├── src │ ├── app │ │ ├── app.component.ts │ │ ├── app.html │ │ ├── app.module.ts │ │ ├── app.scss │ │ └── main.ts │ ├── assets │ │ ├── categories │ │ │ └── categories.json │ │ └── icon │ │ │ └── favicon.ico │ ├── declarations.d.ts │ ├── index.html │ ├── manifest.json │ ├── pages │ │ ├── learn-details │ │ │ ├── learn-details.html │ │ │ ├── learn-details.scss │ │ │ └── learn-details.ts │ │ ├── learn-feed │ │ │ ├── learn-feed.html │ │ │ ├── learn-feed.scss │ │ │ └── learn-feed.ts │ │ ├── manage-answer │ │ │ ├── manage-answer.html │ │ │ ├── manage-answer.scss │ │ │ └── manage-answer.ts │ │ ├── manage-question │ │ │ ├── manage-question.html │ │ │ ├── manage-question.scss │ │ │ └── manage-question.ts │ │ └── question-details │ │ │ ├── question-details.html │ │ │ ├── question-details.scss │ │ │ └── question-details.ts │ ├── service-worker.js │ ├── services │ │ ├── answer.service.ts │ │ ├── learn.model.ts │ │ ├── learn.service.ts │ │ └── question.service.ts │ └── theme │ │ ├── common.scss │ │ ├── common │ │ ├── header.scss │ │ ├── modals.scss │ │ ├── scroll-bar.scss │ │ └── side-menu.scss │ │ └── variables.scss ├── tsconfig.json └── tslint.json ├── ionic.config.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── app.service.spec.ts │ ├── app.service.ts │ ├── learn │ │ ├── categories │ │ │ ├── categories-listing-page │ │ │ │ ├── categories-listing-page.component.html │ │ │ │ ├── categories-listing-page.component.scss │ │ │ │ ├── categories-listing-page.component.spec.ts │ │ │ │ ├── categories-listing-page.component.ts │ │ │ │ └── categories-listing-page.resolver.ts │ │ │ ├── learn-categories-routing.module.ts │ │ │ └── learn-categories.module.ts │ │ └── category │ │ │ ├── category-details-page │ │ │ ├── category-details-page.component.html │ │ │ ├── category-details-page.component.scss │ │ │ ├── category-details-page.component.spec.ts │ │ │ ├── category-details-page.component.ts │ │ │ └── category-details-page.resolver.ts │ │ │ ├── learn-category-routing.module.ts │ │ │ └── learn-category.module.ts │ ├── models │ │ ├── answer.model.ts │ │ ├── category.model.ts │ │ └── question.model.ts │ └── questions │ │ ├── answer-question-modal │ │ ├── answer-question-modal.component.html │ │ ├── answer-question-modal.component.scss │ │ ├── answer-question-modal.component.spec.ts │ │ └── answer-question-modal.component.ts │ │ ├── ask-question-modal │ │ ├── ask-question-modal.component.html │ │ ├── ask-question-modal.component.scss │ │ ├── ask-question-modal.component.spec.ts │ │ └── ask-question-modal.component.ts │ │ ├── question-details-page │ │ ├── question-details-page.component.html │ │ ├── question-details-page.component.scss │ │ ├── question-details-page.component.spec.ts │ │ ├── question-details-page.component.ts │ │ └── question-details-page.resolver.ts │ │ ├── questions-routing.module.ts │ │ ├── questions-shared.module.ts │ │ └── questions.module.ts ├── assets │ ├── icon │ │ └── favicon.png │ └── shapes.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── global.scss ├── index.html ├── main.ts ├── polyfills.ts ├── test.ts ├── theme │ └── variables.scss └── zone-flags.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "q-and-a-ionic-tutorial" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | .tmp 7 | *.tmp 8 | *.tmp.* 9 | *.sublime-project 10 | *.sublime-workspace 11 | .DS_Store 12 | Thumbs.db 13 | UserInterfaceState.xcuserstate 14 | $RECYCLE.BIN/ 15 | 16 | *.log 17 | log.txt 18 | npm-debug.log* 19 | 20 | /.idea 21 | /.ionic 22 | /.sass-cache 23 | /.sourcemaps 24 | /.versions 25 | /.vscode 26 | /coverage 27 | /dist 28 | /node_modules 29 | /platforms 30 | /plugins 31 | /www 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 IonicThemes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build a complete mobile app with Ionic 2 | 3 | This repo is part of an ionic tutorial that explains how to build an ionic app with a question and answer format (Q&A). We will go through the core concepts of an Ionic Framework App such as Project Structure, Navigation, UI/UX and Data Integration with a [NestJs API](https://github.com/ionicthemes/questions-and-answers-tutorial-backend). 4 | 5 | This ionic tutorial includes a free ionic template you can reuse for your own project! 6 | 7 | 8 | **Please support this project by simply putting a Github star ⭐ 🙏** 9 | 10 | [Follow the complete step by step tutorial](https://ionicthemes.com/tutorials/about/build-a-complete-mobile-app-with-ionic-framework). 11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | ## Install the Ionic app 19 | ```sh 20 | npm install 21 | ``` 22 | 23 | ## Run the Ionic app on the browser 24 | 25 | ```sh 26 | ionic serve 27 | ``` 28 | 29 | ## Run the ionic app on your phone 30 | This app is built with Capacitor. Check the [getting started guide](https://capacitorjs.com/docs/getting-started) to see how to run it on your device. 31 | 32 | ## Demo 33 | - [Online demo](https://q-and-a-ionic-tutorial.web.app) 34 | - [Video preview](https://youtu.be/xj3Znnd4Evs) 35 | 36 | 37 | ## Get a complete Ionic 5 Starter App 38 | Did you know that we recently released [Ionic 5 Full Starter App PRO](https://ionicthemes.com/product/ionic5-full-starter-app-pro-version)? It has more than 125 carefully designed views and components, it will help you grasp best practices and the new concepts of Ionic 5 development. 39 | 40 | Try it both as a native app or as a full PWA on your mobile browser and **save yourself hundreds of hours of design and development**. 41 | 42 | 43 | -------------------------------------------------------------------------------- /SCAFFOLDING.md: -------------------------------------------------------------------------------- 1 | ## Learn 2 | ### learn/ 3 | ``` 4 | ionic generate module learn/categories/learn-categories --flat=true --routing=true 5 | 6 | ionic generate component learn/categories/categories-listing-page --module=/src/app/learn/categories/learn-categories.module.ts 7 | ``` 8 | 9 | ### learn/:category 10 | ``` 11 | ionic generate module learn/category/learn-category --flat=true --routing=true 12 | 13 | ionic generate component learn/category/category-details-page --module=/src/app/learn/category/learn-category.module.ts 14 | ``` 15 | 16 | 17 | ## Utils 18 | ``` 19 | ionic generate module utils/utils --flat=true 20 | ``` 21 | 22 | ## Questions 23 | ### questions/:slug 24 | ``` 25 | ionic generate module questions/questions --flat=true --routing=true 26 | ionic generate module questions/questions-shared --flat=true 27 | 28 | ionic generate component questions/question-details-page --module=/src/app/questions/questions.module.ts 29 | ``` 30 | 31 | #### app service AppService 32 | ``` 33 | ionic generate service app 34 | ``` 35 | 36 | #### questions/answer 37 | ``` 38 | ionic generate component questions/answer-question-modal --module=/src/app/questions/questions.module.ts 39 | ``` 40 | 41 | #### questions/ask 42 | ``` 43 | ionic generate component questions/ask-question-modal --module=/src/app/questions/questions-shared.module.ts --export=true 44 | ``` 45 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "defaultProject": "app", 5 | "newProjectRoot": "projects", 6 | "projects": { 7 | "app": { 8 | "root": "", 9 | "sourceRoot": "src", 10 | "projectType": "application", 11 | "prefix": "app", 12 | "schematics": {}, 13 | "architect": { 14 | "build": { 15 | "builder": "@angular-devkit/build-angular:browser", 16 | "options": { 17 | "outputPath": "www", 18 | "index": "src/index.html", 19 | "main": "src/main.ts", 20 | "polyfills": "src/polyfills.ts", 21 | "tsConfig": "tsconfig.app.json", 22 | "assets": [ 23 | { 24 | "glob": "**/*", 25 | "input": "src/assets", 26 | "output": "assets" 27 | }, 28 | { 29 | "glob": "**/*.svg", 30 | "input": "node_modules/ionicons/dist/ionicons/svg", 31 | "output": "./svg" 32 | } 33 | ], 34 | "styles": [ 35 | { 36 | "input": "src/theme/variables.scss" 37 | }, 38 | { 39 | "input": "src/global.scss" 40 | } 41 | ], 42 | "scripts": [] 43 | }, 44 | "configurations": { 45 | "production": { 46 | "fileReplacements": [ 47 | { 48 | "replace": "src/environments/environment.ts", 49 | "with": "src/environments/environment.prod.ts" 50 | } 51 | ], 52 | "optimization": true, 53 | "outputHashing": "all", 54 | "sourceMap": false, 55 | "extractCss": true, 56 | "namedChunks": false, 57 | "aot": true, 58 | "extractLicenses": true, 59 | "vendorChunk": false, 60 | "buildOptimizer": true, 61 | "budgets": [ 62 | { 63 | "type": "initial", 64 | "maximumWarning": "2mb", 65 | "maximumError": "5mb" 66 | } 67 | ] 68 | }, 69 | "ci": { 70 | "progress": false 71 | } 72 | } 73 | }, 74 | "serve": { 75 | "builder": "@angular-devkit/build-angular:dev-server", 76 | "options": { 77 | "browserTarget": "app:build" 78 | }, 79 | "configurations": { 80 | "production": { 81 | "browserTarget": "app:build:production" 82 | }, 83 | "ci": { 84 | "progress": false 85 | } 86 | } 87 | }, 88 | "extract-i18n": { 89 | "builder": "@angular-devkit/build-angular:extract-i18n", 90 | "options": { 91 | "browserTarget": "app:build" 92 | } 93 | }, 94 | "test": { 95 | "builder": "@angular-devkit/build-angular:karma", 96 | "options": { 97 | "main": "src/test.ts", 98 | "polyfills": "src/polyfills.ts", 99 | "tsConfig": "tsconfig.spec.json", 100 | "karmaConfig": "karma.conf.js", 101 | "styles": [], 102 | "scripts": [], 103 | "assets": [ 104 | { 105 | "glob": "favicon.ico", 106 | "input": "src/", 107 | "output": "/" 108 | }, 109 | { 110 | "glob": "**/*", 111 | "input": "src/assets", 112 | "output": "/assets" 113 | } 114 | ] 115 | }, 116 | "configurations": { 117 | "ci": { 118 | "progress": false, 119 | "watch": false 120 | } 121 | } 122 | }, 123 | "lint": { 124 | "builder": "@angular-devkit/build-angular:tslint", 125 | "options": { 126 | "tsConfig": [ 127 | "tsconfig.app.json", 128 | "tsconfig.spec.json", 129 | "e2e/tsconfig.json" 130 | ], 131 | "exclude": ["**/node_modules/**"] 132 | } 133 | }, 134 | "e2e": { 135 | "builder": "@angular-devkit/build-angular:protractor", 136 | "options": { 137 | "protractorConfig": "e2e/protractor.conf.js", 138 | "devServerTarget": "app:serve" 139 | }, 140 | "configurations": { 141 | "production": { 142 | "devServerTarget": "app:serve:production" 143 | }, 144 | "ci": { 145 | "devServerTarget": "app:serve:ci" 146 | } 147 | } 148 | }, 149 | "ionic-cordova-build": { 150 | "builder": "@ionic/angular-toolkit:cordova-build", 151 | "options": { 152 | "browserTarget": "app:build" 153 | }, 154 | "configurations": { 155 | "production": { 156 | "browserTarget": "app:build:production" 157 | } 158 | } 159 | }, 160 | "ionic-cordova-serve": { 161 | "builder": "@ionic/angular-toolkit:cordova-serve", 162 | "options": { 163 | "cordovaBuildTarget": "app:ionic-cordova-build", 164 | "devServerTarget": "app:serve" 165 | }, 166 | "configurations": { 167 | "production": { 168 | "cordovaBuildTarget": "app:ionic-cordova-build:production", 169 | "devServerTarget": "app:serve:production" 170 | } 171 | } 172 | } 173 | } 174 | } 175 | }, 176 | "cli": { 177 | "defaultCollection": "@ionic/angular-toolkit" 178 | }, 179 | "schematics": { 180 | "@ionic/angular-toolkit:component": { 181 | "styleext": "scss" 182 | }, 183 | "@ionic/angular-toolkit:page": { 184 | "styleext": "scss" 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. 13 | -------------------------------------------------------------------------------- /capacitor.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "io.ionic.starter", 3 | "appName": "questions-and-answers-app", 4 | "bundledWebRuntime": false, 5 | "npmClient": "npm", 6 | "webDir": "www", 7 | "plugins": { 8 | "SplashScreen": { 9 | "launchShowDuration": 0 10 | } 11 | }, 12 | "cordova": {} 13 | } 14 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('new App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | describe('default screen', () => { 10 | beforeEach(() => { 11 | page.navigateTo('/Inbox'); 12 | }); 13 | it('should say Inbox', () => { 14 | expect(page.getParagraphText()).toContain('Inbox'); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(destination) { 5 | return browser.get(destination); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.deepCss('app-root ion-content')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "www", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ionic-3/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | 10 | # We recommend you to keep these unchanged 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /ionic-3/.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | *~ 5 | *.sw[mnpcod] 6 | *.log 7 | *.tmp 8 | *.tmp.* 9 | log.txt 10 | *.sublime-project 11 | *.sublime-workspace 12 | .vscode/ 13 | npm-debug.log* 14 | 15 | .idea/ 16 | .sass-cache/ 17 | .tmp/ 18 | .versions/ 19 | coverage/ 20 | dist/ 21 | node_modules/ 22 | tmp/ 23 | temp/ 24 | hooks/ 25 | platforms/ 26 | plugins/ 27 | plugins/android.json 28 | plugins/ios.json 29 | PREVIEW/ 30 | DESIGN/ 31 | RELEASE/ 32 | res/ 33 | www/ 34 | pwa/ 35 | $RECYCLE.BIN/ 36 | 37 | .DS_Store 38 | Thumbs.db 39 | UserInterfaceState.xcuserstate 40 | 41 | .sourcemaps 42 | -------------------------------------------------------------------------------- /ionic-3/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 IonicThemes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ionic-3/README.md: -------------------------------------------------------------------------------- 1 | This repository is part of an ionic tutorial that explains how to build an ionic example app with a question and answer format (Q&A). We will go through the core concepts of an Ionic 3 App such as Project Structure, Navigation, UI/UX and Data Integration. This ionic tutorial includes a working example you can reuse for your needs! 2 | 3 | 4 | **Please support this project by simply putting a Github star ⭐. Share this library with friends on Twitter and everywhere else you can. 🙏** 5 | 6 | Check the complete step by step tutorial in: https://ionicthemes.com/tutorials/about/building-a-complete-mobile-app-with-ionic-3 7 | 8 | ![](https://s3-us-west-2.amazonaws.com/ionicthemes/tutorials/screenshots/build-a-complete-mobile-app-with-ionic-2/app_1.png?v=1) 9 | ![](https://s3-us-west-2.amazonaws.com/ionicthemes/tutorials/screenshots/build-a-complete-mobile-app-with-ionic-2/app_2.png?v=1) 10 | ![](https://s3-us-west-2.amazonaws.com/ionicthemes/tutorials/screenshots/build-a-complete-mobile-app-with-ionic-2/app_4.png?v=1) 11 | 12 | ## Installation 13 | 14 | Install dependencies 15 | ```sh 16 | npm install 17 | ``` 18 | 19 | ## Run the app on the browser 20 | 21 | ```sh 22 | ionic serve 23 | ``` 24 | 25 | ## Run the app on your phone 26 | 27 | ```sh 28 | ionic cordova platform add android 29 | ionic cordova run android 30 | ``` 31 | 32 | or 33 | 34 | ```sh 35 | ionic cordova platform add ios 36 | ionic cordova run ios 37 | ``` 38 | -------------------------------------------------------------------------------- /ionic-3/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | q-a-example-ionic-app 4 | An awesome Ionic/Cordova app. 5 | Ionic Framework Team 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 | 62 | -------------------------------------------------------------------------------- /ionic-3/ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "q-a-example-ionic-app", 3 | "app_id": "", 4 | "type": "ionic-angular", 5 | "integrations": { 6 | "cordova": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ionic-3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "building-a-complete-mobile-app-with-ionic-3", 3 | "author": "IonicThemes", 4 | "homepage": "https://ionicthemes.com/", 5 | "private": true, 6 | "scripts": { 7 | "clean": "ionic-app-scripts clean", 8 | "build": "ionic-app-scripts build", 9 | "ionic:build": "ionic-app-scripts build", 10 | "ionic:serve": "ionic-app-scripts serve" 11 | }, 12 | "dependencies": { 13 | "@angular/common": "4.1.3", 14 | "@angular/compiler": "4.1.3", 15 | "@angular/compiler-cli": "4.1.3", 16 | "@angular/core": "4.1.3", 17 | "@angular/forms": "4.1.3", 18 | "@angular/http": "4.1.3", 19 | "@angular/platform-browser": "4.1.3", 20 | "@angular/platform-browser-dynamic": "4.1.3", 21 | "@ionic-native/core": "3.12.1", 22 | "@ionic-native/splash-screen": "3.12.1", 23 | "@ionic-native/status-bar": "3.12.1", 24 | "@ionic/storage": "2.0.1", 25 | "body-parser": "^1.17.2", 26 | "compression": "^1.7.0", 27 | "cordova-android": "^6.2.3", 28 | "cordova-ios": "^4.4.0", 29 | "cordova-plugin-console": "^1.0.5", 30 | "cordova-plugin-device": "^1.1.4", 31 | "cordova-plugin-splashscreen": "^4.0.3", 32 | "cordova-plugin-statusbar": "^2.2.1", 33 | "cordova-plugin-whitelist": "^1.3.1", 34 | "cors": "^2.8.4", 35 | "express": "^4.15.4", 36 | "ionic-angular": "3.6.0", 37 | "ionic-plugin-keyboard": "^2.2.1", 38 | "ionicons": "3.0.0", 39 | "rxjs": "5.4.0", 40 | "sw-toolbox": "3.6.0", 41 | "zone.js": "0.8.12" 42 | }, 43 | "devDependencies": { 44 | "@ionic/app-scripts": "1.3.7", 45 | "typescript": "2.2.1" 46 | }, 47 | "cordovaPlugins": [ 48 | "cordova-plugin-whitelist", 49 | "cordova-plugin-statusbar", 50 | "cordova-plugin-console", 51 | "cordova-plugin-device", 52 | "cordova-plugin-splashscreen", 53 | "ionic-plugin-keyboard" 54 | ], 55 | "cordovaPlatforms": [ 56 | "ios", 57 | { 58 | "platform": "ios", 59 | "version": "", 60 | "locator": "ios" 61 | } 62 | ], 63 | "description": "Build a complete mobile app with Ionic 3", 64 | "cordova": { 65 | "plugins": { 66 | "cordova-plugin-console": {}, 67 | "cordova-plugin-device": {}, 68 | "cordova-plugin-splashscreen": {}, 69 | "cordova-plugin-statusbar": {}, 70 | "cordova-plugin-whitelist": {}, 71 | "ionic-plugin-keyboard": {} 72 | }, 73 | "platforms": [ 74 | "android", 75 | "ios" 76 | ] 77 | } 78 | } -------------------------------------------------------------------------------- /ionic-3/resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /ionic-3/resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /ionic-3/resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /ionic-3/resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /ionic-3/resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /ionic-3/resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /ionic-3/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/icon.png -------------------------------------------------------------------------------- /ionic-3/resources/icon.png.md5: -------------------------------------------------------------------------------- 1 | 3f1bbdf1aefcb5ce7b60770ce907c68f -------------------------------------------------------------------------------- /ionic-3/resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/resources/splash.png -------------------------------------------------------------------------------- /ionic-3/resources/splash.png.md5: -------------------------------------------------------------------------------- 1 | d14d647ac75b4b12645d0bba2cbdad02 -------------------------------------------------------------------------------- /ionic-3/sdk/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module SDKModule 3 | * @author Jonathan Casarrubias 4 | * @license MIT 2016 Jonathan Casarrubias 5 | * @version 2.1.0 6 | * @description 7 | * The SDKModule is a generated Software Development Kit automatically built by 8 | * the LoopBack SDK Builder open source module. 9 | * 10 | * The SDKModule provides Angular 2 >= RC.5 support, which means that NgModules 11 | * can import this Software Development Kit as follows: 12 | * 13 | * 14 | * APP Route Module Context 15 | * ============================================================================ 16 | * import { NgModule } from '@angular/core'; 17 | * import { BrowserModule } from '@angular/platform-browser'; 18 | * // App Root 19 | * import { AppComponent } from './app.component'; 20 | * // Feature Modules 21 | * import { SDK[Browser|Node|Native]Module } from './shared/sdk/sdk.module'; 22 | * // Import Routing 23 | * import { routing } from './app.routing'; 24 | * @NgModule({ 25 | * imports: [ 26 | * BrowserModule, 27 | * routing, 28 | * SDK[Browser|Node|Native]Module.forRoot() 29 | * ], 30 | * declarations: [ AppComponent ], 31 | * bootstrap: [ AppComponent ] 32 | * }) 33 | * export class AppModule { } 34 | * 35 | **/ 36 | import { JSONSearchParams } from './services/core/search.params'; 37 | import { ErrorHandler } from './services/core/error.service'; 38 | import { LoopBackAuth } from './services/core/auth.service'; 39 | import { LoggerService } from './services/custom/logger.service'; 40 | import { SDKModels } from './services/custom/SDKModels'; 41 | import { InternalStorage, SDKStorage } from './storage/storage.swaps'; 42 | import { HttpModule } from '@angular/http'; 43 | import { CommonModule } from '@angular/common'; 44 | import { NgModule, ModuleWithProviders } from '@angular/core'; 45 | import { CookieBrowser } from './storage/cookie.browser'; 46 | import { StorageBrowser } from './storage/storage.browser'; 47 | import { UserApi } from './services/custom/User'; 48 | import { QuestionApi } from './services/custom/Question'; 49 | import { AnswerApi } from './services/custom/Answer'; 50 | /** 51 | * @module SDKBrowserModule 52 | * @description 53 | * This module should be imported when building a Web Application in the following scenarios: 54 | * 55 | * 1.- Regular web application 56 | * 2.- Angular universal application (Browser Portion) 57 | * 3.- Progressive applications (Angular Mobile, Ionic, WebViews, etc) 58 | **/ 59 | @NgModule({ 60 | imports: [ CommonModule, HttpModule ], 61 | declarations: [ ], 62 | exports: [ ], 63 | providers: [ 64 | ErrorHandler 65 | ] 66 | }) 67 | export class SDKBrowserModule { 68 | static forRoot(internalStorageProvider: any = { 69 | provide: InternalStorage, 70 | useClass: CookieBrowser 71 | }): ModuleWithProviders { 72 | return { 73 | ngModule : SDKBrowserModule, 74 | providers : [ 75 | LoopBackAuth, 76 | LoggerService, 77 | JSONSearchParams, 78 | SDKModels, 79 | UserApi, 80 | QuestionApi, 81 | AnswerApi, 82 | internalStorageProvider, 83 | { provide: SDKStorage, useClass: StorageBrowser } 84 | ] 85 | }; 86 | } 87 | } 88 | /** 89 | * Have Fun!!! 90 | * - Jon 91 | **/ 92 | export * from './models/index'; 93 | export * from './services/index'; 94 | export * from './lb.config'; 95 | export * from './storage/storage.swaps'; 96 | export { CookieBrowser } from './storage/cookie.browser'; 97 | export { StorageBrowser } from './storage/storage.browser'; 98 | -------------------------------------------------------------------------------- /ionic-3/sdk/lb.config.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * @module LoopBackConfig 4 | * @description 5 | * 6 | * The LoopBackConfig module help developers to externally 7 | * configure the base url and api version for loopback.io 8 | * 9 | * Example 10 | * 11 | * import { LoopBackConfig } from './sdk'; 12 | * 13 | * @Component() // No metadata needed for this module 14 | * 15 | * export class MyApp { 16 | * constructor() { 17 | * LoopBackConfig.setBaseURL('http://localhost:3000'); 18 | * LoopBackConfig.setApiVersion('api'); 19 | * } 20 | * } 21 | **/ 22 | export class LoopBackConfig { 23 | private static path: string = '//0.0.0.0:3000'; 24 | private static version: string | number = 'api'; 25 | private static authPrefix: string = ''; 26 | private static debug: boolean = true; 27 | 28 | public static setApiVersion(version: string = 'api'): void { 29 | LoopBackConfig.version = version; 30 | } 31 | 32 | public static getApiVersion(): string | number { 33 | return LoopBackConfig.version; 34 | } 35 | 36 | public static setBaseURL(url: string = '/'): void { 37 | LoopBackConfig.path = url; 38 | } 39 | 40 | public static getPath(): string { 41 | return LoopBackConfig.path; 42 | } 43 | 44 | public static setAuthPrefix(authPrefix: string = ''): void { 45 | LoopBackConfig.authPrefix = authPrefix; 46 | } 47 | 48 | public static getAuthPrefix(): string { 49 | return LoopBackConfig.authPrefix; 50 | } 51 | 52 | public static setDebugMode(isEnabled: boolean): void { 53 | LoopBackConfig.debug = isEnabled; 54 | } 55 | 56 | public static debuggable(): boolean { 57 | return LoopBackConfig.debug; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ionic-3/sdk/models/Answer.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { 3 | Question 4 | } from '../index'; 5 | 6 | declare var Object: any; 7 | export interface AnswerInterface { 8 | "answer": any; 9 | "negativeVotes"?: any; 10 | "positiveVotes"?: any; 11 | "id"?: any; 12 | "questionId"?: any; 13 | question?: Question; 14 | } 15 | 16 | export class Answer implements AnswerInterface { 17 | "answer": any; 18 | "negativeVotes": any; 19 | "positiveVotes": any; 20 | "id": any; 21 | "questionId": any; 22 | question: Question; 23 | constructor(data?: AnswerInterface) { 24 | Object.assign(this, data); 25 | } 26 | /** 27 | * The name of the model represented by this $resource, 28 | * i.e. `Answer`. 29 | */ 30 | public static getModelName() { 31 | return "Answer"; 32 | } 33 | /** 34 | * @method factory 35 | * @author Jonathan Casarrubias 36 | * @license MIT 37 | * This method creates an instance of Answer for dynamic purposes. 38 | **/ 39 | public static factory(data: AnswerInterface): Answer{ 40 | return new Answer(data); 41 | } 42 | /** 43 | * @method getModelDefinition 44 | * @author Julien Ledun 45 | * @license MIT 46 | * This method returns an object that represents some of the model 47 | * definitions. 48 | **/ 49 | public static getModelDefinition() { 50 | return { 51 | name: 'Answer', 52 | plural: 'Answers', 53 | properties: { 54 | "answer": { 55 | name: 'answer', 56 | type: 'any' 57 | }, 58 | "negativeVotes": { 59 | name: 'negativeVotes', 60 | type: 'any', 61 | default: 0 62 | }, 63 | "positiveVotes": { 64 | name: 'positiveVotes', 65 | type: 'any', 66 | default: 0 67 | }, 68 | "id": { 69 | name: 'id', 70 | type: 'any' 71 | }, 72 | "questionId": { 73 | name: 'questionId', 74 | type: 'any' 75 | }, 76 | }, 77 | relations: { 78 | question: { 79 | name: 'question', 80 | type: 'Question', 81 | model: 'Question' 82 | }, 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ionic-3/sdk/models/BaseModels.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | declare var Object: any; 4 | export interface LoopBackFilter { 5 | fields?: any; 6 | include?: any; 7 | limit?: any; 8 | order?: any; 9 | skip?: any; 10 | offset?: any; 11 | where?: any; 12 | } 13 | 14 | export interface AccessTokenInterface { 15 | id?: any; 16 | ttl?: number; 17 | issuedAt?: any; 18 | created: any; 19 | userId?: number; 20 | rememberMe?: boolean; 21 | } 22 | 23 | export class AccessToken implements AccessTokenInterface { 24 | id: any = ''; 25 | ttl: number = 1209600; 26 | created: Date = new Date(0); 27 | userId: number = 0; 28 | user: any = null; 29 | constructor(data?: AccessTokenInterface) { 30 | Object.assign(this, data); 31 | } 32 | /** 33 | * The name of the model represented by this $resource, 34 | * i.e. `AccessToken`. 35 | */ 36 | public static getModelName() { 37 | return "AccessToken"; 38 | } 39 | /** 40 | * @method factory 41 | * @author Jonathan Casarrubias 42 | * @license MIT 43 | * This method creates an instance of AccessToken for dynamic purposes. 44 | **/ 45 | public static factory(data: AccessTokenInterface): AccessToken{ 46 | return new AccessToken(data); 47 | } 48 | /** 49 | * @method getModelDefinition 50 | * @author Julien Ledun 51 | * @license MIT 52 | * This method returns an object that represents some of the model 53 | * definitions. 54 | **/ 55 | public static getModelDefinition() { 56 | return { 57 | name: 'AccessToken', 58 | plural: 'AccessTokens', 59 | properties: { 60 | id: { 61 | name: 'id', 62 | type: 'string' 63 | }, 64 | ttl: { 65 | name: 'ttl', 66 | type: 'number', 67 | default: 1209600 68 | }, 69 | created: { 70 | name: 'created', 71 | type: 'Date', 72 | default: new Date(0) 73 | }, 74 | userId: { 75 | name: 'userId', 76 | type: 'number' 77 | }, 78 | }, 79 | relations: { 80 | user: { 81 | name: 'user', 82 | type: 'User', 83 | model: 'User' 84 | }, 85 | } 86 | } 87 | } 88 | } 89 | 90 | export class SDKToken implements AccessTokenInterface { 91 | id: any = null; 92 | ttl: number = null; 93 | issuedAt: any = null; 94 | created: any = null; 95 | userId: any = null; 96 | user: any = null; 97 | rememberMe: boolean = null; 98 | constructor(data?: AccessTokenInterface) { 99 | Object.assign(this, data); 100 | } 101 | } 102 | /** 103 | * This GeoPoint represents both, LoopBack and MongoDB GeoPoint 104 | **/ 105 | export interface GeoPoint { 106 | lat?: number; 107 | lng?: number; 108 | type?: string; 109 | coordinates?: number[]; 110 | } 111 | 112 | export interface StatFilter { 113 | range: string, 114 | custom?: { 115 | start: string, 116 | end: string 117 | }, 118 | where?: {}, 119 | groupBy?: string 120 | } 121 | -------------------------------------------------------------------------------- /ionic-3/sdk/models/Question.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { 3 | Answer 4 | } from '../index'; 5 | 6 | declare var Object: any; 7 | export interface QuestionInterface { 8 | "questionSlug": any; 9 | "question": any; 10 | "negativeVotes"?: any; 11 | "positiveVotes"?: any; 12 | "id"?: any; 13 | answers?: Answer[]; 14 | } 15 | 16 | export class Question implements QuestionInterface { 17 | "questionSlug": any; 18 | "question": any; 19 | "negativeVotes": any; 20 | "positiveVotes": any; 21 | "id": any; 22 | answers: Answer[]; 23 | constructor(data?: QuestionInterface) { 24 | Object.assign(this, data); 25 | } 26 | /** 27 | * The name of the model represented by this $resource, 28 | * i.e. `Question`. 29 | */ 30 | public static getModelName() { 31 | return "Question"; 32 | } 33 | /** 34 | * @method factory 35 | * @author Jonathan Casarrubias 36 | * @license MIT 37 | * This method creates an instance of Question for dynamic purposes. 38 | **/ 39 | public static factory(data: QuestionInterface): Question{ 40 | return new Question(data); 41 | } 42 | /** 43 | * @method getModelDefinition 44 | * @author Julien Ledun 45 | * @license MIT 46 | * This method returns an object that represents some of the model 47 | * definitions. 48 | **/ 49 | public static getModelDefinition() { 50 | return { 51 | name: 'Question', 52 | plural: 'Questions', 53 | properties: { 54 | "questionSlug": { 55 | name: 'questionSlug', 56 | type: 'any' 57 | }, 58 | "question": { 59 | name: 'question', 60 | type: 'any' 61 | }, 62 | "negativeVotes": { 63 | name: 'negativeVotes', 64 | type: 'any', 65 | default: 0 66 | }, 67 | "positiveVotes": { 68 | name: 'positiveVotes', 69 | type: 'any', 70 | default: 0 71 | }, 72 | "id": { 73 | name: 'id', 74 | type: 'any' 75 | }, 76 | }, 77 | relations: { 78 | answers: { 79 | name: 'answers', 80 | type: 'Answer[]', 81 | model: 'Answer' 82 | }, 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ionic-3/sdk/models/User.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | declare var Object: any; 4 | export interface UserInterface { 5 | "realm"?: any; 6 | "username"?: any; 7 | "password": any; 8 | "email": any; 9 | "emailVerified"?: any; 10 | "verificationToken"?: any; 11 | "id"?: any; 12 | accessTokens?: any[]; 13 | } 14 | 15 | export class User implements UserInterface { 16 | "realm": any; 17 | "username": any; 18 | "password": any; 19 | "email": any; 20 | "emailVerified": any; 21 | "verificationToken": any; 22 | "id": any; 23 | accessTokens: any[]; 24 | constructor(data?: UserInterface) { 25 | Object.assign(this, data); 26 | } 27 | /** 28 | * The name of the model represented by this $resource, 29 | * i.e. `User`. 30 | */ 31 | public static getModelName() { 32 | return "User"; 33 | } 34 | /** 35 | * @method factory 36 | * @author Jonathan Casarrubias 37 | * @license MIT 38 | * This method creates an instance of User for dynamic purposes. 39 | **/ 40 | public static factory(data: UserInterface): User{ 41 | return new User(data); 42 | } 43 | /** 44 | * @method getModelDefinition 45 | * @author Julien Ledun 46 | * @license MIT 47 | * This method returns an object that represents some of the model 48 | * definitions. 49 | **/ 50 | public static getModelDefinition() { 51 | return { 52 | name: 'User', 53 | plural: 'Users', 54 | properties: { 55 | "realm": { 56 | name: 'realm', 57 | type: 'any' 58 | }, 59 | "username": { 60 | name: 'username', 61 | type: 'any' 62 | }, 63 | "password": { 64 | name: 'password', 65 | type: 'any' 66 | }, 67 | "email": { 68 | name: 'email', 69 | type: 'any' 70 | }, 71 | "emailVerified": { 72 | name: 'emailVerified', 73 | type: 'any' 74 | }, 75 | "verificationToken": { 76 | name: 'verificationToken', 77 | type: 'any' 78 | }, 79 | "id": { 80 | name: 'id', 81 | type: 'any' 82 | }, 83 | }, 84 | relations: { 85 | accessTokens: { 86 | name: 'accessTokens', 87 | type: 'any[]', 88 | model: '' 89 | }, 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /ionic-3/sdk/models/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './User'; 3 | export * from './Question'; 4 | export * from './Answer'; 5 | export * from './BaseModels'; 6 | 7 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/core/auth.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | declare var Object: any; 3 | import { Injectable, Inject } from '@angular/core'; 4 | import { InternalStorage } from '../../storage/storage.swaps'; 5 | import { SDKToken } from '../../models/BaseModels'; 6 | /** 7 | * @author Jonathan Casarrubias 8 | * @module SocketConnection 9 | * @license MIT 10 | * @description 11 | * This module handle socket connections and return singleton instances for each 12 | * connection, it will use the SDK Socket Driver Available currently supporting 13 | * Angular 2 for web, NativeScript 2 and Angular Universal. 14 | **/ 15 | @Injectable() 16 | export class LoopBackAuth { 17 | /** 18 | * @type {SDKToken} 19 | **/ 20 | private token: SDKToken = new SDKToken(); 21 | /** 22 | * @type {string} 23 | **/ 24 | protected prefix: string = '$LoopBackSDK$'; 25 | /** 26 | * @method constructor 27 | * @param {InternalStorage} storage Internal Storage Driver 28 | * @description 29 | * The constructor will initialize the token loading data from storage 30 | **/ 31 | constructor(@Inject(InternalStorage) protected storage: InternalStorage) { 32 | this.token.id = this.load('id'); 33 | this.token.user = this.load('user'); 34 | this.token.userId = this.load('userId'); 35 | this.token.issuedAt = this.load('issuedAt'); 36 | this.token.created = this.load('created'); 37 | this.token.ttl = this.load('ttl'); 38 | this.token.rememberMe = this.load('rememberMe'); 39 | } 40 | /** 41 | * @method setRememberMe 42 | * @param {boolean} value Flag to remember credentials 43 | * @return {void} 44 | * @description 45 | * This method will set a flag in order to remember the current credentials 46 | **/ 47 | public setRememberMe(value: boolean): void { 48 | this.token.rememberMe = value; 49 | } 50 | /** 51 | * @method setUser 52 | * @param {any} user Any type of user model 53 | * @return {void} 54 | * @description 55 | * This method will update the user information and persist it if the 56 | * rememberMe flag is set. 57 | **/ 58 | public setUser(user: any) { 59 | this.token.user = user; 60 | this.save(); 61 | } 62 | /** 63 | * @method setToken 64 | * @param {SDKToken} token SDKToken or casted AccessToken instance 65 | * @return {void} 66 | * @description 67 | * This method will set a flag in order to remember the current credentials 68 | **/ 69 | public setToken(token: SDKToken): void { 70 | this.token = Object.assign(this.token, token); 71 | this.save(); 72 | } 73 | /** 74 | * @method getToken 75 | * @return {void} 76 | * @description 77 | * This method will set a flag in order to remember the current credentials. 78 | **/ 79 | public getToken(): SDKToken { 80 | return this.token; 81 | } 82 | /** 83 | * @method getAccessTokenId 84 | * @return {string} 85 | * @description 86 | * This method will return the actual token string, not the object instance. 87 | **/ 88 | public getAccessTokenId(): string { 89 | return this.token.id; 90 | } 91 | /** 92 | * @method getCurrentUserId 93 | * @return {any} 94 | * @description 95 | * This method will return the current user id, it can be number or string. 96 | **/ 97 | public getCurrentUserId(): any { 98 | return this.token.userId; 99 | } 100 | /** 101 | * @method getCurrentUserData 102 | * @return {any} 103 | * @description 104 | * This method will return the current user instance. 105 | **/ 106 | public getCurrentUserData(): any { 107 | return (typeof this.token.user === 'string') ? JSON.parse(this.token.user) : this.token.user; 108 | } 109 | /** 110 | * @method save 111 | * @return {boolean} Wether or not the information was saved 112 | * @description 113 | * This method will save in either local storage or cookies the current credentials. 114 | * But only if rememberMe is enabled. 115 | **/ 116 | public save(): boolean { 117 | if (this.token.rememberMe) { 118 | this.persist('id', this.token.id); 119 | this.persist('user', this.token.user); 120 | this.persist('userId', this.token.userId); 121 | this.persist('issuedAt', this.token.issuedAt); 122 | this.persist('created', this.token.created); 123 | this.persist('ttl', this.token.ttl); 124 | this.persist('rememberMe', this.token.rememberMe); 125 | return true; 126 | } else { 127 | return false; 128 | } 129 | }; 130 | /** 131 | * @method load 132 | * @param {string} prop Property name 133 | * @return {any} Any information persisted in storage 134 | * @description 135 | * This method will load either from local storage or cookies the provided property. 136 | **/ 137 | protected load(prop: string): any { 138 | return this.storage.get(`${this.prefix}${prop}`); 139 | } 140 | /** 141 | * @method clear 142 | * @return {void} 143 | * @description 144 | * This method will clear cookies or the local storage. 145 | **/ 146 | public clear(): void { 147 | Object.keys(this.token).forEach((prop: string) => this.storage.remove(`${this.prefix}${prop}`)); 148 | this.token = new SDKToken(); 149 | } 150 | /** 151 | * @method clear 152 | * @return {void} 153 | * @description 154 | * This method will clear cookies or the local storage. 155 | **/ 156 | protected persist(prop: string, value: any): void { 157 | try { 158 | this.storage.set( 159 | `${this.prefix}${prop}`, 160 | (typeof value === 'object') ? JSON.stringify(value) : value 161 | ); 162 | } 163 | catch(err) { 164 | console.error('Cannot access local/session storage:', err); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/core/error.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { Response } from '@angular/http'; 4 | import { Observable } from 'rxjs/Observable'; 5 | //import { ErrorObservable } from 'rxjs/observable/ErrorObservable'; 6 | import 'rxjs/add/observable/throw'; 7 | /** 8 | * Default error handler 9 | */ 10 | @Injectable() 11 | export class ErrorHandler { 12 | // ErrorObservable when rxjs version < rc.5 13 | // ErrorObservable when rxjs version = rc.5 14 | // I'm leaving any for now to avoid breaking apps using both versions 15 | public handleError(error: Response): any { 16 | return Observable.throw(error.json().error || 'Server error'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/core/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './auth.service'; 3 | export * from './error.service'; 4 | export * from './search.params'; 5 | export * from './base.service'; 6 | 7 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/core/search.params.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { URLSearchParams } from '@angular/http'; 4 | /** 5 | * @author Jonathan Casarrubias 6 | * @module JSONSearchParams 7 | * @license MIT 8 | * @description 9 | * JSON Parser and Wrapper for the Angular2 URLSearchParams 10 | * This module correctly encodes a json object into a query string and then creates 11 | * an instance of the URLSearchParams component for later use in HTTP Calls 12 | **/ 13 | @Injectable() 14 | export class JSONSearchParams { 15 | 16 | private _usp: URLSearchParams; 17 | 18 | public setJSON(obj: any) { 19 | this._usp = new URLSearchParams(this._JSON2URL(obj, false)); 20 | } 21 | 22 | public getURLSearchParams(): URLSearchParams { 23 | return this._usp; 24 | } 25 | 26 | private _JSON2URL(obj: any, parent: any) { 27 | var parts: any = []; 28 | for (var key in obj) 29 | parts.push(this._parseParam(key, obj[key], parent)); 30 | return parts.join('&'); 31 | } 32 | 33 | private _parseParam(key: string, value: any, parent: string) { 34 | let processedKey = parent ? parent + '[' + key + ']' : key; 35 | if (value && ((typeof value) === 'object' || Array.isArray(value))) { 36 | return this._JSON2URL(value, processedKey); 37 | } 38 | return processedKey + '=' + value; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/custom/Answer.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable, Inject, Optional } from '@angular/core'; 3 | import { Http, Response } from '@angular/http'; 4 | import { SDKModels } from './SDKModels'; 5 | import { BaseLoopBackApi } from '../core/base.service'; 6 | import { LoopBackConfig } from '../../lb.config'; 7 | import { LoopBackAuth } from '../core/auth.service'; 8 | import { LoopBackFilter, } from '../../models/BaseModels'; 9 | import { JSONSearchParams } from '../core/search.params'; 10 | import { ErrorHandler } from '../core/error.service'; 11 | import { Subject } from 'rxjs/Subject'; 12 | import { Observable } from 'rxjs/Rx'; 13 | import { Answer } from '../../models/Answer'; 14 | import { Question } from '../../models/Question'; 15 | 16 | 17 | /** 18 | * Api services for the `Answer` model. 19 | */ 20 | @Injectable() 21 | export class AnswerApi extends BaseLoopBackApi { 22 | 23 | constructor( 24 | @Inject(Http) protected http: Http, 25 | @Inject(SDKModels) protected models: SDKModels, 26 | @Inject(LoopBackAuth) protected auth: LoopBackAuth, 27 | @Inject(JSONSearchParams) protected searchParams: JSONSearchParams, 28 | @Optional() @Inject(ErrorHandler) protected errorHandler: ErrorHandler 29 | ) { 30 | super(http, models, auth, searchParams, errorHandler); 31 | } 32 | 33 | /** 34 | * Fetches belongsTo relation question. 35 | * 36 | * @param {any} id answer id 37 | * 38 | * @param {boolean} refresh 39 | * 40 | * @returns {object} An empty reference that will be 41 | * populated with the actual data once the response is returned 42 | * from the server. 43 | * 44 | * 45 | * (The remote method definition does not provide any description. 46 | * This usually means the response is a `Answer` object.) 47 | * 48 | */ 49 | public getQuestion(id: any, refresh: any = {}): Observable { 50 | let _method: string = "GET"; 51 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 52 | "/answers/:id/question"; 53 | let _routeParams: any = { 54 | id: id 55 | }; 56 | let _postBody: any = {}; 57 | let _urlParams: any = {}; 58 | if (refresh) _urlParams.refresh = refresh; 59 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody); 60 | return result; 61 | } 62 | 63 | /** 64 | * Patch an existing model instance or insert a new one into the data source. 65 | * 66 | * @param {object} data Request data. 67 | * 68 | * - `data` – `{object}` - Model instance data 69 | * 70 | * @returns {object} An empty reference that will be 71 | * populated with the actual data once the response is returned 72 | * from the server. 73 | * 74 | * 75 | * (The remote method definition does not provide any description. 76 | * This usually means the response is a `Answer` object.) 77 | * 78 | */ 79 | public patchOrCreate(data: any = {}): Observable { 80 | let _method: string = "PATCH"; 81 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 82 | "/answers"; 83 | let _routeParams: any = {}; 84 | let _postBody: any = { 85 | data: data 86 | }; 87 | let _urlParams: any = {}; 88 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody); 89 | return result; 90 | } 91 | 92 | /** 93 | * Patch attributes for a model instance and persist it into the data source. 94 | * 95 | * @param {any} id answer id 96 | * 97 | * @param {object} data Request data. 98 | * 99 | * - `data` – `{object}` - An object of model property name/value pairs 100 | * 101 | * @returns {object} An empty reference that will be 102 | * populated with the actual data once the response is returned 103 | * from the server. 104 | * 105 | * 106 | * (The remote method definition does not provide any description. 107 | * This usually means the response is a `Answer` object.) 108 | * 109 | */ 110 | public patchAttributes(id: any, data: any = {}): Observable { 111 | let _method: string = "PATCH"; 112 | let _url: string = LoopBackConfig.getPath() + "/" + LoopBackConfig.getApiVersion() + 113 | "/answers/:id"; 114 | let _routeParams: any = { 115 | id: id 116 | }; 117 | let _postBody: any = { 118 | data: data 119 | }; 120 | let _urlParams: any = {}; 121 | let result = this.request(_method, _url, _routeParams, _urlParams, _postBody); 122 | return result; 123 | } 124 | 125 | /** 126 | * The name of the model represented by this $resource, 127 | * i.e. `Answer`. 128 | */ 129 | public getModelName() { 130 | return "Answer"; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/custom/SDKModels.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { User } from '../../models/User'; 4 | import { Question } from '../../models/Question'; 5 | import { Answer } from '../../models/Answer'; 6 | 7 | export interface Models { [name: string]: any } 8 | 9 | @Injectable() 10 | export class SDKModels { 11 | 12 | private models: Models = { 13 | User: User, 14 | Question: Question, 15 | Answer: Answer, 16 | 17 | }; 18 | 19 | public get(modelName: string): any { 20 | return this.models[modelName]; 21 | } 22 | 23 | public getAll(): Models { 24 | return this.models; 25 | } 26 | 27 | public getModelNames(): string[] { 28 | return Object.keys(this.models); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/custom/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './User'; 3 | export * from './Question'; 4 | export * from './Answer'; 5 | export * from './SDKModels'; 6 | export * from './logger.service'; 7 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/custom/logger.service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | import { LoopBackConfig } from '../../lb.config'; 4 | /** 5 | * @author Jonathan Casarrubias 6 | * @module LoggerService 7 | * @license MIT 8 | * @description 9 | * Console Log wrapper that can be disabled in production mode 10 | **/ 11 | @Injectable() 12 | export class LoggerService { 13 | 14 | log(...args: any[]) { 15 | if (LoopBackConfig.debuggable()) 16 | console.log.apply(console, args); 17 | } 18 | 19 | info(...args: any[]) { 20 | if (LoopBackConfig.debuggable()) 21 | console.info.apply(console, args); 22 | } 23 | 24 | error(...args: any[]) { 25 | if (LoopBackConfig.debuggable()) 26 | console.error.apply(console, args); 27 | } 28 | 29 | count(arg: string) { 30 | if (LoopBackConfig.debuggable()) 31 | console.count(arg); 32 | } 33 | 34 | group(arg: string) { 35 | if (LoopBackConfig.debuggable()) 36 | console.count(arg); 37 | } 38 | 39 | groupEnd() { 40 | if (LoopBackConfig.debuggable()) 41 | console.groupEnd(); 42 | } 43 | 44 | profile(arg: string) { 45 | if (LoopBackConfig.debuggable()) 46 | console.count(arg); 47 | } 48 | 49 | profileEnd() { 50 | if (LoopBackConfig.debuggable()) 51 | console.profileEnd(); 52 | } 53 | 54 | time(arg: string) { 55 | if (LoopBackConfig.debuggable()) 56 | console.time(arg); 57 | } 58 | 59 | timeEnd(arg: string) { 60 | if (LoopBackConfig.debuggable()) 61 | console.timeEnd(arg); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ionic-3/sdk/services/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | export * from './core/index'; 3 | export * from './custom/index'; 4 | -------------------------------------------------------------------------------- /ionic-3/sdk/storage/cookie.browser.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | export interface CookieInterface { [key: string]: any } 3 | /** 4 | * @author Jonathan Casarrubias 5 | * @module CookieBrowser 6 | * @license MIT 7 | * @description 8 | * This module handle cookies, it will be provided using DI Swapping according the 9 | * SDK Socket Driver Available currently supporting Angular 2 for web and NativeScript 2. 10 | **/ 11 | @Injectable() 12 | export class CookieBrowser { 13 | /** 14 | * @type {CookieInterface} 15 | **/ 16 | private cookies: CookieInterface = {}; 17 | /** 18 | * @method get 19 | * @param {string} key Cookie key name 20 | * @return {any} 21 | * @description 22 | * The getter will return any type of data persisted in cookies. 23 | **/ 24 | get(key: string): any { 25 | if (!this.cookies[key]) { 26 | let cookie = window.document 27 | .cookie.split('; ') 28 | .filter((item: any) => item.split('=')[0] === key).pop(); 29 | if (!cookie) { 30 | return null; 31 | } 32 | 33 | this.cookies[key] = this.parse(cookie.split('=').slice(1).join('=')); 34 | } 35 | 36 | return this.cookies[key]; 37 | } 38 | /** 39 | * @method set 40 | * @param {string} key Cookie key name 41 | * @param {any} value Any value 42 | * @param {Date=} expires The date of expiration (Optional) 43 | * @return {void} 44 | * @description 45 | * The setter will return any type of data persisted in cookies. 46 | **/ 47 | set(key: string, value: any, expires?: Date): void { 48 | this.cookies[key] = value; 49 | let cookie = `${key}=${value}; path=/${expires ? `; expires=${ expires.toUTCString() }` : ''}`; 50 | window.document.cookie = cookie; 51 | } 52 | /** 53 | * @method remove 54 | * @param {string} key Cookie key name 55 | * @return {void} 56 | * @description 57 | * This method will remove a cookie from the client. 58 | **/ 59 | remove(key: string) { 60 | document.cookie = key + '=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; 61 | } 62 | /** 63 | * @method parse 64 | * @param {any} value Input data expected to be JSON 65 | * @return {void} 66 | * @description 67 | * This method will parse the string as JSON if possible, otherwise will 68 | * return the value itself. 69 | **/ 70 | private parse(value: any) { 71 | try { 72 | return JSON.parse(value); 73 | } catch (e) { 74 | return value; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ionic-3/sdk/storage/storage.browser.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | import { Injectable } from '@angular/core'; 3 | /** 4 | * @author Jonathan Casarrubias 5 | * @module StorageBrowser 6 | * @license MIT 7 | * @description 8 | * This module handle localStorage, it will be provided using DI Swapping according the 9 | * SDK Socket Driver Available currently supporting Angular 2 for web and NativeScript 2. 10 | **/ 11 | @Injectable() 12 | export class StorageBrowser { 13 | /** 14 | * @method get 15 | * @param {string} key Storage key name 16 | * @return {any} 17 | * @description 18 | * The getter will return any type of data persisted in localStorage. 19 | **/ 20 | get(key: string): any { 21 | let data: string = localStorage.getItem(key); 22 | return this.parse(data); 23 | } 24 | /** 25 | * @method set 26 | * @param {string} key Storage key name 27 | * @param {any} value Any value 28 | * @return {void} 29 | * @description 30 | * The setter will return any type of data persisted in localStorage. 31 | **/ 32 | set(key: string, value: any): void { 33 | localStorage.setItem( 34 | key, 35 | typeof value === 'object' ? JSON.stringify(value) : value 36 | ); 37 | } 38 | /** 39 | * @method remove 40 | * @param {string} key Storage key name 41 | * @return {void} 42 | * @description 43 | * This method will remove a localStorage item from the client. 44 | **/ 45 | remove(key: string): void { 46 | if (localStorage[key]) { 47 | localStorage.removeItem(key); 48 | } else { 49 | console.log('Trying to remove unexisting key: ', key); 50 | } 51 | } 52 | /** 53 | * @method parse 54 | * @param {any} value Input data expected to be JSON 55 | * @return {void} 56 | * @description 57 | * This method will parse the string as JSON if possible, otherwise will 58 | * return the value itself. 59 | **/ 60 | private parse(value: any) { 61 | try { 62 | return JSON.parse(value); 63 | } catch (e) { 64 | return value; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ionic-3/sdk/storage/storage.swaps.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module Storage 3 | * @author Jonathan Casarrubias 4 | * @license MIT 5 | * @description 6 | * The InternalStorage class is used for dependency injection swapping. 7 | * It will be provided using factory method from different sources. 8 | **/ 9 | export class Storage { 10 | /** 11 | * @method get 12 | * @param {string} key Storage key name 13 | * @return {any} 14 | * @description 15 | * The getter will return any type of data persisted in storage. 16 | **/ 17 | get(key: string): any {} 18 | /** 19 | * @method set 20 | * @param {string} key Storage key name 21 | * @param {any} value Any value 22 | * @return {void} 23 | * @description 24 | * The setter will return any type of data persisted in localStorage. 25 | **/ 26 | set(key: string, value: any): void {} 27 | /** 28 | * @method remove 29 | * @param {string} key Storage key name 30 | * @return {void} 31 | * @description 32 | * This method will remove a localStorage item from the client. 33 | **/ 34 | remove(key: string): void {} 35 | } 36 | /** 37 | * @module InternalStorage 38 | * @author Jonathan Casarrubias 39 | * @license MIT 40 | * @description 41 | * The InternalStorage class is used for dependency injection swapping. 42 | * It will be provided using factory method from different sources. 43 | * This is mainly required because Angular Universal integration. 44 | * It does inject a CookieStorage instead of LocalStorage. 45 | **/ 46 | export class InternalStorage extends Storage {} 47 | /** 48 | * @module SDKStorage 49 | * @author Jonathan Casarrubias 50 | * @license MIT 51 | * @description 52 | * The SDKStorage class is used for dependency injection swapping. 53 | * It will be provided using factory method according the right environment. 54 | * This is created for public usage, to allow persisting custom data 55 | * Into the local storage API. 56 | **/ 57 | export class SDKStorage extends Storage {} 58 | -------------------------------------------------------------------------------- /ionic-3/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | bodyParser = require('body-parser'), 3 | compression = require('compression'), 4 | cors = require('cors'), 5 | app = express(); 6 | 7 | app.set('port', process.env.PORT || 5000); 8 | 9 | app.use(cors()); 10 | app.use(bodyParser.json()); 11 | app.use(compression()); 12 | 13 | app.use('/', express.static(__dirname + '/www')); 14 | 15 | app.listen(app.get('port'), function () { 16 | console.log('Realty server listening on port ' + app.get('port')); 17 | }); 18 | -------------------------------------------------------------------------------- /ionic-3/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { Platform, MenuController, Nav, App } from 'ionic-angular'; 3 | import { StatusBar } from '@ionic-native/status-bar'; 4 | import { SplashScreen } from '@ionic-native/splash-screen'; 5 | 6 | import { LearnFeedPage } from '../pages/learn-feed/learn-feed'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: 'app.html' 11 | }) 12 | export class MyApp { 13 | @ViewChild(Nav) nav: Nav; 14 | 15 | // make LearnFeedPage the root (or first) page 16 | rootPage: any = LearnFeedPage; 17 | 18 | pages: Array<{title: string, component: any, params: any}>; 19 | 20 | constructor( 21 | platform: Platform, 22 | statusBar: StatusBar, 23 | splashScreen: SplashScreen, 24 | public menu: MenuController, 25 | public app: App 26 | ) { 27 | platform.ready().then(() => { 28 | // Okay, so the platform is ready and our plugins are available. 29 | // Here you can do any higher level native things you might need. 30 | statusBar.styleDefault(); 31 | splashScreen.hide(); 32 | }); 33 | 34 | this.pages = [ 35 | { 36 | title: 'All', 37 | component: LearnFeedPage, 38 | params: { 39 | query: 'all' 40 | } 41 | }, 42 | { 43 | title: 'Basic', 44 | component: LearnFeedPage, 45 | params: { 46 | query: 'basic' 47 | } 48 | }, 49 | { 50 | title: 'Core', 51 | component: LearnFeedPage, 52 | params: { 53 | query: 'core' 54 | } 55 | } 56 | ]; 57 | } 58 | 59 | openPage(page) { 60 | // close the menu when clicking a link from the menu 61 | this.menu.close(); 62 | // navigate to the new page if it is not the current page 63 | this.nav.setRoot(page.component, page.params); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ionic-3/src/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ionic-3/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ErrorHandler } from '@angular/core'; 2 | import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular'; 3 | 4 | import { MyApp } from './app.component'; 5 | 6 | import { LearnFeedPage } from '../pages/learn-feed/learn-feed'; 7 | import { LearnDetailsPage } from '../pages/learn-details/learn-details'; 8 | import { QuestionDetailsPage } from '../pages/question-details/question-details'; 9 | import { ManageQuestionPage } from '../pages/manage-question/manage-question'; 10 | import { ManageAnswerPage } from '../pages/manage-answer/manage-answer'; 11 | 12 | import { QuestionService } from '../services/question.service'; 13 | import { AnswerService } from '../services/answer.service'; 14 | import { LearnService } from '../services/learn.service'; 15 | 16 | import { BrowserModule } from '@angular/platform-browser'; 17 | 18 | import { StatusBar } from '@ionic-native/status-bar'; 19 | import { SplashScreen } from '@ionic-native/splash-screen'; 20 | import { SDKBrowserModule } from '../../sdk/index'; 21 | 22 | @NgModule({ 23 | declarations: [ 24 | MyApp, 25 | LearnFeedPage, 26 | LearnDetailsPage, 27 | QuestionDetailsPage, 28 | ManageQuestionPage, 29 | ManageAnswerPage 30 | ], 31 | imports: [ 32 | BrowserModule, 33 | IonicModule.forRoot(MyApp), 34 | SDKBrowserModule.forRoot() 35 | ], 36 | bootstrap: [IonicApp], 37 | entryComponents: [ 38 | MyApp, 39 | LearnFeedPage, 40 | LearnDetailsPage, 41 | QuestionDetailsPage, 42 | ManageQuestionPage, 43 | ManageAnswerPage 44 | ], 45 | providers: [ 46 | StatusBar, 47 | SplashScreen, 48 | QuestionService, 49 | AnswerService, 50 | LearnService, 51 | {provide: ErrorHandler, useClass: IonicErrorHandler} 52 | ] 53 | }) 54 | export class AppModule {} 55 | -------------------------------------------------------------------------------- /ionic-3/src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | @import "../theme/common"; 18 | -------------------------------------------------------------------------------- /ionic-3/src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { LoopBackConfig } from '../../sdk'; 3 | 4 | import { AppModule } from './app.module'; 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule); 7 | 8 | //This is your API url 9 | LoopBackConfig.setBaseURL('https://q-a-example-loopback-api.herokuapp.com'); 10 | -------------------------------------------------------------------------------- /ionic-3/src/assets/categories/categories.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": [ 3 | { 4 | "slug": "IONIC-3", 5 | "title": "Ionic 3", 6 | "description": "Ask about Ionic 3", 7 | "background": "#0077ff", 8 | "tags": [ 9 | { 10 | "name": "Ionic3" 11 | }, 12 | { 13 | "name": "Angular4" 14 | }, 15 | { 16 | "name": "Typescript" 17 | } 18 | ] 19 | }, 20 | { 21 | "slug": "ANGULAR-4", 22 | "title": "Angular 4", 23 | "description": "Ask about Angular 4", 24 | "background": "#F84C61", 25 | "tags":[ 26 | { 27 | "name": "Typescript" 28 | }, 29 | { 30 | "name": "Angular4" 31 | } 32 | ] 33 | }, 34 | { 35 | "slug": "IONIC-NATIVE", 36 | "title": "Ionic Native", 37 | "description": "Ask about Ionic Native", 38 | "background": "#16144A", 39 | "tags": [ 40 | { 41 | "name": "Ionic3" 42 | }, 43 | { 44 | "name": "Plugins" 45 | } 46 | ] 47 | }, 48 | { 49 | "slug": "IONIC-CLI", 50 | "title": "Ionic CLI", 51 | "description": "Ask about Ionic CLI", 52 | "background": "#432B9C", 53 | "tags": [ 54 | { 55 | "name": "Ionic3" 56 | }, 57 | { 58 | "name": "CLI" 59 | } 60 | ] 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /ionic-3/src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/ionic-3/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /ionic-3/src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Declaration files are how the Typescript compiler knows about the type information(or shape) of an object. 3 | They're what make intellisense work and make Typescript know all about your code. 4 | 5 | A wildcard module is declared below to allow third party libraries to be used in an app even if they don't 6 | provide their own type declarations. 7 | 8 | To learn more about using third party libraries in an Ionic app, check out the docs here: 9 | http://ionicframework.com/docs/v2/resources/third-party-libs/ 10 | 11 | For more info on type definition files, check out the Typescript docs here: 12 | https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html 13 | */ 14 | declare module '*'; -------------------------------------------------------------------------------- /ionic-3/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ionic App 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ionic-3/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ionic", 3 | "short_name": "Ionic", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/imgs/logo.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#4e8ef7", 12 | "theme_color": "#4e8ef7" 13 | } -------------------------------------------------------------------------------- /ionic-3/src/pages/learn-details/learn-details.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | Learn Ionic! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Learn all about {{ category.title }}!

16 |
17 | 18 |

19 | {{ category.description }} 20 |

21 |
22 |
23 | 24 |
25 |

26 | There are no questions at the time 27 |

28 |
29 | 30 |
31 |

32 | Find all the Questions and Answers about 33 | {{ category.title }} 34 | from the community 35 |

36 | 37 | 38 | 39 | 40 | 41 | 44 | {{ question.positiveVotes - question.negativeVotes }} 45 | 48 | 49 | 50 | 51 | 52 |

{{ question.question }}

53 |
54 | 55 |
56 | {{ question.positiveVotes + question.negativeVotes }} 57 | Votes 58 |
59 |
60 | 61 |
62 | {{ question.answers.length }} 63 | {{ (question.answers.length > 1 || question.answers.length == 0) ? 'Answers' : 'Answer' }} 64 |
65 |
66 | 67 | 70 | 71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | 79 | 80 | 81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /ionic-3/src/pages/learn-details/learn-details.scss: -------------------------------------------------------------------------------- 1 | learn-details-page { 2 | .category-concept-details 3 | { 4 | background-color: $theme-color-3; 5 | padding: 16px 12px; 6 | height: 35%; 7 | align-content: space-around; 8 | 9 | .category-title 10 | { 11 | color: #FFF; 12 | font-weight: 600; 13 | font-size: 2.4rem; 14 | margin: 0px; 15 | text-align: center; 16 | } 17 | 18 | .category-description 19 | { 20 | color: #FFF; 21 | font-size: 1.6rem; 22 | margin: 0px; 23 | text-align: center; 24 | } 25 | } 26 | 27 | .questions-call-out 28 | { 29 | font-size: 1.4rem; 30 | margin: 12px; 31 | margin-bottom: 30px; 32 | color: $theme-color-3; 33 | text-align: center; 34 | line-height: 1.8; 35 | 36 | .call-out-tag 37 | { 38 | border: 1px solid $theme-color-2; 39 | color: $theme-color-2; 40 | background: #FFF; 41 | } 42 | 43 | .call-out-explanation 44 | { 45 | font-weight: 300; 46 | } 47 | } 48 | 49 | .questions-list 50 | { 51 | margin: 0px; 52 | } 53 | 54 | .question-item 55 | { 56 | padding: 0px; 57 | 58 | &:last-child 59 | { 60 | border-bottom: none !important; 61 | } 62 | 63 | .item-inner 64 | { 65 | padding: 0px !important; 66 | 67 | ion-label { 68 | margin: 0px; 69 | } 70 | } 71 | 72 | .votes-col 73 | { 74 | background-color: $white-b; 75 | 76 | .vote-button 77 | { 78 | font-size: 2.2rem; 79 | margin: 0px; 80 | padding: 0px; 81 | width: 100%; 82 | height: 4rem; 83 | 84 | ion-icon { 85 | padding: 0px; 86 | } 87 | 88 | &.up-vote 89 | { 90 | color: color($colors, secondary); 91 | opacity: 0.25; 92 | } 93 | 94 | &.down-vote 95 | { 96 | color: color($colors, danger); 97 | opacity: 0.25; 98 | } 99 | 100 | &.activated 101 | { 102 | opacity: 1; 103 | } 104 | } 105 | 106 | .question-score 107 | { 108 | margin: 0px; 109 | text-align: center; 110 | font-size: 2rem; 111 | font-weight: 500; 112 | display: block; 113 | 114 | &.good-score 115 | { 116 | color: color($colors, secondary); 117 | } 118 | 119 | &.no-score 120 | { 121 | color: $theme-color-3; 122 | opacity: 0.3; 123 | } 124 | 125 | &.bad-score 126 | { 127 | color: color($colors, danger); 128 | } 129 | } 130 | } 131 | 132 | .question-details 133 | { 134 | height: 100%; 135 | align-content: space-between; 136 | justify-content: space-between; 137 | 138 | .question-text 139 | { 140 | color: $theme-color-3; 141 | margin: 0px; 142 | font-weight: 500; 143 | font-size: 1.6rem; 144 | line-height: 1.4; 145 | white-space: normal; 146 | } 147 | 148 | .actions-details 149 | { 150 | align-self: flex-end; 151 | text-align: right; 152 | 153 | .delete-button 154 | { 155 | margin: 0px; 156 | font-size: 1.6rem; 157 | } 158 | } 159 | 160 | .votes-details, 161 | .answers-details 162 | { 163 | align-self: center; 164 | 165 | .details-wrapper 166 | { 167 | font-size: 1.2rem; 168 | background-color: $black-c; 169 | color: $white-b; 170 | text-align: center; 171 | border-radius: 6px; 172 | margin: 4px; 173 | padding: 3px 0px 6px; 174 | } 175 | 176 | .details-text 177 | { 178 | display: block; 179 | } 180 | 181 | .total-votes, 182 | .total-answers 183 | { 184 | display: block; 185 | font-weight: 500; 186 | font-size: 1.6rem; 187 | } 188 | } 189 | } 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /ionic-3/src/pages/learn-details/learn-details.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController, NavParams, LoadingController, AlertController, ModalController } from 'ionic-angular'; 3 | import { isPresent } from 'ionic-angular/util/util'; 4 | 5 | import { QuestionService } from '../../services/question.service'; 6 | import { AnswerService } from '../../services/answer.service'; 7 | 8 | import { QuestionDetailsPage } from '../question-details/question-details'; 9 | import { ManageQuestionPage } from '../manage-question/manage-question'; 10 | 11 | @Component({ 12 | selector: 'learn-details-page', 13 | templateUrl: 'learn-details.html' 14 | }) 15 | export class LearnDetailsPage { 16 | 17 | questions: Array = []; 18 | category : any; 19 | 20 | constructor( 21 | public navCtrl: NavController, 22 | public navParams: NavParams, 23 | public questionService: QuestionService, 24 | public answerService: AnswerService, 25 | public loadingCtrl: LoadingController, 26 | public alertCtrl: AlertController, 27 | public modalCtrl: ModalController 28 | ) { 29 | let category_param = navParams.get('category'); 30 | this.category = isPresent(category_param) ? category_param : null; 31 | } 32 | 33 | createQuestionModal() { 34 | let create_question_modal = this.modalCtrl.create(ManageQuestionPage, { slug: this.category.slug }); 35 | create_question_modal.onDidDismiss(data => { 36 | this.getQuestions(); 37 | }); 38 | create_question_modal.present(); 39 | } 40 | 41 | ionViewWillEnter() { 42 | this.getQuestions(); 43 | } 44 | 45 | getQuestions(){ 46 | let loading = this.loadingCtrl.create({ 47 | content: 'Please wait...' 48 | }); 49 | loading.present(); 50 | this.questionService.getQuestionsBySlug(this.category.slug) 51 | .then(res => { 52 | this.questions = res; 53 | loading.dismiss(); 54 | }) 55 | } 56 | 57 | delete(questionId){ 58 | let confirm = this.alertCtrl.create({ 59 | title: 'Delete question', 60 | message: 'Are you sure you want to delete this question?', 61 | buttons: [ 62 | { 63 | text: 'No', 64 | handler: () => { 65 | console.log('No clicked'); 66 | } 67 | }, 68 | { 69 | text: 'Yes', 70 | handler: () => { 71 | this.questionService.deleteQuestion(questionId) 72 | .then(res => this.getQuestions()); 73 | this.answerService.getAnswers(questionId) 74 | .then(answers => { 75 | for(let answer of answers){ 76 | this.answerService.deleteAnswer(answer.id); 77 | } 78 | }) 79 | } 80 | } 81 | ] 82 | }); 83 | confirm.present(); 84 | } 85 | 86 | addPositiveVote(question){ 87 | let data = question; 88 | data.positiveVotes += 1; 89 | data.questionSlug = this.category.slug; 90 | this.questionService.updateQuestion(data) 91 | .then(res => this.getQuestions()) 92 | } 93 | 94 | addNegativeVote(question){ 95 | let data = question; 96 | data.negativeVotes += 1; 97 | data.questionSlug = this.category.slug; 98 | this.questionService.updateQuestion(data) 99 | .then(res => this.getQuestions()) 100 | } 101 | 102 | countAnswers(questionId){ 103 | return this.answerService.countAnswers(questionId) 104 | .then(res => console.log(res)) 105 | } 106 | 107 | openAnswers(question){ 108 | this.navCtrl.push(QuestionDetailsPage, { 109 | id: question.id 110 | }); 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /ionic-3/src/pages/learn-feed/learn-feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | Learn Ionic! 8 | 9 | 10 | 11 | 12 | 13 |

14 | Showing: 15 | {{ _query }} 16 | concepts 17 |

18 | 19 | 20 | 21 |

{{ category.title }}

22 |
23 | 24 |

25 | {{ category.description }} 26 |

27 | 28 | 29 | {{ tag.name }} 30 | 31 | 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /ionic-3/src/pages/learn-feed/learn-feed.scss: -------------------------------------------------------------------------------- 1 | learn-feed-page { 2 | .search-query 3 | { 4 | font-size: 1.4rem; 5 | margin: 12px; 6 | margin-bottom: 30px; 7 | color: $theme-color-3; 8 | text-align: center; 9 | 10 | .query-tag 11 | { 12 | border: 1px solid $theme-color-2; 13 | color: $theme-color-2; 14 | background: #FFF; 15 | } 16 | 17 | .query-explanation 18 | { 19 | font-weight: 300; 20 | } 21 | } 22 | 23 | .category-concept-card 24 | { 25 | background-color: $theme-color-3; 26 | border-radius: 15px; 27 | box-shadow: none; 28 | margin-bottom: 20px; 29 | 30 | .category-title 31 | { 32 | color: #FFF; 33 | font-weight: 600; 34 | font-size: 2.2rem; 35 | margin: 0px; 36 | } 37 | 38 | .category-description 39 | { 40 | color: #FFF; 41 | margin-bottom: 20px; 42 | } 43 | 44 | .category-tag 45 | { 46 | border: 1px solid #FFF; 47 | color: #FFF; 48 | background: transparent; 49 | font-weight: 300; 50 | margin-right: 4px; 51 | opacity: 0.6; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ionic-3/src/pages/learn-feed/learn-feed.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController, NavParams } from 'ionic-angular'; 3 | import { isPresent } from 'ionic-angular/util/util'; 4 | import { LearnDetailsPage } from '../learn-details/learn-details'; 5 | import { LearnService } from '../../services/learn.service'; 6 | import { CategoryModel } from '../../services/learn.model'; 7 | 8 | @Component({ 9 | selector: 'learn-feed-page', 10 | templateUrl: 'learn-feed.html', 11 | }) 12 | export class LearnFeedPage { 13 | _query : string = 'all'; 14 | categories : Array = new Array(); 15 | 16 | constructor( 17 | public navCtrl: NavController, 18 | public navParams: NavParams, 19 | public learnService: LearnService 20 | ) { 21 | let query_param = navParams.get('query'); 22 | this._query = isPresent(query_param) ? query_param : 'all'; 23 | } 24 | 25 | ionViewWillEnter() { 26 | this.learnService.getFeedCategories() 27 | .subscribe(data => { 28 | this.categories = data.categories 29 | }); 30 | } 31 | 32 | openDetails(params) { 33 | this.navCtrl.push(LearnDetailsPage, params); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ionic-3/src/pages/manage-answer/manage-answer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ _mode }} Answer 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 31 | 32 | -------------------------------------------------------------------------------- /ionic-3/src/pages/manage-answer/manage-answer.scss: -------------------------------------------------------------------------------- 1 | manage-answer-page { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /ionic-3/src/pages/manage-answer/manage-answer.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavParams, ViewController } from 'ionic-angular'; 3 | import { isPresent } from 'ionic-angular/util/util'; 4 | import { Validators, FormGroup, FormControl} from '@angular/forms'; 5 | import { AnswerService } from '../../services/answer.service'; 6 | import { Answer } from '../../../sdk'; 7 | 8 | 9 | @Component({ 10 | selector: 'manage-answer-page', 11 | templateUrl: 'manage-answer.html' 12 | }) 13 | export class ManageAnswerPage { 14 | 15 | _mode : string; 16 | _question_id: string; 17 | _answer_id: string; 18 | answerForm: FormGroup; 19 | answer: Answer = new Answer(); 20 | 21 | constructor( 22 | public navParams: NavParams, 23 | public viewCtrl: ViewController, 24 | public answerService: AnswerService 25 | ) { 26 | let data = navParams.get('data'); 27 | this._mode = isPresent(data) && isPresent(data.mode) ? data.mode : ''; 28 | this._question_id = isPresent(data) && isPresent(data.questionId) ? data.questionId : ''; 29 | this._answer_id = isPresent(data) && isPresent(data.answerId) ? data.answerId : ''; 30 | } 31 | 32 | ionViewWillLoad() { 33 | let data = this.navParams.get('data'); 34 | if(data.answer){ 35 | this.answer = data.answer; 36 | } 37 | this.answerForm = new FormGroup({ 38 | answer: new FormControl(this.answer.answer, Validators.required) 39 | }) 40 | } 41 | 42 | dismiss() { 43 | let data = { 'foo': 'bar' }; 44 | this.viewCtrl.dismiss(data); 45 | } 46 | 47 | onSubmit(value){ 48 | let data = value; 49 | data.questionId = this._question_id; 50 | if(this.answer.answer){ 51 | data.id = this.answer.id; 52 | data.positiveVotes = this.answer.positiveVotes; 53 | data.negativeVotes = this.answer.negativeVotes; 54 | this.answerService.updateAnswer(data) 55 | .then( res => this.dismiss()) 56 | } 57 | else{ 58 | this.answerService.createAnswer(value) 59 | .then( res => this.dismiss()) 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /ionic-3/src/pages/manage-question/manage-question.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Create Question 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 31 | 32 | -------------------------------------------------------------------------------- /ionic-3/src/pages/manage-question/manage-question.scss: -------------------------------------------------------------------------------- 1 | manage-question-page { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /ionic-3/src/pages/manage-question/manage-question.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavParams, ViewController } from 'ionic-angular'; 3 | import { Validators, FormGroup, FormControl} from '@angular/forms'; 4 | import { isPresent } from 'ionic-angular/util/util'; 5 | import { QuestionService } from '../../services/question.service'; 6 | 7 | @Component({ 8 | selector: 'manage-question-page', 9 | templateUrl: 'manage-question.html' 10 | }) 11 | export class ManageQuestionPage { 12 | 13 | _detail_slug : string; 14 | questionSlug: string; 15 | questionForm: FormGroup; 16 | 17 | constructor( 18 | public navParams: NavParams, 19 | public viewCtrl: ViewController, 20 | public questionService: QuestionService 21 | ) { 22 | this.questionSlug = navParams.get('slug'); 23 | this._detail_slug = isPresent(this.questionSlug) ? this.questionSlug : ''; 24 | } 25 | 26 | ionViewWillLoad() { 27 | this.questionForm = new FormGroup({ 28 | question: new FormControl('', Validators.required) 29 | }) 30 | } 31 | 32 | dismiss() { 33 | let data = { 'foo': 'bar' }; 34 | this.viewCtrl.dismiss(data); 35 | } 36 | 37 | onSubmit(value){ 38 | console.log(this._detail_slug) 39 | let data = value; 40 | data.questionSlug = this.questionSlug; 41 | this.questionService.createQuestion(value) 42 | .then( res => this.dismiss() ) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /ionic-3/src/pages/question-details/question-details.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | Question details 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |

{{ question.question }}

17 |
18 | 19 |

20 | Was this question usefull? Rate it! 21 |

22 | 23 | 24 | 28 | 29 | 30 |
31 | {{ question.positiveVotes - question.negativeVotes }} 32 | 35 |
36 |
37 | 38 | 42 | 43 |
44 |
45 |
46 |
47 | 48 |
49 |

50 | There are no answers to this question. Be the first one to answer! 51 |

52 |
53 | 54 |
55 |

56 | We found 57 | {{ answers.length }} 58 | Answers for this question! 59 |

60 | 61 | 62 | 63 | 64 | 65 | 68 | {{ answer.positiveVotes - answer.negativeVotes }} 69 | 72 | 73 | 74 | 75 | 76 |

{{ answer.answer }}

77 |
78 | 79 |
80 | {{ answer.positiveVotes + answer.negativeVotes }} 81 | Votes 82 |
83 |
84 | 85 | 88 | 91 | 92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 | 101 | 102 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /ionic-3/src/pages/question-details/question-details.scss: -------------------------------------------------------------------------------- 1 | question-details-page { 2 | 3 | .question-details 4 | { 5 | padding: 16px 12px; 6 | min-height: 35%; 7 | position: relative; 8 | background-size: 650px, 565px, cover; 9 | background-repeat: no-repeat; 10 | margin-bottom: 50px; 11 | 12 | &::before 13 | { 14 | content: ''; 15 | position: absolute; 16 | top: 0px; 17 | bottom: 0px; 18 | left: 0px; 19 | right: 0px; 20 | background: rgba($theme-color-2, .8); 21 | opacity: 1; 22 | } 23 | 24 | .details-content 25 | { 26 | align-content: space-between; 27 | z-index: 2; 28 | height: 100%; 29 | 30 | .question-title 31 | { 32 | color: $white; 33 | font-weight: 600; 34 | font-size: 2.2rem; 35 | text-shadow: 1px 1px 1px rgba($black-a, .5); 36 | text-align: center; 37 | margin: 0 0 20px 0; 38 | } 39 | 40 | .vote-call-to-action 41 | { 42 | color: $white; 43 | font-weight: 500; 44 | text-shadow: 1px 1px 1px rgba($black-a, .5); 45 | text-align: center; 46 | margin: 0px; 47 | font-size: 1.6rem; 48 | opacity: .9; 49 | } 50 | 51 | .question-actions-row 52 | { 53 | justify-content: space-between; 54 | margin-top: 10px; 55 | 56 | .question-action-button 57 | { 58 | margin: 0px; 59 | padding: 0px 6px; 60 | 61 | &.up-vote 62 | { 63 | text-align: left; 64 | } 65 | 66 | &.down-vote 67 | { 68 | text-align: right; 69 | } 70 | } 71 | 72 | .score-col 73 | { 74 | position: relative; 75 | 76 | .score-wrapper 77 | { 78 | padding-bottom: 100%; 79 | height: 0px; 80 | width: 100%; 81 | display: block; 82 | position: absolute; 83 | background-color: $black-b; 84 | border-radius: 12px; 85 | 86 | .question-score 87 | { 88 | margin: 4px 0px; 89 | text-align: center; 90 | font-size: 2.8rem; 91 | font-weight: 600; 92 | display: block; 93 | text-shadow: 1px 1px 1px rgba($black-a, .5); 94 | 95 | &.good-score 96 | { 97 | color: color($colors, secondary); 98 | } 99 | 100 | &.no-score 101 | { 102 | color: $white-d; 103 | opacity: 0.3; 104 | } 105 | 106 | &.bad-score 107 | { 108 | color: color($colors, danger); 109 | } 110 | } 111 | 112 | .favourite-button 113 | { 114 | color: gold; 115 | text-shadow: 1px 1px 1px rgba($black-a, .5); 116 | margin: 0px; 117 | width: 100%; 118 | font-size: 1.6rem; 119 | height: 1.6rem; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | .answers-call-out 128 | { 129 | font-size: 1.4rem; 130 | margin: 12px; 131 | margin-bottom: 30px; 132 | color: $theme-color-3; 133 | text-align: center; 134 | line-height: 1.8; 135 | 136 | .call-out-tag 137 | { 138 | border: 1px solid $theme-color-2; 139 | color: $theme-color-2; 140 | background: #FFF; 141 | } 142 | 143 | .call-out-explanation 144 | { 145 | font-weight: 300; 146 | } 147 | } 148 | 149 | .answers-list 150 | { 151 | margin: 0px; 152 | } 153 | 154 | .answer-item 155 | { 156 | padding: 0px; 157 | 158 | &:last-child 159 | { 160 | border-bottom: none !important; 161 | } 162 | 163 | .item-inner 164 | { 165 | padding: 0px !important; 166 | 167 | ion-label { 168 | margin: 0px; 169 | } 170 | } 171 | 172 | .votes-col 173 | { 174 | background-color: $white-b; 175 | 176 | .vote-button 177 | { 178 | font-size: 2.2rem; 179 | margin: 0px; 180 | padding: 0px; 181 | width: 100%; 182 | height: 4rem; 183 | 184 | ion-icon { 185 | padding: 0px; 186 | } 187 | 188 | &.up-vote 189 | { 190 | color: color($colors, secondary); 191 | opacity: 0.25; 192 | } 193 | 194 | &.down-vote 195 | { 196 | color: color($colors, danger); 197 | opacity: 0.25; 198 | } 199 | 200 | &.activated 201 | { 202 | opacity: 1; 203 | } 204 | } 205 | 206 | .answer-score 207 | { 208 | margin: 0px; 209 | text-align: center; 210 | font-size: 2rem; 211 | font-weight: 500; 212 | display: block; 213 | 214 | &.good-score 215 | { 216 | color: color($colors, secondary); 217 | } 218 | 219 | &.no-score 220 | { 221 | color: $theme-color-3; 222 | opacity: 0.3; 223 | } 224 | 225 | &.bad-score 226 | { 227 | color: color($colors, danger); 228 | } 229 | } 230 | } 231 | 232 | // REVIEW: 233 | .answer-details 234 | { 235 | height: 100%; 236 | align-content: space-between; 237 | justify-content: space-between; 238 | 239 | .answer-text 240 | { 241 | color: $theme-color-3; 242 | margin: 0px; 243 | font-size: 1.4rem; 244 | line-height: 1.4; 245 | white-space: normal; 246 | 247 | } 248 | 249 | .actions-details 250 | { 251 | align-self: flex-end; 252 | text-align: right; 253 | 254 | .delete-button 255 | { 256 | margin: 0px; 257 | font-size: 1.6rem; 258 | } 259 | } 260 | 261 | .votes-details, 262 | .answers-details 263 | { 264 | align-self: center; 265 | 266 | .details-wrapper 267 | { 268 | font-size: 1.2rem; 269 | background-color: $black-c; 270 | color: $white-b; 271 | text-align: center; 272 | border-radius: 6px; 273 | margin: 4px; 274 | padding: 3px 0px 6px; 275 | } 276 | 277 | .details-text 278 | { 279 | display: block; 280 | } 281 | 282 | .total-votes, 283 | .total-answers 284 | { 285 | display: block; 286 | font-weight: 500; 287 | font-size: 1.6rem; 288 | } 289 | } 290 | } 291 | 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /ionic-3/src/pages/question-details/question-details.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavController, LoadingController, AlertController, NavParams, ModalController } from 'ionic-angular'; 3 | import { Question } from '../../../sdk'; 4 | import { AnswerService } from '../../services/answer.service' 5 | import { QuestionService } from '../../services/question.service' 6 | 7 | // import { QuestionPage } from '../question/question' 8 | // import { LearnDetailsPage } from '../learn-details/learn-details' 9 | import { ManageAnswerPage } from '../manage-answer/manage-answer'; 10 | 11 | @Component({ 12 | selector: 'question-details-page', 13 | templateUrl: 'question-details.html' 14 | }) 15 | export class QuestionDetailsPage { 16 | 17 | answers: Array = []; 18 | question: any = new Question(); 19 | questionId: any; 20 | 21 | constructor( 22 | public navCtrl: NavController, 23 | public navParams: NavParams, 24 | public questionService: QuestionService, 25 | public answerService: AnswerService, 26 | public loadingCtrl: LoadingController, 27 | public alertCtrl: AlertController, 28 | public modalCtrl: ModalController 29 | ) {} 30 | 31 | createAnswerModal() { 32 | let create_answer_data = { 33 | mode: 'Create', 34 | questionId: this.questionId 35 | }; 36 | let create_answer_modal = this.modalCtrl.create(ManageAnswerPage, { data: create_answer_data }); 37 | create_answer_modal.onDidDismiss(data => { 38 | this.getAnswers(); 39 | }); 40 | create_answer_modal.present(); 41 | } 42 | 43 | editAnswerModal(answer) { 44 | let edit_answer_data = { 45 | mode: 'Edit', 46 | answer: answer, 47 | questionId: this.questionId 48 | }; 49 | let edit_answer_modal = this.modalCtrl.create(ManageAnswerPage, { data: edit_answer_data }); 50 | edit_answer_modal.onDidDismiss(data => { 51 | this.getAnswers(); 52 | }); 53 | edit_answer_modal.present(); 54 | } 55 | 56 | ionViewWillEnter() { 57 | this.questionId = this.navParams.get('id'); 58 | this.getQuestion(); 59 | this.getAnswers(); 60 | } 61 | 62 | getQuestion(){ 63 | let loading = this.loadingCtrl.create({ 64 | content: 'Please wait...' 65 | }); 66 | this.questionService.getQuestion(this.questionId) 67 | .then(res => { 68 | this.question = res[0]; 69 | loading.dismiss(); 70 | }) 71 | } 72 | 73 | getAnswers(){ 74 | let loading = this.loadingCtrl.create({ 75 | content: 'Please wait...' 76 | }); 77 | loading.present(); 78 | this.answerService.getAnswers(this.questionId) 79 | .then(res => { 80 | this.answers = res; 81 | loading.dismiss(); 82 | }) 83 | } 84 | 85 | delete(answerId){ 86 | let confirm = this.alertCtrl.create({ 87 | title: 'Delete answer', 88 | message: 'Are you sure you want to delete this answer?', 89 | buttons: [ 90 | { 91 | text: 'No', 92 | handler: () => { 93 | console.log('No clicked'); 94 | } 95 | }, 96 | { 97 | text: 'Yes', 98 | handler: () => { 99 | this.answerService.deleteAnswer(answerId) 100 | .then(res => this.getAnswers()) 101 | } 102 | } 103 | ] 104 | }); 105 | confirm.present(); 106 | } 107 | 108 | upVoteQuestion(){ 109 | this.question.positiveVotes += 1; 110 | this.questionService.updateQuestion(this.question) 111 | .then(res => console.log(res)) 112 | } 113 | 114 | downVoteQuestion(){ 115 | this.question.negativeVotes += 1; 116 | this.questionService.updateQuestion(this.question) 117 | .then(res => console.log(res)) 118 | } 119 | 120 | addPositiveVote(answer){ 121 | answer.positiveVotes += 1; 122 | this.answerService.updateAnswer(answer) 123 | .then(res => this.getAnswers()) 124 | } 125 | 126 | addNegativeVote(answer){ 127 | answer.negativeVotes += 1; 128 | this.answerService.updateAnswer(answer) 129 | .then(res => this.getAnswers()) 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /ionic-3/src/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out https://googlechrome.github.io/sw-toolbox/docs/master/index.html for 3 | * more info on how to use sw-toolbox to custom configure your service worker. 4 | */ 5 | 6 | 7 | 'use strict'; 8 | importScripts('./build/sw-toolbox.js'); 9 | 10 | self.toolbox.options.cache = { 11 | name: 'ionic-cache' 12 | }; 13 | 14 | // pre-cache our key assets 15 | self.toolbox.precache( 16 | [ 17 | './build/main.js', 18 | './build/main.css', 19 | './build/polyfills.js', 20 | 'index.html', 21 | 'manifest.json' 22 | ] 23 | ); 24 | 25 | // dynamically cache any other local assets 26 | self.toolbox.router.any('/*', self.toolbox.cacheFirst); 27 | 28 | // for any other requests go to the network, cache, 29 | // and then only use that cached resource if your user goes offline 30 | self.toolbox.router.default = self.toolbox.networkFirst; -------------------------------------------------------------------------------- /ionic-3/src/services/answer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AnswerApi, Answer } from '../../sdk'; 3 | import 'rxjs/add/operator/toPromise'; 4 | 5 | @Injectable() 6 | export class AnswerService { 7 | constructor( 8 | private answerApi: AnswerApi 9 | ){} 10 | 11 | getAnswers(questionId){ 12 | let query = { 13 | questionId: questionId 14 | } 15 | return this.answerApi.find({where: query}) 16 | .toPromise() 17 | } 18 | 19 | getAnswer(anserId){ 20 | let query = { 21 | id: anserId 22 | } 23 | return this.answerApi.find({where: query}) 24 | .toPromise() 25 | } 26 | 27 | deleteAnswer(answerId){ 28 | return this.answerApi.deleteById(answerId) 29 | .toPromise() 30 | } 31 | 32 | updateAnswer(values){ 33 | let data = new Answer(); 34 | data.answer = values.answer; 35 | data.positiveVotes = values.positiveVotes; 36 | data.negativeVotes = values.negativeVotes; 37 | data.questionId = values.questionId; 38 | return this.answerApi.updateAttributes(values.id, data) 39 | .toPromise() 40 | } 41 | 42 | createAnswer(values){ 43 | let data = new Answer(); 44 | data.answer = values.answer; 45 | data.questionId = values.questionId; 46 | return this.answerApi.create(data) 47 | .toPromise() 48 | } 49 | 50 | countAnswers(questionId){ 51 | let query = { 52 | questionId: questionId 53 | } 54 | return this.answerApi.count({where: query}) 55 | .toPromise() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ionic-3/src/services/learn.model.ts: -------------------------------------------------------------------------------- 1 | export class CategoryModel { 2 | slug: string; 3 | title: string; 4 | description: string; 5 | background: string; 6 | tags: Array; 7 | } 8 | -------------------------------------------------------------------------------- /ionic-3/src/services/learn.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import 'rxjs/add/operator/map'; 3 | import { Http } from '@angular/http'; 4 | 5 | @Injectable() 6 | export class LearnService { 7 | constructor(public http: Http){} 8 | 9 | getFeedCategories(){ 10 | return this.http.get("./assets/categories/categories.json") 11 | .map((res) => res.json()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ionic-3/src/services/question.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { QuestionApi, Question, LoopBackFilter } from '../../sdk'; 3 | import 'rxjs/add/operator/toPromise'; 4 | 5 | @Injectable() 6 | export class QuestionService { 7 | constructor( 8 | private questionApi: QuestionApi 9 | ){} 10 | 11 | getQuestions(){ 12 | let filter: LoopBackFilter = { 13 | "include":{ 14 | "relation": "answers" 15 | } 16 | } 17 | return this.questionApi.find(filter) 18 | .toPromise() 19 | } 20 | 21 | getQuestion(questionId){ 22 | let query = { 23 | id: questionId 24 | } 25 | return this.questionApi.find({where: query}) 26 | .toPromise() 27 | } 28 | 29 | getQuestionsBySlug(slug){ 30 | let filter: LoopBackFilter = { 31 | "include":{ 32 | "relation": "answers" 33 | }, 34 | "where": { 35 | "questionSlug": slug 36 | } 37 | } 38 | return this.questionApi.find(filter) 39 | .toPromise() 40 | } 41 | 42 | deleteQuestion(questionId){ 43 | return this.questionApi.deleteById(questionId) 44 | .toPromise() 45 | } 46 | 47 | updateQuestion(values){ 48 | let data = new Question(); 49 | data.question = values.question; 50 | data.positiveVotes = values.positiveVotes; 51 | data.negativeVotes = values.negativeVotes; 52 | data.questionSlug = values.questionSlug; 53 | return this.questionApi.updateAttributes(values.id, data) 54 | .toPromise() 55 | } 56 | 57 | createQuestion(values){ 58 | let data = new Question(); 59 | data.question = values.question; 60 | data.questionSlug = values.questionSlug 61 | return this.questionApi.create(data) 62 | .toPromise() 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /ionic-3/src/theme/common.scss: -------------------------------------------------------------------------------- 1 | // Basic 2 | @import "./common/scroll-bar"; 3 | 4 | // Elements 5 | @import "./common/side-menu"; 6 | @import "./common/header"; 7 | 8 | // Views 9 | @import "./common/modals"; 10 | -------------------------------------------------------------------------------- /ionic-3/src/theme/common/header.scss: -------------------------------------------------------------------------------- 1 | @mixin header-styles($background, $color){ 2 | .toolbar-background 3 | { 4 | border: none !important; 5 | background-color: $background; 6 | } 7 | 8 | .toolbar-title, 9 | .back-button, 10 | .bar-button 11 | { 12 | color: $color; 13 | } 14 | } 15 | 16 | ion-header { 17 | @include header-styles(color($colors, header, base), color($colors, header, contrast)); 18 | } 19 | ion-footer { 20 | @include header-styles(color($colors, header, base), color($colors, header, contrast)); 21 | } 22 | -------------------------------------------------------------------------------- /ionic-3/src/theme/common/modals.scss: -------------------------------------------------------------------------------- 1 | ion-modal { 2 | .modal-content 3 | { 4 | background-color: rgba($black-b, 1); 5 | 6 | .modal-form 7 | { 8 | margin-top: 30px; 9 | 10 | ion-item.textarea-item 11 | { 12 | background-color: rgba($black-b, 1); 13 | padding: 0px; 14 | 15 | .item-inner 16 | { 17 | padding: 0px; 18 | border-bottom: none; 19 | } 20 | 21 | ion-label { 22 | color: $black-b; 23 | margin: 8px 8px 0px; 24 | } 25 | 26 | &.input-has-focus 27 | { 28 | ion-label { 29 | color: $white-b; 30 | margin: 0px 0px 8px; 31 | } 32 | } 33 | 34 | ion-textarea { 35 | background-color: $white-b; 36 | color: $black-b; 37 | border-radius: 4px; 38 | 39 | textarea.text-input 40 | { 41 | margin: 8px; 42 | width: calc(100% - 16px); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ionic-3/src/theme/common/scroll-bar.scss: -------------------------------------------------------------------------------- 1 | // Hide all scroll indicators 2 | .scroll-bar-indicator 3 | { 4 | opacity: 0; 5 | } 6 | ::-webkit-scrollbar 7 | { 8 | display: none; 9 | } 10 | -------------------------------------------------------------------------------- /ionic-3/src/theme/common/side-menu.scss: -------------------------------------------------------------------------------- 1 | ion-menu { 2 | .menu-content 3 | { 4 | background-color: color($colors, background, base); 5 | } 6 | 7 | .menu-list 8 | { 9 | margin-top: 20vh; 10 | 11 | .item 12 | { 13 | background-color: color($colors, background, base); 14 | padding-right: 32px; 15 | 16 | &:first-child 17 | { 18 | border-top: solid 1px rgba(#c2c2c2, 0.15); 19 | } 20 | 21 | &:last-child 22 | { 23 | border-bottom: solid 1px rgba(#c2c2c2, 0.15); 24 | } 25 | 26 | .item-inner 27 | { 28 | border-bottom: 1px solid rgba(#c2c2c2, 0.15); 29 | font-size: 1.8rem; 30 | font-weight: 300; 31 | letter-spacing: 1px; 32 | color: $theme-color-3; 33 | } 34 | 35 | &.activated 36 | { 37 | background-color: $theme-color-1; 38 | transition-duration: 0ms; 39 | 40 | .item-inner 41 | { 42 | color: #FFF; 43 | } 44 | 45 | .item-icon 46 | { 47 | color: #FFF; 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ionic-3/src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/v2/theming/ 3 | $font-path: "../assets/fonts"; 4 | 5 | @import "ionic.globals"; 6 | 7 | 8 | // Shared Variables 9 | // -------------------------------------------------- 10 | // To customize the look and feel of this app, you can override 11 | // the Sass variables found in Ionic's source scss files. 12 | // To view all the possible Ionic variables, see: 13 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ 14 | 15 | 16 | 17 | 18 | // Named Color Variables 19 | // -------------------------------------------------- 20 | // Named colors makes it easy to reuse colors on various components. 21 | // It's highly recommended to change the default colors 22 | // to match your app's branding. Ionic uses a Sass map of 23 | // colors so you can add, rename and remove colors as needed. 24 | // The "primary" color is the only required color in the map. 25 | 26 | $white: #FFFFFF; 27 | $white-a: darken($white, 3%); 28 | $white-b: darken($white, 6%); 29 | $white-c: darken($white, 12%); 30 | $white-d: darken($white, 24%); 31 | 32 | $black: rgba(#000000, .8); 33 | $black-a: lighten($black, 10%); 34 | $black-b: lighten($black, 20%); 35 | $black-c: lighten($black, 40%); 36 | $black-d: lighten($black, 60%); 37 | 38 | $theme-color-1: $black-a; 39 | $theme-color-2: #00e9d5; 40 | $theme-color-3: $black-b; 41 | $theme-color-4: #ae75e7; 42 | 43 | $colors: ( 44 | primary: #387ef5, 45 | secondary: #26bf7e, 46 | danger: #ed4f50, 47 | light: #f4f4f4, 48 | dark: $black-b, 49 | ask: $theme-color-4, 50 | answer: $theme-color-2, 51 | header:( 52 | base: $black-a, 53 | contrast: $white-b 54 | ), 55 | background:( 56 | base: $white-b, 57 | contrast: $black-a 58 | ) 59 | ); 60 | 61 | 62 | // App iOS Variables 63 | // -------------------------------------------------- 64 | // iOS only Sass variables can go here 65 | 66 | 67 | 68 | 69 | // App Material Design Variables 70 | // -------------------------------------------------- 71 | // Material Design only Sass variables can go here 72 | 73 | 74 | 75 | 76 | // App Windows Variables 77 | // -------------------------------------------------- 78 | // Windows only Sass variables can go here 79 | 80 | 81 | 82 | 83 | // App Theme 84 | // -------------------------------------------------- 85 | // Ionic apps can have different themes applied, which can 86 | // then be future customized. This import comes last 87 | // so that the above variables are used and Ionic's 88 | // default are overridden. 89 | 90 | @import "ionic.theme.default"; 91 | 92 | 93 | // Ionicons 94 | // -------------------------------------------------- 95 | // The premium icon font for Ionic. For more info, please see: 96 | // http://ionicframework.com/docs/v2/ionicons/ 97 | 98 | @import "ionic.ionicons"; 99 | 100 | 101 | // Fonts 102 | // -------------------------------------------------- 103 | 104 | @import "roboto"; 105 | @import "noto-sans"; 106 | -------------------------------------------------------------------------------- /ionic-3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "target": "es5" 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } -------------------------------------------------------------------------------- /ionic-3/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-variable": true, 4 | "no-unused-variable": [ 5 | true 6 | ] 7 | }, 8 | "rulesDirectory": [ 9 | "node_modules/tslint-eslint-rules/dist/rules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "questions-and-answers-app", 3 | "integrations": { 4 | "capacitor": {} 5 | }, 6 | "type": "angular" 7 | } 8 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "questions-and-answers-app", 3 | "version": "1.0.0", 4 | "author": "IonicThemes Team", 5 | "homepage": "https://ionicthemes.com/", 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/common": "~9.1.6", 17 | "@angular/core": "~9.1.6", 18 | "@angular/forms": "~9.1.6", 19 | "@angular/platform-browser": "~9.1.6", 20 | "@angular/platform-browser-dynamic": "~9.1.6", 21 | "@angular/router": "~9.1.6", 22 | "@capacitor/core": "2.4.0", 23 | "@ionic-native/core": "^5.0.7", 24 | "@ionic-native/splash-screen": "^5.0.0", 25 | "@ionic-native/status-bar": "^5.0.0", 26 | "@ionic/angular": "^5.0.0", 27 | "rxjs": "~6.5.1", 28 | "tslib": "^1.10.0", 29 | "zone.js": "~0.10.2" 30 | }, 31 | "devDependencies": { 32 | "@angular-devkit/build-angular": "~0.901.5", 33 | "@angular/cli": "~9.1.5", 34 | "@angular/compiler": "~9.1.6", 35 | "@angular/compiler-cli": "~9.1.6", 36 | "@angular/language-service": "~9.1.6", 37 | "@capacitor/cli": "2.4.0", 38 | "@ionic/angular-toolkit": "^2.1.1", 39 | "@types/jasmine": "~3.5.0", 40 | "@types/jasminewd2": "~2.0.3", 41 | "@types/node": "^12.11.1", 42 | "codelyzer": "^5.1.2", 43 | "jasmine-core": "~3.5.0", 44 | "jasmine-spec-reporter": "~4.2.1", 45 | "karma": "~5.0.0", 46 | "karma-chrome-launcher": "~3.1.0", 47 | "karma-coverage-istanbul-reporter": "~2.1.0", 48 | "karma-jasmine": "~3.0.1", 49 | "karma-jasmine-html-reporter": "^1.4.2", 50 | "protractor": "~5.4.3", 51 | "ts-node": "~8.3.0", 52 | "tslint": "~6.1.0", 53 | "typescript": "~3.8.3" 54 | }, 55 | "description": "Getting Started With Ionic Tutorial" 56 | } 57 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { PreloadAllModules, RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = [ 5 | { 6 | path: '', 7 | redirectTo: 'learn', 8 | pathMatch: 'full' 9 | }, 10 | { 11 | path: 'learn', 12 | loadChildren: () => import('./learn/categories/learn-categories.module').then(m => m.LearnCategoriesModule) 13 | }, 14 | { 15 | path: 'learn/:category', 16 | loadChildren: () => import('./learn/category/learn-category.module').then(m => m.LearnCategoryModule) 17 | }, 18 | { 19 | path: 'questions/:id/:slug', 20 | loadChildren: () => import('./questions/questions.module').then(m => m.QuestionsModule) 21 | } 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [ 26 | RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }) 27 | ], 28 | exports: [RouterModule] 29 | }) 30 | export class AppRoutingModule {} 31 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tags 7 | Filter categories to learn 8 | 9 | 10 | 12 | All 13 | 14 | 15 | 16 | 18 | Basic 19 | 20 | 21 | 22 | 24 | Advanced 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | ion-menu ion-content { 2 | --background: var(--ion-item-background, var(--ion-background-color, #fff)); 3 | } 4 | 5 | ion-menu.md ion-content { 6 | --padding-start: 8px; 7 | --padding-end: 8px; 8 | --padding-top: 20px; 9 | --padding-bottom: 20px; 10 | } 11 | 12 | ion-menu.md ion-list { 13 | padding: 20px 0; 14 | } 15 | 16 | ion-menu.md ion-note { 17 | margin-bottom: 30px; 18 | } 19 | 20 | ion-menu.md ion-list-header, 21 | ion-menu.md ion-note { 22 | padding-left: 10px; 23 | } 24 | 25 | ion-menu.md ion-list#inbox-list { 26 | border-bottom: 1px solid var(--ion-color-step-150, #d7d8da); 27 | } 28 | 29 | ion-menu.md ion-list#inbox-list ion-list-header { 30 | font-size: 22px; 31 | font-weight: 600; 32 | 33 | min-height: 20px; 34 | } 35 | 36 | ion-menu.md ion-list#labels-list ion-list-header { 37 | font-size: 16px; 38 | 39 | margin-bottom: 18px; 40 | 41 | color: #757575; 42 | 43 | min-height: 26px; 44 | } 45 | 46 | ion-menu.md ion-item { 47 | --padding-start: 10px; 48 | --padding-end: 10px; 49 | border-radius: 4px; 50 | } 51 | 52 | ion-menu.md ion-item.selected { 53 | --background: rgba(var(--ion-color-primary-rgb), 0.14); 54 | } 55 | 56 | ion-menu.md ion-item.selected ion-icon { 57 | color: var(--ion-color-primary); 58 | } 59 | 60 | ion-menu.md ion-item ion-icon { 61 | color: #616e7e; 62 | } 63 | 64 | ion-menu.md ion-item ion-label { 65 | font-weight: 500; 66 | } 67 | 68 | ion-menu.ios ion-content { 69 | --padding-bottom: 20px; 70 | } 71 | 72 | ion-menu.ios ion-list { 73 | padding: 20px 0 0 0; 74 | } 75 | 76 | ion-menu.ios ion-note { 77 | line-height: 24px; 78 | margin-bottom: 20px; 79 | } 80 | 81 | ion-menu.ios ion-item { 82 | --padding-start: 16px; 83 | --padding-end: 16px; 84 | --min-height: 50px; 85 | } 86 | 87 | ion-menu.ios ion-item.selected ion-icon { 88 | color: var(--ion-color-primary); 89 | } 90 | 91 | ion-menu.ios ion-item ion-icon { 92 | font-size: 24px; 93 | color: #73849a; 94 | } 95 | 96 | ion-menu.ios ion-list#labels-list ion-list-header { 97 | margin-bottom: 8px; 98 | } 99 | 100 | ion-menu.ios ion-list-header, 101 | ion-menu.ios ion-note { 102 | padding-left: 16px; 103 | padding-right: 16px; 104 | } 105 | 106 | ion-menu.ios ion-note { 107 | margin-bottom: 8px; 108 | } 109 | 110 | ion-note { 111 | display: inline-block; 112 | font-size: 16px; 113 | 114 | color: var(--ion-color-medium-shade); 115 | } 116 | 117 | ion-item.selected { 118 | --color: var(--ion-color-primary); 119 | } -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 2 | import { TestBed, async } from '@angular/core/testing'; 3 | 4 | import { Platform } from '@ionic/angular'; 5 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 6 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 7 | import { RouterTestingModule } from '@angular/router/testing'; 8 | 9 | import { AppComponent } from './app.component'; 10 | 11 | describe('AppComponent', () => { 12 | 13 | let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy; 14 | 15 | beforeEach(async(() => { 16 | statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']); 17 | splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']); 18 | platformReadySpy = Promise.resolve(); 19 | platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy }); 20 | 21 | TestBed.configureTestingModule({ 22 | declarations: [AppComponent], 23 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 24 | providers: [ 25 | { provide: StatusBar, useValue: statusBarSpy }, 26 | { provide: SplashScreen, useValue: splashScreenSpy }, 27 | { provide: Platform, useValue: platformSpy }, 28 | ], 29 | imports: [ RouterTestingModule.withRoutes([])], 30 | }).compileComponents(); 31 | })); 32 | 33 | it('should create the app', async () => { 34 | const fixture = TestBed.createComponent(AppComponent); 35 | const app = fixture.debugElement.componentInstance; 36 | expect(app).toBeTruthy(); 37 | }); 38 | 39 | it('should initialize the app', async () => { 40 | TestBed.createComponent(AppComponent); 41 | expect(platformSpy.ready).toHaveBeenCalled(); 42 | await platformReadySpy; 43 | expect(statusBarSpy.styleDefault).toHaveBeenCalled(); 44 | expect(splashScreenSpy.hide).toHaveBeenCalled(); 45 | }); 46 | 47 | it('should have menu labels', async () => { 48 | const fixture = await TestBed.createComponent(AppComponent); 49 | await fixture.detectChanges(); 50 | const app = fixture.nativeElement; 51 | const menuItems = app.querySelectorAll('ion-label'); 52 | expect(menuItems.length).toEqual(12); 53 | expect(menuItems[0].textContent).toContain('Inbox'); 54 | expect(menuItems[1].textContent).toContain('Outbox'); 55 | }); 56 | 57 | it('should have urls', async () => { 58 | const fixture = await TestBed.createComponent(AppComponent); 59 | await fixture.detectChanges(); 60 | const app = fixture.nativeElement; 61 | const menuItems = app.querySelectorAll('ion-item'); 62 | expect(menuItems.length).toEqual(12); 63 | expect(menuItems[0].getAttribute('ng-reflect-router-link')).toEqual('/folder/Inbox'); 64 | expect(menuItems[1].getAttribute('ng-reflect-router-link')).toEqual('/folder/Outbox'); 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Platform } from '@ionic/angular'; 4 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 5 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 6 | 7 | @Component({ 8 | selector: 'app-root', 9 | templateUrl: 'app.component.html', 10 | styleUrls: ['app.component.scss'] 11 | }) 12 | export class AppComponent implements OnInit { 13 | constructor( 14 | private platform: Platform, 15 | private splashScreen: SplashScreen, 16 | private statusBar: StatusBar 17 | ) { 18 | this.initializeApp(); 19 | } 20 | 21 | initializeApp() { 22 | this.platform.ready().then(() => { 23 | this.statusBar.styleDefault(); 24 | this.splashScreen.hide(); 25 | }); 26 | } 27 | 28 | ngOnInit() {} 29 | } 30 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { RouteReuseStrategy } from '@angular/router'; 4 | 5 | import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; 6 | import { SplashScreen } from '@ionic-native/splash-screen/ngx'; 7 | import { StatusBar } from '@ionic-native/status-bar/ngx'; 8 | 9 | import { AppComponent } from './app.component'; 10 | import { AppRoutingModule } from './app-routing.module'; 11 | 12 | import { HttpClientModule } from '@angular/common/http'; 13 | 14 | @NgModule({ 15 | declarations: [AppComponent], 16 | entryComponents: [], 17 | imports: [ 18 | BrowserModule, 19 | HttpClientModule, 20 | IonicModule.forRoot(), 21 | AppRoutingModule 22 | ], 23 | providers: [ 24 | StatusBar, 25 | SplashScreen, 26 | { provide: RouteReuseStrategy, useClass: IonicRouteStrategy } 27 | ], 28 | bootstrap: [AppComponent] 29 | }) 30 | export class AppModule {} 31 | -------------------------------------------------------------------------------- /src/app/app.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { AppService } from './questions.service'; 4 | 5 | describe('AppService', () => { 6 | let service: AppService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(AppService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { environment } from '../environments/environment'; 4 | import { Observable } from 'rxjs'; 5 | import { Answer } from './models/answer.model'; 6 | import { Question } from './models/question.model'; 7 | import { Category } from './models/category.model'; 8 | 9 | @Injectable({ 10 | providedIn: 'root' 11 | }) 12 | export class AppService { 13 | 14 | constructor(private http: HttpClient) {} 15 | 16 | // --------------- Categories Operations --------------- 17 | getCategories(): Observable { 18 | return this.http.get(environment.API_URL + 'categories'); 19 | } 20 | 21 | getCategoryBySlug(categorySlug: string): Observable { 22 | return this.http.get(environment.API_URL + 'categories/' + categorySlug); 23 | } 24 | 25 | // --------------- Answers CRUD Operations --------------- 26 | getQuestionAnswers(questionId: string): Observable { 27 | return this.http.get(environment.API_URL + 'answers/' + questionId); 28 | } 29 | 30 | updateAnswer(answer: string, answerId: string) { 31 | const body = { 32 | "_id": answerId, 33 | "answer": answer 34 | } 35 | return this.http.post(environment.API_URL + 'answers/update', body); 36 | } 37 | 38 | deleteAnswer(answerId: string) { 39 | return this.http.delete(environment.API_URL + 'answers/' + answerId); 40 | } 41 | 42 | createAnswer(answer: string, questionId: string) { 43 | const body = { 44 | "answer": answer, 45 | "questionId": questionId 46 | } 47 | return this.http.post(environment.API_URL + 'answers/insert', body); 48 | } 49 | 50 | voteAnswer(answerId: string, vote: number) { 51 | // vote should be 1 or -1 52 | const body = { 53 | "answerId": answerId, 54 | "vote": vote 55 | } 56 | return this.http.post(environment.API_URL + 'answers/vote', body); 57 | } 58 | 59 | // --------------- Questions CRUD Operations --------------- 60 | getQuestionById(questionId: string): Observable { 61 | return this.http.get(environment.API_URL + 'questions/' + questionId); 62 | } 63 | 64 | getQuestionsByCategory(categorySlug: string): Observable { 65 | return this.http.get(environment.API_URL + 'questions/by-category/' + categorySlug); 66 | } 67 | 68 | createQuestion(question: Question) { 69 | const body = { 70 | "slug": this.slugify(question.title), 71 | "title": question.title, 72 | "description": question.description, 73 | "categoryId": question.categoryId 74 | } 75 | return this.http.post(environment.API_URL + 'questions/insert', body); 76 | } 77 | 78 | deleteQuestion(questionId: string) { 79 | return this.http.delete(environment.API_URL + 'questions/' + questionId); 80 | } 81 | 82 | voteQuestion(questionId: string, vote: number) { 83 | // vote should be 1 or -1 84 | const body = { 85 | "questionId": questionId, 86 | "vote": vote 87 | } 88 | return this.http.post(environment.API_URL + 'questions/vote', body); 89 | } 90 | 91 | // --------------- Utils --------------- 92 | slugify(input: string): string { 93 | return input.toString().toLowerCase() 94 | .replace(/\s+/g, '-') // Replace spaces with - 95 | .replace(/[^\w\-]+/g, '') // Remove all non-word chars 96 | .replace(/\-\-+/g, '-') // Replace multiple - with single - 97 | .replace(/^-+/, '') // Trim - from start of text 98 | .replace(/-+$/, ''); // Trim - from end of text 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/app/learn/categories/categories-listing-page/categories-listing-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Learning Categories 7 | 8 | 9 | 10 | 11 | 12 | 13 | Categories Listing 14 | 15 | 16 | 17 |

18 | Showing: 19 | {{ listingTopic }} 20 | concepts 21 |

22 | 23 |
24 | 25 | 26 | Framework 27 | 28 | {{category.title}} 29 | 30 | 31 | 32 | {{category.description}} 33 | 34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /src/app/learn/categories/categories-listing-page/categories-listing-page.component.scss: -------------------------------------------------------------------------------- 1 | .categories-listing-main-header { 2 | // Override Ionic properties 3 | @at-root ion-header#{&}.header-collapse-condense-inactive { 4 | // To fix blink when page loads 5 | ion-title { 6 | visibility: hidden; 7 | } 8 | } 9 | } 10 | 11 | .categories-listing-main-toolbar { 12 | // Override Ionic properties 13 | @at-root ion-toolbar#{&} { 14 | --background: transparent; 15 | --color: var(--ion-color-dark); 16 | } 17 | } 18 | 19 | ion-content { 20 | position: absolute; 21 | top: 0; 22 | border-top: var(--app-header-height); 23 | border-top-style: solid; 24 | // Same as default ion-content background 25 | border-top-color: #FFF; 26 | } 27 | 28 | .categories-call-out { 29 | margin: 15px 20px 30px; 30 | color: rgba(51, 51, 51, 0.8); 31 | text-align: center; 32 | line-height: 1.4; 33 | 34 | ion-badge { 35 | vertical-align: sub; 36 | text-transform: uppercase; 37 | margin: 0px 6px; 38 | } 39 | } 40 | 41 | .category-card { 42 | --color: #FFF; 43 | 44 | ion-card-title, 45 | ion-card-subtitle { 46 | --color: #FFF; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/learn/categories/categories-listing-page/categories-listing-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { IonicModule } from '@ionic/angular'; 3 | 4 | import { CategoriesListingPageComponent } from './categories-listing-page.component'; 5 | 6 | describe('CategoriesListingPageComponent', () => { 7 | let component: CategoriesListingPageComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ CategoriesListingPageComponent ], 13 | imports: [IonicModule.forRoot()] 14 | }).compileComponents(); 15 | 16 | fixture = TestBed.createComponent(CategoriesListingPageComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | })); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/learn/categories/categories-listing-page/categories-listing-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Category } from '../../../models/category.model'; 4 | 5 | @Component({ 6 | selector: 'app-categories-listing-page', 7 | templateUrl: './categories-listing-page.component.html', 8 | styleUrls: ['./categories-listing-page.component.scss'], 9 | }) 10 | export class CategoriesListingPageComponent implements OnInit { 11 | listingTopic = 'all'; 12 | categories: Category[] = []; 13 | 14 | constructor( 15 | private route: ActivatedRoute 16 | ) { } 17 | 18 | ngOnInit() { 19 | this.route.data.subscribe(data => { 20 | this.categories = data.categories; 21 | }); 22 | 23 | this.route.queryParams.subscribe(params => { 24 | console.log('queryParams', params); 25 | // tslint:disable-next-line:no-string-literal 26 | this.listingTopic = (params['topic'] && params['topic'] !== '') ? params['topic'] : this.listingTopic; 27 | }); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/learn/categories/categories-listing-page/categories-listing-page.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve } from '@angular/router'; 3 | import { AppService } from 'src/app/app.service'; 4 | import { Category } from 'src/app/models/category.model'; 5 | import { Observable } from 'rxjs'; 6 | 7 | @Injectable() 8 | export class CategoriesListingPageResolver implements Resolve { 9 | 10 | constructor(private appService: AppService) { } 11 | 12 | resolve(): Observable { 13 | return this.appService.getCategories(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/learn/categories/learn-categories-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { CategoriesListingPageComponent } from './categories-listing-page/categories-listing-page.component'; 5 | import { CategoriesListingPageResolver } from './categories-listing-page/categories-listing-page.resolver'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: CategoriesListingPageComponent, 11 | resolve: { 12 | categories: CategoriesListingPageResolver 13 | } 14 | } 15 | ]; 16 | 17 | @NgModule({ 18 | imports: [RouterModule.forChild(routes)], 19 | exports: [RouterModule], 20 | providers: [CategoriesListingPageResolver] 21 | }) 22 | export class LearnCategoriesRoutingModule { } 23 | -------------------------------------------------------------------------------- /src/app/learn/categories/learn-categories.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { IonicModule } from '@ionic/angular'; 4 | 5 | import { LearnCategoriesRoutingModule } from './learn-categories-routing.module'; 6 | import { CategoriesListingPageComponent } from './categories-listing-page/categories-listing-page.component'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [CategoriesListingPageComponent], 11 | imports: [ 12 | CommonModule, 13 | IonicModule, 14 | LearnCategoriesRoutingModule 15 | ] 16 | }) 17 | export class LearnCategoriesModule { } 18 | -------------------------------------------------------------------------------- /src/app/learn/category/category-details-page/category-details-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Learn {{ category.title | titlecase }} 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | Learn all about {{ category.title }} 16 | 17 | 18 |

19 | {{ category.description }} 20 |

21 |
22 | 23 |

24 | Find all the Questions and Answers about 25 | {{ category.title }} 26 | from the community 27 |

28 | 29 |

30 | There are no questions on 31 | {{ category.title }} 32 | yet. Be the first to ask! 33 |

34 | 35 | 36 | 37 |
38 | Votes 39 | 40 | {{ question.positiveVotes + question.negativeVotes }} 41 | 42 |
43 |
44 | Answers 45 | 46 | {{ question.answersCount }} 47 | 48 |
49 |
50 | 51 | Delete 52 | 53 |
54 |
55 | 56 | 57 | 58 |

59 | {{ question.title }} 60 |

61 |

62 | {{ question.description }} 63 |

64 |
65 |
66 | Asked on 67 | {{ question.createdDate | date }} 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 | 78 | Have a Question? Ask! 79 | 80 |
81 |
82 | -------------------------------------------------------------------------------- /src/app/learn/category/category-details-page/category-details-page.component.scss: -------------------------------------------------------------------------------- 1 | .category-details-main-header { 2 | // Override Ionic properties 3 | @at-root ion-header#{&}.header-collapse-condense-inactive { 4 | // To fix blink when page loads 5 | ion-title { 6 | visibility: hidden; 7 | } 8 | } 9 | } 10 | 11 | .category-details-main-toolbar { 12 | // Override Ionic properties 13 | @at-root ion-toolbar#{&} { 14 | --background: transparent; 15 | --color: var(--ion-color-light); 16 | } 17 | } 18 | 19 | ion-content { 20 | position: absolute; 21 | top: 0; 22 | border-top: var(--app-header-height); 23 | border-top-style: solid; 24 | border-top-color: var(--category-color); 25 | 26 | .category-brief { 27 | background-color: var(--category-color); 28 | } 29 | } 30 | 31 | .category-details-wrapper { 32 | padding-bottom: 70px; 33 | } 34 | 35 | .category-fixed-content { 36 | bottom: 0; 37 | left: 0; 38 | right: 0; 39 | padding: 10px; 40 | height: 70px; 41 | background-color: #FFF; 42 | display: flex; 43 | align-items: center; 44 | } 45 | 46 | .ask-question-btn { 47 | margin: 0; 48 | flex: 1 0 100%; 49 | height: 100%; 50 | 51 | // Override Ionic properties 52 | @at-root ion-button#{&} { 53 | font-weight: 600; 54 | } 55 | } 56 | 57 | .category-brief { 58 | height: 250px; 59 | display: flex; 60 | flex-direction: column; 61 | justify-content: space-evenly; 62 | 63 | // Override Ionic properties 64 | ion-toolbar.category-brief-toolbar { 65 | --background: transparent; 66 | --border-width: 0px; 67 | 68 | padding-top: 30px; 69 | 70 | ion-title { 71 | font-size: 20px; 72 | padding: 0px; 73 | color: #FFF; 74 | } 75 | } 76 | 77 | .category-description { 78 | margin: 20px 15%; 79 | text-align: center; 80 | color: #FFF; 81 | } 82 | } 83 | 84 | .questions-call-out { 85 | margin: 15px 20px 10px; 86 | color: rgba(51, 51, 51, 0.8); 87 | text-align: center; 88 | line-height: 1.4; 89 | 90 | ion-badge { 91 | vertical-align: sub; 92 | text-transform: uppercase; 93 | margin: 0px 6px; 94 | } 95 | } 96 | 97 | .category-question-item-row { 98 | margin: 24px 16px; 99 | 100 | // Override Ionic properties 101 | @at-root ion-row#{&} { 102 | --ion-grid-column-padding: 0px; 103 | } 104 | 105 | &:focus-within { 106 | .question-attribute { 107 | transition: transform 500ms cubic-bezier(0.12, 0.72, 0.29, 1) 0s, -webkit-transform 500ms cubic-bezier(0.12, 0.72, 0.29, 1) 0s; 108 | transform: scale3d(0.97, 0.97, 1); 109 | } 110 | } 111 | } 112 | 113 | .question-reputation-col { 114 | padding-inline-end: 12px; 115 | padding-top: 8px; 116 | padding-bottom: 8px; 117 | display: flex; 118 | flex-direction: column; 119 | } 120 | 121 | .question-attribute { 122 | transform: translateZ(0px); 123 | padding: 6px; 124 | border-radius: 6px; 125 | background-color: var(--ion-color-light); 126 | border: 1px solid var(--ion-color-medium-tint); 127 | 128 | & + .question-attribute{ 129 | margin-top: 10px; 130 | } 131 | 132 | .question-attribute-title { 133 | display: block; 134 | font-size: 10px; 135 | font-weight: 500; 136 | text-align: center; 137 | text-transform: uppercase; 138 | margin-bottom: 4px; 139 | color: var(--ion-color-dark-tint); 140 | } 141 | 142 | .question-attribute-value { 143 | margin: 0px; 144 | text-align: center; 145 | font-size: 18px; 146 | font-weight: 500; 147 | display: block; 148 | } 149 | } 150 | 151 | .question-score { 152 | .question-score-value { 153 | --question-score-color: var(--ion-color-medium-tint); 154 | 155 | color: var(--question-score-color); 156 | 157 | &.good-score { 158 | --question-score-color: var(--ion-color-success-shade); 159 | } 160 | 161 | &.bad-score { 162 | --question-score-color: var(--ion-color-danger-shade); 163 | } 164 | } 165 | } 166 | 167 | .question-answers { 168 | .question-answers-value { 169 | --question-score-color: var(--ion-color-medium-tint); 170 | 171 | color: var(--question-score-color); 172 | 173 | &.has-answers { 174 | --question-score-color: var(--ion-color-success-shade); 175 | } 176 | } 177 | } 178 | 179 | .question-actions { 180 | display: flex; 181 | justify-content: center; 182 | margin-top: auto; 183 | padding-top: 5px; 184 | 185 | // Override Ionic styles 186 | ion-button { 187 | --padding-start: 5px; 188 | --padding-end: 5px; 189 | 190 | height: initial; 191 | // This way it has the same height as the question-date 192 | margin-top: 3px; 193 | margin-bottom: 0px; 194 | } 195 | } 196 | 197 | .question-item-details { 198 | height: 100%; 199 | 200 | // Override Ionic properties 201 | @at-root ion-card#{&} { 202 | --background: transparent; 203 | 204 | margin: 0px; 205 | // Remove card outline styles 206 | box-shadow: none; 207 | 208 | &::part(native) { 209 | height: 100%; 210 | } 211 | } 212 | 213 | ion-card-content { 214 | padding: 8px; 215 | display: flex; 216 | flex-direction: column; 217 | height: 100%; 218 | } 219 | 220 | .question-title { 221 | margin-top: 0px; 222 | margin-bottom: 8px; 223 | color: var(--ion-color-tertiary); 224 | } 225 | 226 | .question-description { 227 | margin: 0px; 228 | margin-bottom: 10px; 229 | color: var(--ion-color-dark-tint); 230 | } 231 | 232 | .question-secondary-info { 233 | display: flex; 234 | justify-content: flex-end; 235 | font-size: 12px; 236 | color: var(--ion-color-medium-tint); 237 | 238 | // To create a spece from the other elements 239 | margin-top: auto; 240 | 241 | .question-secondary-attribute-title { 242 | display: inline-block; 243 | } 244 | 245 | .question-secondary-attribute-value { 246 | display: inline-block; 247 | font-weight: 500; 248 | margin-inline-start: 4px; 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/app/learn/category/category-details-page/category-details-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { IonicModule } from '@ionic/angular'; 3 | 4 | import { CategoryDetailsPageComponent } from './category-details-page.component'; 5 | 6 | describe('CategoryDetailsPageComponent', () => { 7 | let component: CategoryDetailsPageComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ CategoryDetailsPageComponent ], 13 | imports: [IonicModule.forRoot()] 14 | }).compileComponents(); 15 | 16 | fixture = TestBed.createComponent(CategoryDetailsPageComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | })); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/learn/category/category-details-page/category-details-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { ModalController, IonRouterOutlet, AlertController } from '@ionic/angular'; 4 | import { AskQuestionModalComponent } from 'src/app/questions/ask-question-modal/ask-question-modal.component'; 5 | import { AppService } from 'src/app/app.service'; 6 | import { Question } from 'src/app/models/question.model'; 7 | import { Category } from 'src/app/models/category.model'; 8 | 9 | @Component({ 10 | selector: 'app-category-details-page', 11 | templateUrl: './category-details-page.component.html', 12 | styleUrls: ['./category-details-page.component.scss'], 13 | }) 14 | export class CategoryDetailsPageComponent implements OnInit { 15 | questions: Question[]; 16 | category: Category; 17 | 18 | constructor( 19 | private route: ActivatedRoute, 20 | public modalController: ModalController, 21 | private routerOutlet: IonRouterOutlet, 22 | public alertController: AlertController, 23 | private appService: AppService 24 | ) { } 25 | 26 | ngOnInit() { 27 | this.route.data.subscribe(pageData => { 28 | this.category = pageData.data.category; 29 | this.questions = pageData.data.questions? pageData.data.questions: []; 30 | }); 31 | } 32 | 33 | async createQuestion() { 34 | const modal = await this.modalController.create({ 35 | component: AskQuestionModalComponent, 36 | swipeToClose: true, 37 | presentingElement: this.routerOutlet.nativeEl, 38 | componentProps: { 39 | 'categoryId': this.category._id 40 | } 41 | }); 42 | await modal.present(); 43 | 44 | const { data } = await modal.onWillDismiss(); 45 | if (data.success) { 46 | const question = data.success; 47 | question.answersCount = 0; 48 | this.questions.push(question); 49 | } 50 | } 51 | 52 | async deleteQuestion(question: Question) { 53 | const alert = await this.alertController.create({ 54 | header: 'Confirm', 55 | message: 'Are you sure you want to delete this Question?', 56 | buttons: [ 57 | { 58 | text: 'Cancel', 59 | role: 'cancel', 60 | cssClass: 'secondary' 61 | }, { 62 | text: 'Yes', 63 | handler: () => { 64 | this.appService.deleteQuestion(question._id) 65 | .subscribe(res => { 66 | this.questions = this.questions.filter(x => x._id != question._id); 67 | }, err => { 68 | console.log('error', err); 69 | }) 70 | } 71 | } 72 | ] 73 | }); 74 | await alert.present(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/app/learn/category/category-details-page/category-details-page.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; 3 | import { AppService } from 'src/app/app.service'; 4 | import { forkJoin } from 'rxjs'; 5 | 6 | @Injectable() 7 | export class CategoryDetailsPageResolver implements Resolve { 8 | 9 | constructor( 10 | private appService: AppService 11 | ) { } 12 | 13 | resolve(route: ActivatedRouteSnapshot) { 14 | const categorySlug = route.paramMap.get('category'); 15 | const category = this.appService.getCategoryBySlug(categorySlug); 16 | const questionsDtos = this.appService.getQuestionsByCategory(categorySlug); 17 | 18 | return forkJoin({ questions: questionsDtos, category: category }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/learn/category/learn-category-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { CategoryDetailsPageComponent } from './category-details-page/category-details-page.component'; 4 | import { CategoryDetailsPageResolver } from './category-details-page/category-details-page.resolver'; 5 | 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: CategoryDetailsPageComponent, 11 | resolve: { 12 | data: CategoryDetailsPageResolver 13 | } 14 | } 15 | ]; 16 | 17 | @NgModule({ 18 | imports: [RouterModule.forChild(routes)], 19 | exports: [RouterModule], 20 | providers: [CategoryDetailsPageResolver] 21 | }) 22 | export class LearnCategoryRoutingModule { } 23 | -------------------------------------------------------------------------------- /src/app/learn/category/learn-category.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { LearnCategoryRoutingModule } from './learn-category-routing.module'; 5 | import { CategoryDetailsPageComponent } from './category-details-page/category-details-page.component'; 6 | import { IonicModule } from '@ionic/angular'; 7 | import { QuestionsSharedModule } from 'src/app/questions/questions-shared.module'; 8 | 9 | @NgModule({ 10 | declarations: [CategoryDetailsPageComponent], 11 | imports: [ 12 | CommonModule, 13 | IonicModule, 14 | LearnCategoryRoutingModule, 15 | QuestionsSharedModule 16 | ] 17 | }) 18 | export class LearnCategoryModule { } 19 | -------------------------------------------------------------------------------- /src/app/models/answer.model.ts: -------------------------------------------------------------------------------- 1 | export class Answer { 2 | _id: string; 3 | answer: string; 4 | questionId: string; 5 | positiveVotes: number; 6 | negativeVotes: number; 7 | createdDate: Date; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/models/category.model.ts: -------------------------------------------------------------------------------- 1 | export class Category { 2 | _id: string; 3 | slug: string; 4 | title: string; 5 | description: string; 6 | color: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/models/question.model.ts: -------------------------------------------------------------------------------- 1 | export class Question { 2 | _id: string; 3 | slug: string; 4 | title: string; 5 | description: string; 6 | categoryId: string; 7 | positiveVotes: number; 8 | negativeVotes: number; 9 | createdDate: Date; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/questions/answer-question-modal/answer-question-modal.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Your Answer 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |

17 | Please be sure to answer the question. Provide details and share your research. 18 |

19 | 20 | Description 21 | 22 | 23 |
24 | 25 | Post your Answer 26 | Update your answer 27 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /src/app/questions/answer-question-modal/answer-question-modal.component.scss: -------------------------------------------------------------------------------- 1 | .answer-question-form-row { 2 | margin: 24px 16px; 3 | 4 | // Override Ionic properties 5 | @at-root ion-row#{&} { 6 | --ion-grid-column-padding: 0px; 7 | } 8 | } 9 | 10 | .answer-form-input-description { 11 | color: var(--ion-color-medium-shade); 12 | font-size: 14px; 13 | } 14 | 15 | .answer-form-input-col { 16 | margin-bottom: 40px; 17 | } 18 | 19 | .answer-form-input-item { 20 | // Override Ionic styles 21 | --padding-start: 0px; 22 | } -------------------------------------------------------------------------------- /src/app/questions/answer-question-modal/answer-question-modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { IonicModule } from '@ionic/angular'; 3 | 4 | import { AnswerQuestionModalComponent } from './answer-question-modal.component'; 5 | 6 | describe('AnswerQuestionModalComponent', () => { 7 | let component: AnswerQuestionModalComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ AnswerQuestionModalComponent ], 13 | imports: [IonicModule.forRoot()] 14 | }).compileComponents(); 15 | 16 | fixture = TestBed.createComponent(AnswerQuestionModalComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | })); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/questions/answer-question-modal/answer-question-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { FormGroup, FormControl, Validators } from '@angular/forms'; 3 | import { ModalController } from '@ionic/angular'; 4 | import { AppService } from '../../app.service'; 5 | import { Answer } from '../../models/answer.model'; 6 | import { Observable } from 'rxjs'; 7 | 8 | @Component({ 9 | selector: 'app-answer-question-modal', 10 | templateUrl: './answer-question-modal.component.html', 11 | styleUrls: ['./answer-question-modal.component.scss'], 12 | }) 13 | export class AnswerQuestionModalComponent implements OnInit { 14 | answerForm: FormGroup; 15 | @Input() questionId: string; 16 | @Input() answerToUpdate: Answer; 17 | 18 | constructor( 19 | private modalController: ModalController, 20 | private appService: AppService 21 | ){} 22 | 23 | ngOnInit() { 24 | this.answerForm = new FormGroup({ 25 | answer: new FormControl(this.answerToUpdate? this.answerToUpdate.answer: '', Validators.required), 26 | }); 27 | } 28 | 29 | async submitAnswer() { 30 | const answer = this.answerForm.value.answer; 31 | let observable: Observable; 32 | 33 | if (this.answerToUpdate) { 34 | observable = this.appService.updateAnswer(answer, this.answerToUpdate._id); 35 | } else { 36 | observable = this.appService.createAnswer(answer, this.questionId); 37 | } 38 | 39 | observable.subscribe( 40 | res => { 41 | this.dismissModal(this.answerToUpdate? answer: res, null); 42 | }, 43 | error => { 44 | console.log("error", error); 45 | this.dismissModal(null, error); 46 | } 47 | ); 48 | } 49 | 50 | dismissModal(success?: Object, error?: any) { 51 | this.modalController.dismiss({ 52 | 'success': success, 53 | 'error': error 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/questions/ask-question-modal/ask-question-modal.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Ask a Question 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |

17 | Please be specific and imagine you're asking a question to another person. 18 |

19 | 20 | Title 21 | 22 | 23 |
24 | 25 |

26 | Include all the information someone would need to answer your question. 27 |

28 | 29 | Description 30 | 31 | 32 |
33 | 34 | Ask 35 | 36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /src/app/questions/ask-question-modal/ask-question-modal.component.scss: -------------------------------------------------------------------------------- 1 | .ask-question-form-row { 2 | margin: 24px 16px; 3 | 4 | // Override Ionic properties 5 | @at-root ion-row#{&} { 6 | --ion-grid-column-padding: 0px; 7 | } 8 | } 9 | 10 | .ask-form-input-description { 11 | color: var(--ion-color-medium-shade); 12 | font-size: 14px; 13 | } 14 | 15 | .ask-form-input-col { 16 | margin-bottom: 40px; 17 | } 18 | 19 | .ask-form-input-item { 20 | // Override Ionic styles 21 | --padding-start: 0px; 22 | } -------------------------------------------------------------------------------- /src/app/questions/ask-question-modal/ask-question-modal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { IonicModule } from '@ionic/angular'; 3 | 4 | import { AskQuestionModalComponent } from './ask-question-modal.component'; 5 | 6 | describe('AskQuestionModalComponent', () => { 7 | let component: AskQuestionModalComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ AskQuestionModalComponent ], 13 | imports: [IonicModule.forRoot()] 14 | }).compileComponents(); 15 | 16 | fixture = TestBed.createComponent(AskQuestionModalComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | })); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/questions/ask-question-modal/ask-question-modal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { ModalController } from '@ionic/angular'; 3 | import { FormGroup, FormControl, Validators } from '@angular/forms'; 4 | import { AppService } from '../../app.service'; 5 | import { Question } from '../../models/question.model'; 6 | 7 | @Component({ 8 | selector: 'app-ask-question-modal', 9 | templateUrl: './ask-question-modal.component.html', 10 | styleUrls: ['./ask-question-modal.component.scss'], 11 | }) 12 | export class AskQuestionModalComponent implements OnInit { 13 | askQuestionForm: FormGroup; 14 | @Input() categoryId: string; 15 | 16 | constructor( 17 | private modalController: ModalController, 18 | private appService: AppService 19 | ){} 20 | 21 | ngOnInit() { 22 | this.askQuestionForm = new FormGroup({ 23 | title: new FormControl('', Validators.required), 24 | description: new FormControl('', Validators.required) 25 | }); 26 | } 27 | 28 | createQuestion() { 29 | let question = new Question(); 30 | question.title = this.askQuestionForm.value.title;; 31 | question.description = this.askQuestionForm.value.description;; 32 | question.categoryId = this.categoryId; 33 | 34 | this.appService.createQuestion(question) 35 | .subscribe( 36 | res => { 37 | this.dismissModal(res, null); 38 | }, 39 | error => { 40 | this.dismissModal(null, error); 41 | } 42 | ); 43 | } 44 | 45 | dismissModal(success?: Object, error?: any) { 46 | this.modalController.dismiss({ 47 | 'success': success, 48 | 'error': error 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/questions/question-details-page/question-details-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Question Details 7 | 8 | 9 | 10 | 11 |
12 |
13 |

14 | {{question.title}} 15 |

16 |

17 | {{ question.description }} 18 |

19 |
20 | 21 | 22 | 23 | 24 |

25 | Was this question useful? Rate it! 26 |

27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | {{ question.positiveVotes - question.negativeVotes }} 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 |

45 | We found 46 | {{ answers.length }} 47 | 48 | answers for this question. 49 | 50 |

51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {{ answer.positiveVotes - answer.negativeVotes }} 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |

70 | {{ answer.answer }} 71 |

72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 |
80 |
81 |
82 | Answered on 83 | {{ answer.createdDate | date }} 84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | 92 |
93 | 94 | Know the Answer? Don't be shy! 95 | 96 |
97 |
98 | -------------------------------------------------------------------------------- /src/app/questions/question-details-page/question-details-page.component.scss: -------------------------------------------------------------------------------- 1 | .question-details-main-toolbar { 2 | // Override Ionic properties 3 | @at-root ion-toolbar#{&} { 4 | --background: transparent; 5 | --color: var(--ion-color-tertiary-contrast); 6 | } 7 | } 8 | 9 | ion-content { 10 | position: absolute; 11 | top: 0; 12 | border-top: var(--app-header-height); 13 | border-top-style: solid; 14 | border-top-color: var(--ion-color-tertiary); 15 | } 16 | 17 | .question-details-wrapper { 18 | padding-bottom: 70px; 19 | } 20 | 21 | .question-fixed-content { 22 | bottom: 0; 23 | left: 0; 24 | right: 0; 25 | padding: 10px; 26 | height: 70px; 27 | background-color: #FFF; 28 | display: flex; 29 | align-items: center; 30 | } 31 | 32 | .answer-question-btn { 33 | margin: 0; 34 | flex: 1 0 100%; 35 | height: 100%; 36 | 37 | // Override Ionic properties 38 | @at-root ion-button#{&} { 39 | font-weight: 600; 40 | } 41 | } 42 | 43 | :host { 44 | --vote-box-height: 90px; 45 | } 46 | 47 | .question-brief { 48 | background: var(--ion-color-tertiary); 49 | height: 250px; 50 | display: flex; 51 | flex-direction: column; 52 | justify-content: space-evenly; 53 | padding-bottom: calc(var(--vote-box-height) / 2); 54 | 55 | .question-title { 56 | margin: 30px 10% 0px; 57 | text-align: center; 58 | color: var(--ion-color-tertiary-contrast); 59 | } 60 | 61 | .question-description { 62 | margin: 20px 15%; 63 | text-align: center; 64 | color: #FFF; 65 | } 66 | } 67 | 68 | .vote-question-box-row { 69 | margin: 0px 16px; 70 | margin-top: calc(calc(var(--vote-box-height) / 2) * -1); 71 | height: var(--vote-box-height); 72 | 73 | // Override Ionic properties 74 | @at-root ion-row#{&} { 75 | --ion-grid-column-padding: 0px; 76 | } 77 | 78 | .vote-call-to-action { 79 | display: flex; 80 | flex-direction: column; 81 | 82 | .call-to-action-message { 83 | margin: 0px 16px; 84 | margin-top: auto; 85 | height: calc(var(--vote-box-height) / 2); 86 | line-height: calc(var(--vote-box-height) / 2); 87 | text-align: center; 88 | color: var(--ion-color-medium-tint); 89 | 90 | b { 91 | color: var(--ion-color-tertiary); 92 | margin-inline-start: 4px; 93 | } 94 | } 95 | } 96 | 97 | .vote-question-actions-col { 98 | padding-inline-start: 12px; 99 | } 100 | 101 | .vote-actions { 102 | height: 100%; 103 | } 104 | } 105 | 106 | .answers-call-out { 107 | margin: 30px 20px 10px; 108 | color: rgba(51, 51, 51, 0.8); 109 | text-align: center; 110 | line-height: 1.4; 111 | 112 | ion-badge { 113 | vertical-align: sub; 114 | text-transform: uppercase; 115 | margin: 0px 6px; 116 | } 117 | } 118 | 119 | 120 | .question-answer-item-row { 121 | margin: 24px 16px; 122 | 123 | // Override Ionic properties 124 | @at-root ion-row#{&} { 125 | --ion-grid-column-padding: 0px; 126 | } 127 | } 128 | 129 | .answer-reputation-col { 130 | padding-inline-end: 12px; 131 | } 132 | 133 | .item-reputation { 134 | display: flex; 135 | flex-direction: column; 136 | justify-content: space-between; 137 | 138 | // Override Ionic properties 139 | @at-root ion-card#{&} { 140 | margin: 0px; 141 | } 142 | 143 | ion-button { 144 | --padding-start: 6px; 145 | --padding-end: 6px; 146 | } 147 | 148 | .reputation-score { 149 | --reputation-score-color: var(--ion-color-medium-tint); 150 | 151 | margin: 0px; 152 | text-align: center; 153 | font-size: 18px; 154 | font-weight: 500; 155 | display: block; 156 | color: var(--reputation-score-color); 157 | 158 | &.good-score { 159 | --reputation-score-color: var(--ion-color-success-shade); 160 | } 161 | 162 | &.bad-score { 163 | --reputation-score-color: var(--ion-color-danger-shade); 164 | } 165 | } 166 | } 167 | 168 | .answer-item-details { 169 | height: 100%; 170 | 171 | // Override Ionic properties 172 | @at-root ion-card#{&} { 173 | --background: transparent; 174 | 175 | margin: 0px; 176 | // Remove card outline styles 177 | box-shadow: none; 178 | 179 | &::part(native) { 180 | height: 100%; 181 | } 182 | } 183 | 184 | ion-card-content { 185 | padding: 8px; 186 | display: flex; 187 | flex-direction: column; 188 | height: 100%; 189 | } 190 | 191 | .answer-description { 192 | margin: 0px; 193 | color: var(--ion-color-medium-shade); 194 | } 195 | 196 | .answer-actions { 197 | display: flex; 198 | justify-content: flex-start; 199 | margin-bottom: 10px; 200 | 201 | // Override Ionic styles 202 | ion-button { 203 | --padding-start: 5px; 204 | --padding-end: 5px; 205 | } 206 | } 207 | 208 | .answer-secondary-info { 209 | display: flex; 210 | justify-content: flex-end; 211 | font-size: 12px; 212 | color: var(--ion-color-medium-tint); 213 | 214 | // To create a spece from the other elements 215 | margin-top: auto; 216 | 217 | .answer-secondary-attribute-title { 218 | display: inline-block; 219 | } 220 | 221 | .answer-secondary-attribute-value { 222 | display: inline-block; 223 | font-weight: 500; 224 | margin-inline-start: 4px; 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/app/questions/question-details-page/question-details-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { IonicModule } from '@ionic/angular'; 3 | 4 | import { QuestionDetailsPageComponent } from './question-details-page.component'; 5 | 6 | describe('QuestionDetailsPageComponent', () => { 7 | let component: QuestionDetailsPageComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [ QuestionDetailsPageComponent ], 13 | imports: [IonicModule.forRoot()] 14 | }).compileComponents(); 15 | 16 | fixture = TestBed.createComponent(QuestionDetailsPageComponent); 17 | component = fixture.componentInstance; 18 | fixture.detectChanges(); 19 | })); 20 | 21 | it('should create', () => { 22 | expect(component).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/questions/question-details-page/question-details-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { ModalController, IonRouterOutlet, AlertController } from '@ionic/angular'; 4 | import { AnswerQuestionModalComponent } from '../answer-question-modal/answer-question-modal.component'; 5 | import { AppService } from '../../app.service'; 6 | import { Answer } from '../../models/answer.model'; 7 | import { Question } from '../../models/question.model'; 8 | 9 | @Component({ 10 | selector: 'app-question-details-page', 11 | templateUrl: './question-details-page.component.html', 12 | styleUrls: ['./question-details-page.component.scss'], 13 | }) 14 | export class QuestionDetailsPageComponent implements OnInit { 15 | answers: Answer[]; 16 | question: Question; 17 | 18 | constructor( 19 | private route: ActivatedRoute, 20 | private appService: AppService, 21 | public modalController: ModalController, 22 | private routerOutlet: IonRouterOutlet, 23 | public alertController: AlertController 24 | ) { } 25 | 26 | ngOnInit() { 27 | this.route.data.subscribe(pageData => { 28 | this.answers = pageData.data.answers; 29 | this.question = pageData.data.question; 30 | }); 31 | } 32 | 33 | voteQuestion(vote: number) { 34 | this.appService.voteQuestion(this.question._id, vote) 35 | .subscribe((res: any) => { 36 | if(res.status == 200) { 37 | if (vote == 1) { 38 | this.question.positiveVotes ++; 39 | } else { 40 | this.question.negativeVotes ++; 41 | } 42 | } 43 | }) 44 | } 45 | 46 | voteAnswer(answer: Answer, vote: number) { 47 | this.appService.voteAnswer(answer._id, vote) 48 | .subscribe((res: any) => { 49 | if(res.status == 200) { 50 | if (vote == 1) { 51 | answer.positiveVotes ++; 52 | } else { 53 | answer.negativeVotes ++; 54 | } 55 | } 56 | }) 57 | } 58 | 59 | async deleteAnswer(answer: Answer) { 60 | const alert = await this.alertController.create({ 61 | header: 'Confirm', 62 | message: 'Are you sure you want to delete this answer?', 63 | buttons: [ 64 | { 65 | text: 'Cancel', 66 | role: 'cancel', 67 | cssClass: 'secondary' 68 | }, { 69 | text: 'Yes', 70 | handler: () => { 71 | this.appService.deleteAnswer(answer._id) 72 | .subscribe(res => { 73 | this.answers = this.answers.filter(x => x._id != answer._id); 74 | }, err => { 75 | console.log('error', err); 76 | }) 77 | } 78 | } 79 | ] 80 | }); 81 | 82 | await alert.present(); 83 | } 84 | 85 | async updateAnswer(answer: Answer) { 86 | const modal = await this.modalController.create({ 87 | component: AnswerQuestionModalComponent, 88 | swipeToClose: true, 89 | presentingElement: this.routerOutlet.nativeEl, 90 | componentProps: { 91 | 'answerToUpdate': answer, 92 | 'questionId': this.question._id 93 | } 94 | }); 95 | 96 | await modal.present(); 97 | 98 | const { data } = await modal.onWillDismiss(); 99 | if (data.success) { 100 | answer.answer = data.success; 101 | } 102 | } 103 | 104 | async openAnswerModal() { 105 | const modal = await this.modalController.create({ 106 | component: AnswerQuestionModalComponent, 107 | swipeToClose: true, 108 | presentingElement: this.routerOutlet.nativeEl, 109 | componentProps: { 110 | 'questionId': this.question._id 111 | } 112 | }); 113 | 114 | await modal.present(); 115 | 116 | const { data } = await modal.onWillDismiss(); 117 | if (data.success) { 118 | const answer = data.success; 119 | this.answers.push(answer); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/app/questions/question-details-page/question-details-page.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; 3 | import { forkJoin } from 'rxjs'; 4 | import { AppService } from 'src/app/app.service'; 5 | 6 | @Injectable() 7 | export class QuestionDetailsPageResolver implements Resolve { 8 | 9 | constructor( 10 | private appService: AppService 11 | ) { } 12 | 13 | resolve(route: ActivatedRouteSnapshot) { 14 | const questionId = route.paramMap.get('id'); 15 | const question = this.appService.getQuestionById(questionId); 16 | const answers = this.appService.getQuestionAnswers(questionId); 17 | 18 | return forkJoin({ question: question, answers: answers }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/questions/questions-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { QuestionDetailsPageComponent } from './question-details-page/question-details-page.component'; 4 | import { QuestionDetailsPageResolver } from './question-details-page/question-details-page.resolver'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: QuestionDetailsPageComponent, 10 | resolve: { 11 | data: QuestionDetailsPageResolver 12 | } 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | RouterModule.forChild(routes) 19 | ], 20 | exports: [RouterModule], 21 | providers: [QuestionDetailsPageResolver] 22 | }) 23 | export class QuestionsRoutingModule { } 24 | -------------------------------------------------------------------------------- /src/app/questions/questions-shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { AskQuestionModalComponent } from './ask-question-modal/ask-question-modal.component'; 4 | import { ReactiveFormsModule, FormsModule } from '@angular/forms'; 5 | import { IonicModule } from '@ionic/angular'; 6 | 7 | @NgModule({ 8 | declarations: [AskQuestionModalComponent], 9 | imports: [ 10 | CommonModule, 11 | IonicModule, 12 | FormsModule, 13 | ReactiveFormsModule 14 | ], 15 | exports: [AskQuestionModalComponent] 16 | }) 17 | export class QuestionsSharedModule { } 18 | -------------------------------------------------------------------------------- /src/app/questions/questions.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { QuestionsRoutingModule } from './questions-routing.module'; 5 | import { QuestionDetailsPageComponent } from './question-details-page/question-details-page.component'; 6 | import { AnswerQuestionModalComponent } from './answer-question-modal/answer-question-modal.component'; 7 | import { IonicModule } from '@ionic/angular'; 8 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 9 | 10 | 11 | @NgModule({ 12 | declarations: [QuestionDetailsPageComponent, AnswerQuestionModalComponent], 13 | imports: [ 14 | CommonModule, 15 | IonicModule, 16 | QuestionsRoutingModule, 17 | FormsModule, 18 | ReactiveFormsModule 19 | ] 20 | }) 21 | export class QuestionsModule { } 22 | -------------------------------------------------------------------------------- /src/assets/icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionicthemes/build-a-complete-mobile-app-with-ionic-framework/08abb98133c960207159b94fdbe430541186bc74/src/assets/icon/favicon.png -------------------------------------------------------------------------------- /src/assets/shapes.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | API_URL: 'https://q-a-nest-api.ionicthemes.com/' 4 | }; 5 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | API_URL: 'https://q-a-nest-api.ionicthemes.com/' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/global.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * App Global CSS 3 | * ---------------------------------------------------------------------------- 4 | * Put style rules here that you want to apply globally. These styles are for 5 | * the entire app and not just one component. Additionally, this file can be 6 | * used as an entry point to import other CSS/Sass files to be included in the 7 | * output CSS. 8 | * For more information on global stylesheets, visit the documentation: 9 | * https://ionicframework.com/docs/layout/global-stylesheets 10 | */ 11 | 12 | /* Core CSS required for Ionic components to work properly */ 13 | @import "~@ionic/angular/css/core.css"; 14 | 15 | /* Basic CSS for apps built with Ionic */ 16 | @import "~@ionic/angular/css/normalize.css"; 17 | @import "~@ionic/angular/css/structure.css"; 18 | @import "~@ionic/angular/css/typography.css"; 19 | @import '~@ionic/angular/css/display.css'; 20 | 21 | /* Optional CSS utils that can be commented out */ 22 | @import "~@ionic/angular/css/padding.css"; 23 | @import "~@ionic/angular/css/float-elements.css"; 24 | @import "~@ionic/angular/css/text-alignment.css"; 25 | @import "~@ionic/angular/css/text-transformation.css"; 26 | @import "~@ionic/angular/css/flex-utils.css"; 27 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ionic App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | import './zone-flags'; 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by default for Angular itself. 59 | */ 60 | 61 | import 'zone.js/dist/zone'; // Included with Angular CLI. 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/zone-flags.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prevents Angular change detection from 3 | * running with certain Web Component callbacks 4 | */ 5 | (window as any).__Zone_disable_customElements = true; 6 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.ts", 13 | "src/**/*.d.ts" 14 | ], 15 | "exclude": [ 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true 22 | } 23 | } -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-return-shorthand": true, 12 | "curly": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": [true, "Page", "Component"], 17 | "contextual-lifecycle": true, 18 | "directive-class-suffix": true, 19 | "directive-selector": [ 20 | true, 21 | "attribute", 22 | "app", 23 | "camelCase" 24 | ], 25 | "component-selector": [ 26 | true, 27 | "element", 28 | "app", 29 | "kebab-case" 30 | ], 31 | "eofline": true, 32 | "import-blacklist": [ 33 | true, 34 | "rxjs/Rx" 35 | ], 36 | "import-spacing": true, 37 | "indent": { 38 | "options": [ 39 | "spaces" 40 | ] 41 | }, 42 | "max-classes-per-file": false, 43 | "max-line-length": [ 44 | true, 45 | 140 46 | ], 47 | "member-ordering": [ 48 | true, 49 | { 50 | "order": [ 51 | "static-field", 52 | "instance-field", 53 | "static-method", 54 | "instance-method" 55 | ] 56 | } 57 | ], 58 | "no-console": [ 59 | true, 60 | "debug", 61 | "info", 62 | "time", 63 | "timeEnd", 64 | "trace" 65 | ], 66 | "no-empty": false, 67 | "no-inferrable-types": [ 68 | true, 69 | "ignore-params" 70 | ], 71 | "no-non-null-assertion": true, 72 | "no-redundant-jsdoc": true, 73 | "no-switch-case-fall-through": true, 74 | "no-var-requires": false, 75 | "object-literal-key-quotes": [ 76 | true, 77 | "as-needed" 78 | ], 79 | "quotemark": [ 80 | true, 81 | "single" 82 | ], 83 | "semicolon": { 84 | "options": [ 85 | "always" 86 | ] 87 | }, 88 | "space-before-function-paren": { 89 | "options": { 90 | "anonymous": "never", 91 | "asyncArrow": "always", 92 | "constructor": "never", 93 | "method": "never", 94 | "named": "never" 95 | } 96 | }, 97 | "typedef-whitespace": { 98 | "options": [ 99 | { 100 | "call-signature": "nospace", 101 | "index-signature": "nospace", 102 | "parameter": "nospace", 103 | "property-declaration": "nospace", 104 | "variable-declaration": "nospace" 105 | }, 106 | { 107 | "call-signature": "onespace", 108 | "index-signature": "onespace", 109 | "parameter": "onespace", 110 | "property-declaration": "onespace", 111 | "variable-declaration": "onespace" 112 | } 113 | ] 114 | }, 115 | "variable-name": { 116 | "options": [ 117 | "ban-keywords", 118 | "check-format", 119 | "allow-pascal-case" 120 | ] 121 | }, 122 | "whitespace": { 123 | "options": [ 124 | "check-branch", 125 | "check-decl", 126 | "check-operator", 127 | "check-separator", 128 | "check-type", 129 | "check-typecast" 130 | ] 131 | }, 132 | "no-conflicting-lifecycle": true, 133 | "no-host-metadata-property": true, 134 | "no-input-rename": true, 135 | "no-inputs-metadata-property": true, 136 | "no-output-native": true, 137 | "no-output-on-prefix": true, 138 | "no-output-rename": true, 139 | "no-outputs-metadata-property": true, 140 | "template-banana-in-box": true, 141 | "template-no-negated-async": true, 142 | "use-lifecycle-interface": true, 143 | "use-pipe-transform-interface": true, 144 | "object-literal-sort-keys": false 145 | }, 146 | "rulesDirectory": [ 147 | "codelyzer" 148 | ] 149 | } --------------------------------------------------------------------------------