├── .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 |
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 | 
9 | 
10 | 
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 |
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 | 0">
31 |
32 | Find all the Questions and Answers about
33 | {{ category.title }}
34 | from the community
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 | 0, 'no-score': (question.positiveVotes - question.negativeVotes) == 0, 'bad-score': (question.positiveVotes - question.negativeVotes) < 0}">{{ 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 | 0, 'no-score': (question.positiveVotes - question.negativeVotes) == 0, 'bad-score': (question.positiveVotes - question.negativeVotes) < 0}">{{ 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 | 0">
55 |
56 | We found
57 | {{ answers.length }}
58 | Answers for this question!
59 |
60 |
61 |
62 |
63 |
64 |
65 |
68 | 0, 'no-score': (answer.positiveVotes - answer.negativeVotes) == 0, 'bad-score': (answer.positiveVotes - answer.negativeVotes) < 0}">{{ 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 |
0">
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 | 0, 'bad-score': (question.positiveVotes - question.negativeVotes) < 0}">
40 | {{ question.positiveVotes + question.negativeVotes }}
41 |
42 |
43 |
44 | Answers
45 | 0}">
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 |
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 |
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 | 0, 'bad-score': (question.positiveVotes - question.negativeVotes) < 0}">
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 | 0, 'bad-score': (answer.positiveVotes - answer.negativeVotes) < 0}">
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 | }
--------------------------------------------------------------------------------