├── .gitignore
├── assets
├── screenshot.png
├── firebase_init_step_1.png
└── firebase_init_step_2.png
├── .stylelintrc.json
├── firestore.indexes.json
├── public
├── src
│ ├── css
│ │ └── styles.css
│ ├── models
│ │ ├── user.js
│ │ ├── project.js
│ │ └── todo.js
│ ├── modules
│ │ ├── database.js
│ │ └── doman.js
│ └── index.js
├── 404.html
└── dist
│ ├── 404.html
│ ├── index.html
│ └── main.js
├── .eslintrc.json
├── webpack.config.js
├── firestore.rules
├── LICENSE
├── .github
└── workflows
│ └── linters.yml
├── package.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | firebase-debug.log
3 | .firebaserc
4 | firebase.json
--------------------------------------------------------------------------------
/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdelp/todo-list-js/HEAD/assets/screenshot.png
--------------------------------------------------------------------------------
/assets/firebase_init_step_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdelp/todo-list-js/HEAD/assets/firebase_init_step_1.png
--------------------------------------------------------------------------------
/assets/firebase_init_step_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abdelp/todo-list-js/HEAD/assets/firebase_init_step_2.png
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard"],
3 | "plugins": ["stylelint-scss"],
4 | "rules": {
5 | "at-rule-no-unknown": null,
6 | "scss/at-rule-no-unknown": true
7 | },
8 | "ignoreFiles": [
9 | "build/**",
10 | "dist/**",
11 | "**/reset*.css",
12 | "**/bootstrap*.css"
13 | ]
14 | }
--------------------------------------------------------------------------------
/firestore.indexes.json:
--------------------------------------------------------------------------------
1 | {
2 | "indexes": [
3 | {
4 | "collectionGroup": "projects",
5 | "queryScope": "COLLECTION",
6 | "fields": [
7 | {
8 | "fieldPath": "userId",
9 | "order": "ASCENDING"
10 | },
11 | {
12 | "fieldPath": "createdAt",
13 | "order": "DESCENDING"
14 | }
15 | ]
16 | }
17 | ],
18 | "fieldOverrides": []
19 | }
20 |
--------------------------------------------------------------------------------
/public/src/css/styles.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: 'Comfortaa', cursive;
3 | }
4 |
5 | .heading {
6 | font-family: 'Ranchers', cursive;
7 | font-size: 50px;
8 | }
9 |
10 | body {
11 | background-image: url("https://thumbs.dreamstime.com/b/stationery-background-school-tools-seamless-pattern-art-education-wallpaper-line-icons-pencil-pen-paintbrush-palette-169146367.jpg");
12 | height: 100%;
13 | background-position: center;
14 | }
15 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true
6 | },
7 | "parserOptions": {
8 | "ecmaVersion": 2018,
9 | "sourceType": "module"
10 | },
11 | "extends": ["airbnb-base"],
12 | "rules": {
13 | "no-shadow": "off",
14 | "no-param-reassign": "off",
15 | "eol-last": "off",
16 | "arrow-parens": "off"
17 | },
18 | "ignorePatterns": [
19 | "dist/",
20 | "build/"
21 | ]
22 | }
--------------------------------------------------------------------------------
/public/src/models/user.js:
--------------------------------------------------------------------------------
1 | import * as Database from '../modules/database';
2 |
3 | const params = ({ userName, createdAt }) => ({ userName, createdAt });
4 |
5 | const create = async (data) => {
6 | const collection = 'users';
7 | let result;
8 | data.createdAt = Database.currentTimestamp();
9 |
10 | try {
11 | result = await Database.add(collection, params(data));
12 | localStorage.setItem('userId', result.id);
13 | } catch (error) {
14 | result = await error;
15 | }
16 |
17 | return result;
18 | };
19 |
20 | export default create;
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: './public/src/index.js',
6 | output: {
7 | filename: 'main.js',
8 | path: path.resolve(__dirname, 'public/dist'),
9 | },
10 | module: {
11 | rules: [{
12 | test: /\.css$/,
13 | use: [
14 | 'style-loader',
15 | 'css-loader',
16 | ],
17 | },
18 | {
19 | test: /\.(png|svg|jpg|gif)$/,
20 | use: [
21 | 'file-loader',
22 | ],
23 | },
24 | {
25 | test: /\.(woff|woff2|eot|ttf|otf)$/,
26 | use: [
27 | 'file-loader',
28 | ],
29 | },
30 | ],
31 | },
32 | };
--------------------------------------------------------------------------------
/firestore.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 | service cloud.firestore {
3 | match /databases/{database}/documents {
4 |
5 | // This rule allows anyone with your database reference to view, edit,
6 | // and delete all data in your Firestore database. It is useful for getting
7 | // started, but it is configured to expire after 30 days because it
8 | // leaves your app open to attackers. At that time, all client
9 | // requests to your Firestore database will be denied.
10 | //
11 | // Make sure to write security rules for your app before that time, or else
12 | // all client requests to your Firestore database will be denied until you Update
13 | // your rules
14 | match /{document=**} {
15 | allow read, write: if request.time < timestamp.date(2020, 8, 19);
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/public/src/models/project.js:
--------------------------------------------------------------------------------
1 | import * as Database from '../modules/database';
2 |
3 | const params = ({
4 | title, description, userId, createdAt,
5 | }) => ({
6 | title, description, userId, createdAt,
7 | });
8 |
9 | const create = async (data) => {
10 | const collection = 'projects';
11 | let result;
12 | data.createdAt = Database.currentTimestamp();
13 |
14 | try {
15 | result = await Database.add(collection, params(data));
16 | } catch (error) {
17 | result = await error;
18 | }
19 |
20 | return result;
21 | };
22 |
23 | const allProjects = async (userId) => {
24 | const collection = 'projects';
25 | const projects = await Database.getCollection(collection, { params: [{ key: 'userId', sign: '==', value: userId }], orderBy: { field: 'createdAt', order: 'desc' } });
26 | return projects;
27 | };
28 |
29 | export { create, allProjects };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Abdel Omar Pérez Téllez
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 |
--------------------------------------------------------------------------------
/.github/workflows/linters.yml:
--------------------------------------------------------------------------------
1 | name: Linters
2 |
3 | on: pull_request
4 |
5 | env:
6 | FORCE_COLOR: 1
7 |
8 | jobs:
9 | eslint:
10 | name: ESLint
11 | runs-on: ubuntu-18.04
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v1
15 | with:
16 | node-version: "12.x"
17 | - name: Setup ESLint
18 | run: |
19 | npm install --save-dev eslint@6.8.x eslint-config-airbnb-base@14.1.x eslint-plugin-import@2.20.x
20 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/javascript/.eslintrc.json
21 | - name: ESLint Report
22 | run: npx eslint .
23 | stylelint:
24 | name: Stylelint
25 | runs-on: ubuntu-18.04
26 | steps:
27 | - uses: actions/checkout@v2
28 | - uses: actions/setup-node@v1
29 | with:
30 | node-version: "12.x"
31 | - name: Setup Stylelint
32 | run: |
33 | npm install --save-dev stylelint@13.3.x stylelint-scss@3.17.x stylelint-config-standard@20.0.x
34 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/javascript/.stylelintrc.json
35 | - name: Stylelint Report
36 | run: npx stylelint "**/*.{css,scss}"
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-list",
3 | "version": "1.0.0",
4 | "description": "A TO-DO application made with javascript",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack",
9 | "watch": "webpack --watch",
10 | "server": "webpack-dev-server --open"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/abdelp/todo-list-js.git"
15 | },
16 | "keywords": [
17 | "javascript"
18 | ],
19 | "author": "Elbie Moonga & Abdel Pérez",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/abdelp/todo-list-js/issues"
23 | },
24 | "homepage": "https://github.com/abdelp/todo-list-js#readme",
25 | "devDependencies": {
26 | "autoprefixer": "^9.8.5",
27 | "css-loader": "^3.6.0",
28 | "eslint": "^6.8.0",
29 | "eslint-config-airbnb-base": "^14.1.0",
30 | "eslint-plugin-import": "^2.20.2",
31 | "exports-loader": "^1.1.0",
32 | "node-sass": "^4.14.1",
33 | "postcss-loader": "^3.0.0",
34 | "sass-loader": "^9.0.2",
35 | "style-loader": "^1.2.1",
36 | "stylelint": "^13.3.3",
37 | "stylelint-config-standard": "^20.0.0",
38 | "stylelint-csstree-validator": "^1.8.0",
39 | "stylelint-scss": "^3.17.2",
40 | "webpack": "^4.43.0",
41 | "webpack-cli": "^3.3.12",
42 | "webpack-dev-server": "^3.11.0"
43 | },
44 | "dependencies": {
45 | "pubsub-js": "^1.8.0"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/src/models/todo.js:
--------------------------------------------------------------------------------
1 | import * as Database from '../modules/database';
2 |
3 | const params = ({
4 | title, description, dueDate, priority,
5 | }) => ({
6 | title, description, dueDate, priority,
7 | });
8 |
9 | const create = async (projectId, data) => {
10 | const collection = `projects/${projectId}/todos`;
11 | let result;
12 | try {
13 | result = await Database.add(collection, params(data));
14 | } catch (error) {
15 | result = await error;
16 | }
17 |
18 | return result;
19 | };
20 |
21 | const update = async (projectId, data) => {
22 | const collection = `projects/${projectId}/todos`;
23 | const { id: doc } = data;
24 | let result;
25 | try {
26 | result = await Database.edit(collection, doc, params(data));
27 | } catch (error) {
28 | result = await error;
29 | }
30 |
31 | return result;
32 | };
33 |
34 | const deleteTodo = async (projectId, docId) => {
35 | const collection = `projects/${projectId}/todos`;
36 | let result;
37 |
38 | try {
39 | result = await Database.deleteDoc(collection, docId);
40 | } catch (error) {
41 | result = await error;
42 | }
43 |
44 | return result;
45 | };
46 |
47 | const allTodos = async (projectId) => {
48 | const collection = `projects/${projectId}/todos`;
49 | const todos = await Database.getCollection(collection);
50 | return todos;
51 | };
52 |
53 | const where = async (projectId, conditions) => {
54 | const collection = `projects/${projectId}/todos`;
55 | let result;
56 |
57 | try {
58 | result = await Database.getCollection(collection, conditions);
59 | } catch (error) {
60 | result = await error;
61 | }
62 |
63 | return result;
64 | };
65 |
66 | export {
67 | create, update, allTodos, deleteTodo, where,
68 | };
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Page Not Found
7 |
8 |
23 |
24 |
25 |
26 |
404
27 |
Page Not Found
28 |
The specified file was not found on this website. Please check the URL for mistakes and try again.
29 |
Why am I seeing this?
30 |
This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/public/dist/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Page Not Found
7 |
8 |
23 |
24 |
25 |
26 |
404
27 |
Page Not Found
28 |
The specified file was not found on this website. Please check the URL for mistakes and try again.
29 |
Why am I seeing this?
30 |
This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Todo List Application
2 |
3 | > This project consists of building a todo list application with Javascript and firebase hosting.
4 |
5 | 
6 |
7 | ## Built With
8 |
9 | - Javascript
10 | - Firebase
11 | - Webpack
12 | - HTML5
13 | - CSS
14 | - Bootstrap 4.5
15 | - ESLint
16 | - Stylelint
17 |
18 | ## Usage
19 |
20 | - To start the application follow the link given in the live demo.
21 | - A default project is assigned to you via a auto generated username.
22 | - Add new projects.
23 | - Add new todos within those projects you create
24 |
25 | ## Live Demo
26 |
27 | [Live Demo Link](https://todo-list-41950.web.app/)
28 |
29 |
30 | ## Requirements
31 |
32 | - Compatible Web browser (Chrome, Mozilla, IE, Safari)
33 |
34 | ## Installation
35 |
36 | ### Prerequisite
37 |
38 | - Create a firebase [account](https://console.firebase.google.com/)
39 | - Install [firebase CLI](https://firebase.google.com/docs/cli)
40 | - Install [Node](https://nodejs.org/en/)
41 |
42 | ### Steps
43 |
44 | From the command line/terminal clone the repository:
45 |
46 | ```
47 | $ git clone https://github.com/abdelp/todo-list-js.git
48 | ```
49 |
50 | Initialize the firebase hosting service
51 |
52 | ```
53 | $ firebase init
54 | ```
55 |
56 | Select firestore and hosting features:
57 |
58 | 
59 |
60 | Select public/dist as the public directory:
61 |
62 | 
63 |
64 | ## Deploy
65 |
66 | To deploy on your local environment run:
67 |
68 | ```
69 | $ firebase serve
70 | ```
71 |
72 | To deploy to your firebase production environment:
73 |
74 | ```
75 | $ firebase deploy
76 | ```
77 |
78 | ## Authors
79 |
80 | 👤 **Abdel Pérez**
81 |
82 | - Github: [@abdelp](https://github.com/abdelp/)
83 | - Twitter: [@AbdelPerez11](https://twitter.com/abdelperez11)
84 | - Linkedin: [Abdel Pérez](https://www.linkedin.com/in/abdel-perez/)
85 |
86 |
87 | 👤 **Elbie Moonga**
88 | - GitHub: [@Elbie-Em](https://github.com/Elbie-em)
89 | - Twitter: [ElbieEm](https://twitter.com/ElbieEm)
90 | - LinkedIn: [elbie-moonga](https://www.linkedin.com/in/elbiemoonga/)
91 |
92 | ## 🤝 Contributing
93 |
94 | Contributions, issues and feature requests are welcome!
95 |
96 | Feel free to check the [issues page](https://github.com/abdelp/todo-list-js/issues).
97 |
98 | ## Show your support
99 |
100 | Give a ⭐️ if you like this project!
101 |
102 | ## 📝 License
103 |
104 | This project is [MIT](lic.url) licensed.
--------------------------------------------------------------------------------
/public/src/modules/database.js:
--------------------------------------------------------------------------------
1 | const firestore = firebase.firestore(); // eslint-disable-line no-undef
2 |
3 | const add = async (collection, data) => {
4 | const collRef = firestore.collection(collection);
5 | let result;
6 |
7 | try {
8 | result = await collRef.add(data);
9 | } catch (error) {
10 | result = await error;
11 | }
12 |
13 | return result;
14 | };
15 |
16 | const edit = async (collection, doc, data) => {
17 | const docRef = firestore.collection(collection).doc(doc);
18 | let result;
19 |
20 | try {
21 | result = await docRef.update(data);
22 | } catch (error) {
23 | result = await error;
24 | }
25 |
26 | return result;
27 | };
28 |
29 | const deleteDoc = async (collection, doc) => {
30 | const docRef = firestore.collection(collection).doc(doc);
31 | let result;
32 |
33 | try {
34 | result = await docRef.delete();
35 | } catch (error) {
36 | result = await error;
37 | }
38 |
39 | return result;
40 | };
41 |
42 | const setCurrentProject = (projectId) => {
43 | localStorage.setItem('currentProject', projectId);
44 | };
45 |
46 | const getCurrentProject = () => localStorage.getItem('currentProject');
47 |
48 | const getDoc = async (collection, queryProps = {}) => {
49 | const {
50 | params,
51 | orderBy,
52 | doc,
53 | } = queryProps;
54 | let collectionRef = firestore.collection(collection);
55 |
56 | if (params) {
57 | params.forEach(param => {
58 | collectionRef = collectionRef.where(param.key, param.sign, param.value);
59 | });
60 | }
61 | if (orderBy) {
62 | collectionRef = collectionRef.orderBy(orderBy.field, orderBy.order);
63 | }
64 | if (doc) {
65 | collectionRef = collectionRef.doc(doc);
66 | }
67 |
68 | let result;
69 |
70 | try {
71 | const rDoc = await collectionRef.get();
72 | result = {
73 | id: rDoc.id,
74 | ...rDoc.data(),
75 | };
76 | } catch (error) {
77 | result = await error;
78 | }
79 |
80 | return result;
81 | };
82 |
83 | const getCollection = async (collection, queryProps = {}) => {
84 | const {
85 | params,
86 | orderBy,
87 | } = queryProps;
88 | let collectionRef = firestore.collection(collection);
89 |
90 | if (params) {
91 | params.forEach(param => {
92 | collectionRef = collectionRef.where(param.key, param.sign, param.value);
93 | });
94 | }
95 | if (orderBy) {
96 | collectionRef = collectionRef.orderBy(orderBy.field, orderBy.order);
97 | }
98 |
99 | let result = [];
100 |
101 | try {
102 | const docs = await collectionRef.get();
103 |
104 | docs.forEach(doc => {
105 | result.push({
106 | id: doc.id,
107 | ...doc.data(),
108 | });
109 | });
110 | } catch (error) {
111 | result = await error;
112 | }
113 |
114 | return result;
115 | };
116 |
117 | const getDefaultProject = async (userId) => {
118 | let result;
119 |
120 | try {
121 | const params = [{
122 | key: 'title',
123 | sign: '==',
124 | value: 'default',
125 | },
126 | {
127 | key: 'userId',
128 | sign: '==',
129 | value: userId,
130 | },
131 | ];
132 | result = await getDoc('projects', params);
133 | } catch (error) {
134 | result = await error;
135 | }
136 |
137 | return result[0];
138 | };
139 |
140 | const getUserId = () => localStorage.getItem('userId');
141 |
142 | const currentTimestamp = () => {
143 | firebase.firestore.FieldValue.serverTimestamp(); // eslint-disable-line no-undef
144 | };
145 |
146 | export {
147 | add,
148 | edit,
149 | getDoc,
150 | deleteDoc,
151 | getCollection,
152 | getUserId,
153 | setCurrentProject,
154 | getDefaultProject,
155 | currentTimestamp,
156 | getCurrentProject,
157 | };
--------------------------------------------------------------------------------
/public/src/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jquery */
2 | import PubSub from 'pubsub-js';
3 | import * as Doman from './modules/doman';
4 | import * as Database from './modules/database';
5 | import * as Todo from './models/todo';
6 | import * as Project from './models/project';
7 | import * as User from './models/user';
8 | import './css/styles.css';
9 |
10 | const addProject = () => {
11 | const formId = 'project-form';
12 | const data = Doman.getFormValues(formId);
13 | const userId = Database.getUserId();
14 | data.userId = userId;
15 | Project.create(data);
16 | Doman.cleanForm(formId);
17 | Doman.hideModal('project-modal');
18 | PubSub.publish('LOAD PROJECTS');
19 | };
20 |
21 | Doman.assignBtn('add-project', addProject);
22 |
23 | $('#todo-modal').on('hidden.bs.modal', () => {
24 | Doman.cleanForm('todo-form');
25 | });
26 |
27 | const getCurrentDate = () => {
28 | const date = new Date();
29 | const dateTimeFormat = new Intl.DateTimeFormat('en', {
30 | year: 'numeric',
31 | month: '2-digit',
32 | day: '2-digit',
33 | });
34 | const [{
35 | value: month,
36 | }, , {
37 | value: day,
38 | }, , {
39 | value: year,
40 | }] = dateTimeFormat.formatToParts(date);
41 | const currentDate = `${year}-${month}-${day}`;
42 | return currentDate;
43 | };
44 |
45 | const loadTodos = async (msg, condition, projectId) => {
46 | let sign;
47 | const currentDate = getCurrentDate();
48 | let container;
49 |
50 | if (condition === 'today') {
51 | sign = '==';
52 | container = 'today-todo-list';
53 | } else if (condition === 'upcoming') {
54 | sign = '>';
55 | container = 'upcoming-todo-list';
56 | } else if (condition === 'completed') {
57 | sign = '<';
58 | container = 'completed-todo-list';
59 | }
60 |
61 | const conditions = {
62 | params: [{
63 | key: 'dueDate',
64 | sign,
65 | value: currentDate,
66 | }],
67 | };
68 |
69 | const todos = await Todo.where(projectId, conditions);
70 | const todoCollapses = [];
71 |
72 | todos.forEach(todo => {
73 | const deleteHandler = () => {
74 | Todo.deleteTodo(projectId, todo.id);
75 | PubSub.publish('LOAD TODOS', projectId);
76 | Doman.hideModal('confirm-modal');
77 | };
78 |
79 | const data = {
80 | id: todo.id,
81 | innerText: todo.title,
82 | description: todo.description,
83 | dueDate: todo.dueDate,
84 | priority: todo.priority,
85 | deleteButton: {
86 | onclick: () => Doman.showConfirmModal(deleteHandler),
87 | },
88 | };
89 |
90 | const todoCollapse = Doman.createCollapse(data);
91 | todoCollapses.push(todoCollapse);
92 | });
93 |
94 | const todoList = Doman.createList(todoCollapses);
95 |
96 | Doman.cleanElement(container);
97 | Doman.addChild(container, todoList);
98 | };
99 |
100 | const addTodo = () => {
101 | const data = Doman.getFormValues('todo-form');
102 | const currentProject = Database.getCurrentProject();
103 | if (!data.id) {
104 | Todo.create(currentProject, data)
105 | .then(() => {
106 | Doman.cleanForm('todo-form');
107 | Doman.hideModal('todo-modal');
108 | PubSub.publish('LOAD TODOS', 'today', currentProject);
109 | });
110 | } else {
111 | Todo.update(currentProject, data)
112 | .then(() => {
113 | Doman.cleanForm('todo-form');
114 | Doman.hideModal('todo-modal');
115 | PubSub.publish('LOAD TODOS', 'today', currentProject);
116 | });
117 | }
118 | };
119 |
120 | Doman.assignBtn('add-todo', addTodo);
121 |
122 | const loadProjects = () => {
123 | const userId = Database.getUserId();
124 | Doman.cleanElement('projects-list');
125 | Project.allProjects(userId)
126 | .then(result => {
127 | const onclickHandler = () => {
128 | Database.setCurrentProject(this.id);
129 | Doman.setTitle(this.innerHTML);
130 | loadTodos('', 'today', this.id);
131 | };
132 |
133 | const projectsButtons = result.map(item => Doman.createButton({
134 | id: item.id,
135 | innerText: item.title,
136 | color: 'info',
137 | onclick: onclickHandler,
138 | }));
139 |
140 | const list = Doman.createList(projectsButtons);
141 | Doman.addChild('projects-list', list);
142 | });
143 | };
144 |
145 | const userId = Database.getUserId();
146 |
147 | if (!userId) {
148 | User.create({
149 | userName: 'test',
150 | })
151 | .then(user => {
152 | const data = {
153 | title: 'Default',
154 | description: 'This is the default project for your application',
155 | userId: user.id,
156 | };
157 | Project.create(data)
158 | .then(project => {
159 | Database.setCurrentProject(project.id);
160 | Doman.setTitle(data.title);
161 | loadProjects();
162 | });
163 | });
164 | } else {
165 | Database.getDoc('projects', {
166 | doc: Database.getCurrentProject(),
167 | })
168 | .then(doc => {
169 | Doman.setTitle(doc.title);
170 | loadTodos('', 'today', doc.id);
171 | });
172 | }
173 |
174 | PubSub.subscribe('LOAD PROJECTS', loadProjects);
175 | PubSub.subscribe('LOAD TODOS', loadTodos);
176 |
177 | loadProjects();
178 |
179 | Doman.assignBtn('completed-todos-btn', () => loadTodos('', 'completed', Database.getCurrentProject()));
180 | Doman.assignBtn('upcoming-todos-btn', () => loadTodos('', 'upcoming', Database.getCurrentProject()));
181 | Doman.assignBtn('today-todos-btn', () => loadTodos('', 'today', Database.getCurrentProject()));
--------------------------------------------------------------------------------
/public/src/modules/doman.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jquery */
2 | const getFormValues = formId => {
3 | const {
4 | elements,
5 | } = document.getElementById(formId);
6 | const obj = {};
7 | for (let i = 0; i < elements.length; i += 1) {
8 | const item = elements.item(i);
9 | obj[item.name] = item.value;
10 | }
11 |
12 | return obj;
13 | };
14 |
15 | const cleanForm = formId => {
16 | const form = document.getElementById(formId);
17 | form.reset();
18 | };
19 |
20 | const hideModal = modalId => {
21 | $(`#${modalId}`).modal('hide');
22 | };
23 |
24 | const createList = (list) => {
25 | const ul = document.createElement('ul');
26 | ul.className = 'list-group mt-3';
27 | list.forEach(item => {
28 | const li = document.createElement('li');
29 | li.className = 'list-group-item border-0';
30 | li.id = item.id;
31 | li.appendChild(item);
32 | ul.appendChild(li);
33 | });
34 |
35 | return ul;
36 | };
37 |
38 | const setTitle = (title) => {
39 | const elem = document.getElementById('project-name');
40 | elem.innerHTML = title;
41 | };
42 |
43 | const cleanElement = (containerId) => {
44 | const container = document.getElementById(containerId);
45 | container.innerHTML = '';
46 | };
47 |
48 | const addChild = (containerId, element) => {
49 | const container = document.getElementById(containerId);
50 | container.appendChild(element);
51 | };
52 |
53 | const createButton = (params) => {
54 | const {
55 | id,
56 | color = 'primary',
57 | innerText,
58 | onclick,
59 | } = params;
60 |
61 | const btn = document.createElement('button');
62 | btn.id = id;
63 | btn.className = `btn btn-${color} w-100 mb-1`;
64 | btn.innerText = innerText;
65 | btn.onclick = onclick;
66 | btn.type = 'button';
67 | return btn;
68 | };
69 |
70 | const createBadge = (priority) => {
71 | const badge = document.createElement('span');
72 | if (priority === '1') {
73 | badge.className = 'badge badge-danger h-75';
74 | badge.innerText = 'High';
75 | } else if (priority === '2') {
76 | badge.className = 'badge badge-success h-75';
77 | badge.innerHTML = 'Medium';
78 | } else if (priority === '3') {
79 | badge.className = 'badge badge-warning h-75';
80 | badge.innerHTML = 'Low';
81 | }
82 |
83 | return badge;
84 | };
85 |
86 | const select = (selectId, optionValToSelect) => {
87 | const selectElement = document.getElementById(selectId);
88 | const selectOptions = selectElement.options;
89 | let cont = true;
90 | let j = 0;
91 |
92 | while (cont) {
93 | const opt = selectOptions[j];
94 | if (opt.value === optionValToSelect) {
95 | selectElement.selectedIndex = j;
96 | cont = false;
97 | }
98 | j += 1;
99 | }
100 | };
101 |
102 | const openEditModal = (modalId, todo) => {
103 | const form = document.getElementById('todo-modal').querySelector('form');
104 | const hiddenInput = form.querySelector('#id');
105 | hiddenInput.setAttribute('value', todo.id);
106 | form.querySelector('#title').value = todo.innerText;
107 | form.querySelector('#description').value = todo.description;
108 | form.querySelector('#dueDate').value = todo.dueDate;
109 | select('priority', todo.priority);
110 | $('#todo-modal').modal('show');
111 | };
112 |
113 | const createCollapse = (element) => {
114 | const container = document.createElement('div');
115 | const collapseBtn = document.createElement('button');
116 | collapseBtn.className = 'btn btn-secondary w-100 mt-2';
117 | collapseBtn.type = 'button';
118 | collapseBtn.setAttribute('data-toggle', 'collapse');
119 | const collapseId = `t-${element.id}`;
120 | collapseBtn.setAttribute('data-target', `#${collapseId}`);
121 | collapseBtn.setAttribute('aria-expanded', 'false');
122 | collapseBtn.setAttribute('aria-controls', collapseId);
123 | collapseBtn.innerText = element.innerText;
124 |
125 | const collapse = document.createElement('div');
126 | collapse.id = collapseId;
127 | collapse.className = 'collapse';
128 | const collapseBody = document.createElement('div');
129 | collapseBody.className = 'card card-body';
130 |
131 | const todoTop = document.createElement('div');
132 | todoTop.className = 'd-flex flex-row justify-content-between';
133 |
134 | const todoDate = document.createElement('h6');
135 | todoDate.innerHTML = `Date: ${element.dueDate}`;
136 |
137 | const todoPriority = createBadge(element.priority);
138 |
139 | todoTop.appendChild(todoDate);
140 | todoTop.appendChild(todoPriority);
141 |
142 | const todoBody = document.createElement('div');
143 | const todoDescription = document.createElement('p');
144 | todoDescription.innerHTML = `Details
${element.description}`;
145 |
146 | todoBody.appendChild(todoDescription);
147 |
148 | collapseBody.appendChild(todoTop);
149 | collapseBody.appendChild(todoBody);
150 |
151 | collapse.appendChild(collapseBody);
152 |
153 | const todoBottom = document.createElement('div');
154 | todoBottom.className = 'text-right';
155 |
156 | const editBtn = createButton({
157 | id: `edit-btn-${collapseId}`,
158 | color: 'success',
159 | onclick: () => openEditModal('todo-modal', element),
160 | innerText: 'Edit',
161 | });
162 | todoBottom.appendChild(editBtn);
163 |
164 | const deleteBtn = createButton({
165 | id: `dlt-btn-${collapseId}`,
166 | color: 'danger',
167 | onclick: element.deleteButton.onclick,
168 | innerText: 'Delete',
169 | });
170 | todoBottom.appendChild(deleteBtn);
171 |
172 | todoBody.appendChild(todoBottom);
173 |
174 | container.appendChild(collapseBtn);
175 | container.appendChild(collapse);
176 |
177 | return container;
178 | };
179 |
180 | const showConfirmModal = (deleteHandler) => {
181 | const deleteBtn = document.getElementById('confirm-btn');
182 | deleteBtn.onclick = deleteHandler;
183 | $('#confirm-modal').modal('show');
184 | };
185 |
186 | const assignBtn = (btnId, onclickHandler) => {
187 | const btnElement = document.getElementById(btnId);
188 | btnElement.onclick = onclickHandler;
189 | };
190 |
191 | export {
192 | getFormValues,
193 | cleanForm,
194 | hideModal,
195 | createList,
196 | addChild,
197 | cleanElement,
198 | setTitle,
199 | createButton,
200 | createCollapse,
201 | showConfirmModal,
202 | assignBtn,
203 | };
--------------------------------------------------------------------------------
/public/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | todo.app
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
TODO APP
23 |
24 |
25 |
26 |
32 |
33 |
Are you sure you want to delete this TO-DO item?
34 |
35 |
39 |
40 |
41 |
42 |
82 |
110 |
111 |
112 |
113 |
116 |
117 |
118 |
119 |
120 |
121 |
Project:
122 |
125 |
126 |
129 |
136 |
139 |
146 |
149 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/public/dist/main.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
40 | /******/ }
41 | /******/ };
42 | /******/
43 | /******/ // define __esModule on exports
44 | /******/ __webpack_require__.r = function(exports) {
45 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
46 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
47 | /******/ }
48 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
49 | /******/ };
50 | /******/
51 | /******/ // create a fake namespace object
52 | /******/ // mode & 1: value is a module id, require it
53 | /******/ // mode & 2: merge all properties of value into the ns
54 | /******/ // mode & 4: return value when already ns object
55 | /******/ // mode & 8|1: behave like require
56 | /******/ __webpack_require__.t = function(value, mode) {
57 | /******/ if(mode & 1) value = __webpack_require__(value);
58 | /******/ if(mode & 8) return value;
59 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
60 | /******/ var ns = Object.create(null);
61 | /******/ __webpack_require__.r(ns);
62 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
63 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
64 | /******/ return ns;
65 | /******/ };
66 | /******/
67 | /******/ // getDefaultExport function for compatibility with non-harmony modules
68 | /******/ __webpack_require__.n = function(module) {
69 | /******/ var getter = module && module.__esModule ?
70 | /******/ function getDefault() { return module['default']; } :
71 | /******/ function getModuleExports() { return module; };
72 | /******/ __webpack_require__.d(getter, 'a', getter);
73 | /******/ return getter;
74 | /******/ };
75 | /******/
76 | /******/ // Object.prototype.hasOwnProperty.call
77 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
78 | /******/
79 | /******/ // __webpack_public_path__
80 | /******/ __webpack_require__.p = "";
81 | /******/
82 | /******/
83 | /******/ // Load entry module and return exports
84 | /******/ return __webpack_require__(__webpack_require__.s = "./public/src/index.js");
85 | /******/ })
86 | /************************************************************************/
87 | /******/ ({
88 |
89 | /***/ "./node_modules/css-loader/dist/cjs.js!./public/src/css/styles.css":
90 | /*!*************************************************************************!*\
91 | !*** ./node_modules/css-loader/dist/cjs.js!./public/src/css/styles.css ***!
92 | \*************************************************************************/
93 | /*! no static exports found */
94 | /***/ (function(module, exports, __webpack_require__) {
95 |
96 | eval("// Imports\nvar ___CSS_LOADER_API_IMPORT___ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\nexports = ___CSS_LOADER_API_IMPORT___(false);\n// Module\nexports.push([module.i, \"* {\\r\\n font-family: 'Comfortaa', cursive;\\r\\n}\\r\\n\\r\\n.heading {\\r\\n font-family: 'Ranchers', cursive;\\r\\n font-size: 50px;\\r\\n}\\r\\n\\r\\nbody {\\r\\n background-image: url(\\\"https://thumbs.dreamstime.com/b/stationery-background-school-tools-seamless-pattern-art-education-wallpaper-line-icons-pencil-pen-paintbrush-palette-169146367.jpg\\\");\\r\\n height: 100%;\\r\\n background-position: center;\\r\\n}\\r\\n\", \"\"]);\n// Exports\nmodule.exports = exports;\n\n\n//# sourceURL=webpack:///./public/src/css/styles.css?./node_modules/css-loader/dist/cjs.js");
97 |
98 | /***/ }),
99 |
100 | /***/ "./node_modules/css-loader/dist/runtime/api.js":
101 | /*!*****************************************************!*\
102 | !*** ./node_modules/css-loader/dist/runtime/api.js ***!
103 | \*****************************************************/
104 | /*! no static exports found */
105 | /***/ (function(module, exports, __webpack_require__) {
106 |
107 | "use strict";
108 | eval("\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\n// css base code, injected by the css-loader\n// eslint-disable-next-line func-names\nmodule.exports = function (useSourceMap) {\n var list = []; // return the list of modules as css string\n\n list.toString = function toString() {\n return this.map(function (item) {\n var content = cssWithMappingToString(item, useSourceMap);\n\n if (item[2]) {\n return \"@media \".concat(item[2], \" {\").concat(content, \"}\");\n }\n\n return content;\n }).join('');\n }; // import a list of modules into the list\n // eslint-disable-next-line func-names\n\n\n list.i = function (modules, mediaQuery, dedupe) {\n if (typeof modules === 'string') {\n // eslint-disable-next-line no-param-reassign\n modules = [[null, modules, '']];\n }\n\n var alreadyImportedModules = {};\n\n if (dedupe) {\n for (var i = 0; i < this.length; i++) {\n // eslint-disable-next-line prefer-destructuring\n var id = this[i][0];\n\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n\n for (var _i = 0; _i < modules.length; _i++) {\n var item = [].concat(modules[_i]);\n\n if (dedupe && alreadyImportedModules[item[0]]) {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (mediaQuery) {\n if (!item[2]) {\n item[2] = mediaQuery;\n } else {\n item[2] = \"\".concat(mediaQuery, \" and \").concat(item[2]);\n }\n }\n\n list.push(item);\n }\n };\n\n return list;\n};\n\nfunction cssWithMappingToString(item, useSourceMap) {\n var content = item[1] || ''; // eslint-disable-next-line prefer-destructuring\n\n var cssMapping = item[3];\n\n if (!cssMapping) {\n return content;\n }\n\n if (useSourceMap && typeof btoa === 'function') {\n var sourceMapping = toComment(cssMapping);\n var sourceURLs = cssMapping.sources.map(function (source) {\n return \"/*# sourceURL=\".concat(cssMapping.sourceRoot || '').concat(source, \" */\");\n });\n return [content].concat(sourceURLs).concat([sourceMapping]).join('\\n');\n }\n\n return [content].join('\\n');\n} // Adapted from convert-source-map (MIT)\n\n\nfunction toComment(sourceMap) {\n // eslint-disable-next-line no-undef\n var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));\n var data = \"sourceMappingURL=data:application/json;charset=utf-8;base64,\".concat(base64);\n return \"/*# \".concat(data, \" */\");\n}\n\n//# sourceURL=webpack:///./node_modules/css-loader/dist/runtime/api.js?");
109 |
110 | /***/ }),
111 |
112 | /***/ "./node_modules/pubsub-js/src/pubsub.js":
113 | /*!**********************************************!*\
114 | !*** ./node_modules/pubsub-js/src/pubsub.js ***!
115 | \**********************************************/
116 | /*! no static exports found */
117 | /***/ (function(module, exports, __webpack_require__) {
118 |
119 | eval("/* WEBPACK VAR INJECTION */(function(module) {/**\n * Copyright (c) 2010,2011,2012,2013,2014 Morgan Roderick http://roderick.dk\n * License: MIT - http://mrgnrdrck.mit-license.org\n *\n * https://github.com/mroderick/PubSubJS\n */\n\n(function (root, factory){\n 'use strict';\n\n var PubSub = {};\n root.PubSub = PubSub;\n\n var define = root.define;\n\n factory(PubSub);\n\n // AMD support\n if (typeof define === 'function' && define.amd){\n define(function() { return PubSub; });\n\n // CommonJS and Node.js module support\n } else if (true){\n if (module !== undefined && module.exports) {\n exports = module.exports = PubSub; // Node.js specific `module.exports`\n }\n exports.PubSub = PubSub; // CommonJS module 1.1.1 spec\n module.exports = exports = PubSub; // CommonJS\n }\n\n}(( typeof window === 'object' && window ) || this, function (PubSub){\n 'use strict';\n\n var messages = {},\n lastUid = -1;\n\n function hasKeys(obj){\n var key;\n\n for (key in obj){\n if ( obj.hasOwnProperty(key) ){\n return true;\n }\n }\n return false;\n }\n\n /**\n * Returns a function that throws the passed exception, for use as argument for setTimeout\n * @alias throwException\n * @function\n * @param { Object } ex An Error object\n */\n function throwException( ex ){\n return function reThrowException(){\n throw ex;\n };\n }\n\n function callSubscriberWithDelayedExceptions( subscriber, message, data ){\n try {\n subscriber( message, data );\n } catch( ex ){\n setTimeout( throwException( ex ), 0);\n }\n }\n\n function callSubscriberWithImmediateExceptions( subscriber, message, data ){\n subscriber( message, data );\n }\n\n function deliverMessage( originalMessage, matchedMessage, data, immediateExceptions ){\n var subscribers = messages[matchedMessage],\n callSubscriber = immediateExceptions ? callSubscriberWithImmediateExceptions : callSubscriberWithDelayedExceptions,\n s;\n\n if ( !messages.hasOwnProperty( matchedMessage ) ) {\n return;\n }\n\n for (s in subscribers){\n if ( subscribers.hasOwnProperty(s)){\n callSubscriber( subscribers[s], originalMessage, data );\n }\n }\n }\n\n function createDeliveryFunction( message, data, immediateExceptions ){\n return function deliverNamespaced(){\n var topic = String( message ),\n position = topic.lastIndexOf( '.' );\n\n // deliver the message as it is now\n deliverMessage(message, message, data, immediateExceptions);\n\n // trim the hierarchy and deliver message to each level\n while( position !== -1 ){\n topic = topic.substr( 0, position );\n position = topic.lastIndexOf('.');\n deliverMessage( message, topic, data, immediateExceptions );\n }\n };\n }\n\n function messageHasSubscribers( message ){\n var topic = String( message ),\n found = Boolean(messages.hasOwnProperty( topic ) && hasKeys(messages[topic])),\n position = topic.lastIndexOf( '.' );\n\n while ( !found && position !== -1 ){\n topic = topic.substr( 0, position );\n position = topic.lastIndexOf( '.' );\n found = Boolean(messages.hasOwnProperty( topic ) && hasKeys(messages[topic]));\n }\n\n return found;\n }\n\n function publish( message, data, sync, immediateExceptions ){\n message = (typeof message === 'symbol') ? message.toString() : message;\n\n var deliver = createDeliveryFunction( message, data, immediateExceptions ),\n hasSubscribers = messageHasSubscribers( message );\n\n if ( !hasSubscribers ){\n return false;\n }\n\n if ( sync === true ){\n deliver();\n } else {\n setTimeout( deliver, 0 );\n }\n return true;\n }\n\n /**\n * Publishes the message, passing the data to it's subscribers\n * @function\n * @alias publish\n * @param { String } message The message to publish\n * @param {} data The data to pass to subscribers\n * @return { Boolean }\n */\n PubSub.publish = function( message, data ){\n return publish( message, data, false, PubSub.immediateExceptions );\n };\n\n /**\n * Publishes the message synchronously, passing the data to it's subscribers\n * @function\n * @alias publishSync\n * @param { String } message The message to publish\n * @param {} data The data to pass to subscribers\n * @return { Boolean }\n */\n PubSub.publishSync = function( message, data ){\n return publish( message, data, true, PubSub.immediateExceptions );\n };\n\n /**\n * Subscribes the passed function to the passed message. Every returned token is unique and should be stored if you need to unsubscribe\n * @function\n * @alias subscribe\n * @param { String } message The message to subscribe to\n * @param { Function } func The function to call when a new message is published\n * @return { String }\n */\n PubSub.subscribe = function( message, func ){\n if ( typeof func !== 'function'){\n return false;\n }\n\n message = (typeof message === 'symbol') ? message.toString() : message;\n\n // message is not registered yet\n if ( !messages.hasOwnProperty( message ) ){\n messages[message] = {};\n }\n\n // forcing token as String, to allow for future expansions without breaking usage\n // and allow for easy use as key names for the 'messages' object\n var token = 'uid_' + String(++lastUid);\n messages[message][token] = func;\n \n // return token for unsubscribing\n return token;\n };\n\n /**\n * Subscribes the passed function to the passed message once\n * @function\n * @alias subscribeOnce\n * @param { String } message The message to subscribe to\n * @param { Function } func The function to call when a new message is published\n * @return { PubSub }\n */\n PubSub.subscribeOnce = function( message, func ){\n var token = PubSub.subscribe( message, function(){\n // before func apply, unsubscribe message\n PubSub.unsubscribe( token );\n func.apply( this, arguments );\n });\n return PubSub;\n };\n\n /**\n * Clears all subscriptions\n * @function\n * @public\n * @alias clearAllSubscriptions\n */\n PubSub.clearAllSubscriptions = function clearAllSubscriptions(){\n messages = {};\n };\n\n /**\n * Clear subscriptions by the topic\n * @function\n * @public\n * @alias clearAllSubscriptions\n * @return { int }\n */\n PubSub.clearSubscriptions = function clearSubscriptions(topic){\n var m;\n for (m in messages){\n if (messages.hasOwnProperty(m) && m.indexOf(topic) === 0){\n delete messages[m];\n }\n }\n };\n\n /** \n Count subscriptions by the topic\n * @function\n * @public\n * @alias countSubscriptions\n * @return { Array }\n */\n PubSub.countSubscriptions = function countSubscriptions(topic){\n var m;\n var count = 0;\n for (m in messages){\n if (messages.hasOwnProperty(m) && m.indexOf(topic) === 0){\n count++;\n }\n }\n return count;\n };\n\n \n /** \n Gets subscriptions by the topic\n * @function\n * @public\n * @alias getSubscriptions\n */\n PubSub.getSubscriptions = function getSubscriptions(topic){\n var m;\n var list = [];\n for (m in messages){\n if (messages.hasOwnProperty(m) && m.indexOf(topic) === 0){\n list.push(m);\n }\n }\n return list;\n };\n\n /**\n * Removes subscriptions\n *\n * - When passed a token, removes a specific subscription.\n *\n\t * - When passed a function, removes all subscriptions for that function\n *\n\t * - When passed a topic, removes all subscriptions for that topic (hierarchy)\n * @function\n * @public\n * @alias subscribeOnce\n * @param { String | Function } value A token, function or topic to unsubscribe from\n * @example // Unsubscribing with a token\n * var token = PubSub.subscribe('mytopic', myFunc);\n * PubSub.unsubscribe(token);\n * @example // Unsubscribing with a function\n * PubSub.unsubscribe(myFunc);\n * @example // Unsubscribing from a topic\n * PubSub.unsubscribe('mytopic');\n */\n PubSub.unsubscribe = function(value){\n var descendantTopicExists = function(topic) {\n var m;\n for ( m in messages ){\n if ( messages.hasOwnProperty(m) && m.indexOf(topic) === 0 ){\n // a descendant of the topic exists:\n return true;\n }\n }\n\n return false;\n },\n isTopic = typeof value === 'string' && ( messages.hasOwnProperty(value) || descendantTopicExists(value) ),\n isToken = !isTopic && typeof value === 'string',\n isFunction = typeof value === 'function',\n result = false,\n m, message, t;\n\n if (isTopic){\n PubSub.clearSubscriptions(value);\n return;\n }\n\n for ( m in messages ){\n if ( messages.hasOwnProperty( m ) ){\n message = messages[m];\n\n if ( isToken && message[value] ){\n delete message[value];\n result = value;\n // tokens are unique, so we can just stop here\n break;\n }\n\n if (isFunction) {\n for ( t in message ){\n if (message.hasOwnProperty(t) && message[t] === value){\n delete message[t];\n result = true;\n }\n }\n }\n }\n }\n\n return result;\n };\n}));\n\n/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(/*! ./../../webpack/buildin/module.js */ \"./node_modules/webpack/buildin/module.js\")(module)))\n\n//# sourceURL=webpack:///./node_modules/pubsub-js/src/pubsub.js?");
120 |
121 | /***/ }),
122 |
123 | /***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js":
124 | /*!****************************************************************************!*\
125 | !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***!
126 | \****************************************************************************/
127 | /*! no static exports found */
128 | /***/ (function(module, exports, __webpack_require__) {
129 |
130 | "use strict";
131 | eval("\n\nvar isOldIE = function isOldIE() {\n var memo;\n return function memorize() {\n if (typeof memo === 'undefined') {\n // Test for IE <= 9 as proposed by Browserhacks\n // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805\n // Tests for existence of standard globals is to allow style-loader\n // to operate correctly into non-standard environments\n // @see https://github.com/webpack-contrib/style-loader/issues/177\n memo = Boolean(window && document && document.all && !window.atob);\n }\n\n return memo;\n };\n}();\n\nvar getTarget = function getTarget() {\n var memo = {};\n return function memorize(target) {\n if (typeof memo[target] === 'undefined') {\n var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself\n\n if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n try {\n // This will throw an exception if access to iframe is blocked\n // due to cross-origin restrictions\n styleTarget = styleTarget.contentDocument.head;\n } catch (e) {\n // istanbul ignore next\n styleTarget = null;\n }\n }\n\n memo[target] = styleTarget;\n }\n\n return memo[target];\n };\n}();\n\nvar stylesInDom = [];\n\nfunction getIndexByIdentifier(identifier) {\n var result = -1;\n\n for (var i = 0; i < stylesInDom.length; i++) {\n if (stylesInDom[i].identifier === identifier) {\n result = i;\n break;\n }\n }\n\n return result;\n}\n\nfunction modulesToDom(list, options) {\n var idCountMap = {};\n var identifiers = [];\n\n for (var i = 0; i < list.length; i++) {\n var item = list[i];\n var id = options.base ? item[0] + options.base : item[0];\n var count = idCountMap[id] || 0;\n var identifier = \"\".concat(id, \" \").concat(count);\n idCountMap[id] = count + 1;\n var index = getIndexByIdentifier(identifier);\n var obj = {\n css: item[1],\n media: item[2],\n sourceMap: item[3]\n };\n\n if (index !== -1) {\n stylesInDom[index].references++;\n stylesInDom[index].updater(obj);\n } else {\n stylesInDom.push({\n identifier: identifier,\n updater: addStyle(obj, options),\n references: 1\n });\n }\n\n identifiers.push(identifier);\n }\n\n return identifiers;\n}\n\nfunction insertStyleElement(options) {\n var style = document.createElement('style');\n var attributes = options.attributes || {};\n\n if (typeof attributes.nonce === 'undefined') {\n var nonce = true ? __webpack_require__.nc : undefined;\n\n if (nonce) {\n attributes.nonce = nonce;\n }\n }\n\n Object.keys(attributes).forEach(function (key) {\n style.setAttribute(key, attributes[key]);\n });\n\n if (typeof options.insert === 'function') {\n options.insert(style);\n } else {\n var target = getTarget(options.insert || 'head');\n\n if (!target) {\n throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\n }\n\n target.appendChild(style);\n }\n\n return style;\n}\n\nfunction removeStyleElement(style) {\n // istanbul ignore if\n if (style.parentNode === null) {\n return false;\n }\n\n style.parentNode.removeChild(style);\n}\n/* istanbul ignore next */\n\n\nvar replaceText = function replaceText() {\n var textStore = [];\n return function replace(index, replacement) {\n textStore[index] = replacement;\n return textStore.filter(Boolean).join('\\n');\n };\n}();\n\nfunction applyToSingletonTag(style, index, remove, obj) {\n var css = remove ? '' : obj.media ? \"@media \".concat(obj.media, \" {\").concat(obj.css, \"}\") : obj.css; // For old IE\n\n /* istanbul ignore if */\n\n if (style.styleSheet) {\n style.styleSheet.cssText = replaceText(index, css);\n } else {\n var cssNode = document.createTextNode(css);\n var childNodes = style.childNodes;\n\n if (childNodes[index]) {\n style.removeChild(childNodes[index]);\n }\n\n if (childNodes.length) {\n style.insertBefore(cssNode, childNodes[index]);\n } else {\n style.appendChild(cssNode);\n }\n }\n}\n\nfunction applyToTag(style, options, obj) {\n var css = obj.css;\n var media = obj.media;\n var sourceMap = obj.sourceMap;\n\n if (media) {\n style.setAttribute('media', media);\n } else {\n style.removeAttribute('media');\n }\n\n if (sourceMap && btoa) {\n css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\n } // For old IE\n\n /* istanbul ignore if */\n\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n while (style.firstChild) {\n style.removeChild(style.firstChild);\n }\n\n style.appendChild(document.createTextNode(css));\n }\n}\n\nvar singleton = null;\nvar singletonCounter = 0;\n\nfunction addStyle(obj, options) {\n var style;\n var update;\n var remove;\n\n if (options.singleton) {\n var styleIndex = singletonCounter++;\n style = singleton || (singleton = insertStyleElement(options));\n update = applyToSingletonTag.bind(null, style, styleIndex, false);\n remove = applyToSingletonTag.bind(null, style, styleIndex, true);\n } else {\n style = insertStyleElement(options);\n update = applyToTag.bind(null, style, options);\n\n remove = function remove() {\n removeStyleElement(style);\n };\n }\n\n update(obj);\n return function updateStyle(newObj) {\n if (newObj) {\n if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) {\n return;\n }\n\n update(obj = newObj);\n } else {\n remove();\n }\n };\n}\n\nmodule.exports = function (list, options) {\n options = options || {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of