AppToDoList

This commit is contained in:
2021-12-01 09:16:49 +01:00
commit 10f70bb4c8
24 changed files with 1938 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

View File

@@ -0,0 +1,19 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze

1
.meteor/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
local

7
.meteor/.id Normal file
View File

@@ -0,0 +1,7 @@
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
yruldplduo4h.k19thi4lc1ct

26
.meteor/packages Normal file
View File

@@ -0,0 +1,26 @@
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.13.0 # The database Meteor supports right now
blaze-html-templates # Compile .html files into Meteor Blaze views
jquery # Wrapper package for npm-installed jquery
reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.2.0 # Meteor's client-side reactive programming library
standard-minifier-css@1.7.4 # CSS minifier run for production mode
standard-minifier-js@2.7.2 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
ecmascript@0.16.0 # Enable ECMAScript2015+ syntax in app code
typescript@4.4.0 # Enable TypeScript syntax in .ts and .tsx modules
shell-server@0.5.0 # Server-side component of the `meteor shell` command
hot-module-replacement@0.4.0 # Update code in development without reloading the page
blaze-hot # Update files using Blaze's API with HMR
reactive-dict
accounts-password

3
.meteor/platforms Normal file
View File

@@ -0,0 +1,3 @@
android
browser
server

1
.meteor/release Normal file
View File

@@ -0,0 +1 @@
METEOR@2.5.1

89
.meteor/versions Normal file
View File

@@ -0,0 +1,89 @@
accounts-base@2.2.0
accounts-password@2.2.0
allow-deny@1.1.0
autoupdate@1.8.0
babel-compiler@7.7.0
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze@2.5.0
blaze-hot@1.1.0
blaze-html-templates@1.2.1
blaze-tools@1.1.2
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.0
callback-hook@1.4.0
check@1.3.1
ddp@1.4.0
ddp-client@2.5.0
ddp-common@1.4.0
ddp-rate-limiter@1.1.0
ddp-server@2.5.0
diff-sequence@1.1.1
dynamic-import@0.7.2
ecmascript@0.16.0
ecmascript-runtime@0.8.0
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.1
email@2.2.0
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
hot-module-replacement@0.4.0
html-tools@1.1.2
htmljs@1.1.1
id-map@1.1.1
inter-process-messaging@0.1.1
jquery@3.0.0
launch-screen@1.3.0
localstorage@1.2.0
logging@1.3.1
meteor@1.10.0
meteor-base@1.5.1
minifier-css@1.6.0
minifier-js@2.7.2
minimongo@1.7.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.7
modules@0.17.0
modules-runtime@0.12.0
modules-runtime-hot@0.14.0
mongo@1.13.0
mongo-decimal@0.1.2
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@3.9.1
observe-sequence@1.0.19
ordered-dict@1.1.0
promise@0.12.0
random@1.2.0
rate-limit@1.0.9
react-fast-refresh@0.2.0
reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.3.1
retry@1.1.0
routepolicy@1.1.1
service-configuration@1.3.0
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.4.0
spacebars@1.2.0
spacebars-compiler@1.2.1
standard-minifier-css@1.7.4
standard-minifier-js@2.7.2
templating@1.4.1
templating-compiler@1.4.1
templating-runtime@1.5.0
templating-tools@1.2.0
tracker@1.2.0
typescript@4.4.0
ui@1.0.13
underscore@1.0.10
url@1.3.2
webapp@1.13.0
webapp-hashing@1.1.0

194
client/main.css Normal file
View File

@@ -0,0 +1,194 @@
body {
font-family: sans-serif;
background-color: #315481;
background-image: linear-gradient(to bottom, #315481, #918e82 100%);
background-attachment: fixed;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0;
margin: 0;
font-size: 14px;
}
button {
font-weight: bold;
font-size: 1em;
border: none;
color: white;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
padding: 5px;
cursor: pointer;
}
button:focus {
outline: 0;
}
.app {
display: flex;
flex-direction: column;
height: 100vh;
}
.app-header {
flex-grow: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.main {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: auto;
background: white;
}
.main::-webkit-scrollbar {
width: 0;
height: 0;
background: inherit;
}
header {
background: #d2edf4;
background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
padding: 20px 15px 15px 15px;
position: relative;
box-shadow: 0 3px 3px rgba(34, 25, 25, 0.4);
}
.app-bar {
display: flex;
justify-content: space-between;
}
.app-bar h1 {
font-size: 1.5em;
margin: 0;
display: inline-block;
margin-right: 1em;
}
.task-form {
display: flex;
margin: 16px;
}
.task-form > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
}
.task-form > input:focus {
outline: 0;
}
.task-form > button {
min-width: 100px;
height: 95%;
background-color: #315481;
}
.tasks {
list-style-type: none;
padding-inline-start: 0;
padding-left: 16px;
padding-right: 16px;
margin-block-start: 0;
margin-block-end: 0;
}
.tasks > li {
display: flex;
padding: 16px;
border-bottom: #eee solid 1px;
}
.tasks > li > span {
flex-grow: 1;
}
.tasks > li > button {
justify-self: flex-end;
background-color: #ff3046;
}
.filter {
display: flex;
justify-content: center;
}
.filter > button {
background-color: #62807e;
}
.login-form {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
}
.login-form > div {
margin: 8px;
}
.login-form > div > label {
font-weight: bold;
}
.login-form > div > input {
flex-grow: 1;
box-sizing: border-box;
padding: 10px 6px;
background: transparent;
border: 1px solid #aaa;
width: 100%;
font-size: 1em;
margin-right: 16px;
margin-top: 4px;
}
.login-form > div > input:focus {
outline: 0;
}
.login-form > div > button {
background-color: #62807e;
}
.user {
display: flex;
align-self: flex-end;
margin: 8px 16px 0;
font-weight: bold;
}
.loading {
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
align-items: center;
font-weight: bold;
}

11
client/main.html Normal file
View File

@@ -0,0 +1,11 @@
<head>
<meta charset="utf-8"/>
<meta http-equiv="x-ua-compatible" content="ie=edge"/>
<meta
name="viewport"
content="width=device-width, height=device-height, viewport-fit=cover, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-capable" content="yes"/>
<title>Simple todo</title>
</head>

1
client/main.js Normal file
View File

@@ -0,0 +1 @@
import '../imports/ui/App.js';

View File

@@ -0,0 +1,56 @@
import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check';
import { TasksCollection } from '../db/TasksCollection';
Meteor.methods({
'tasks.insert'(text) {
check(text, String);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
TasksCollection.insert({
text,
createdAt: new Date,
userId: this.userId,
})
},
'tasks.remove'(taskId) {
check(taskId, String);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
const task = TasksCollection.findOne({ _id: taskId, userId: this.userId });
if (!task) {
throw new Meteor.Error('Access denied.');
}
TasksCollection.remove(taskId);
},
'tasks.setIsChecked'(taskId, isChecked) {
check(taskId, String);
check(isChecked, Boolean);
if (!this.userId) {
throw new Meteor.Error('Not authorized.');
}
const task = TasksCollection.findOne({ _id: taskId, userId: this.userId });
if (!task) {
throw new Meteor.Error('Access denied.');
}
TasksCollection.update(taskId, {
$set: {
isChecked,
},
});
}
});

View File

@@ -0,0 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { TasksCollection } from '/imports/db/TasksCollection';
Meteor.publish('tasks', function publishTasks() {
return TasksCollection.find({ userId: this.userId });
});

View File

@@ -0,0 +1,3 @@
import { Mongo } from 'meteor/mongo';
export const TasksCollection = new Mongo.Collection('tasks');

54
imports/ui/App.html Normal file
View File

@@ -0,0 +1,54 @@
<body>
{{> mainContainer }}
</body>
<template name="mainContainer">
<div class="app">
<header>
<div class="app-bar">
<div class="app-header">
<h1>📝️ To Do List {{incompleteCount}}</h1>
</div>
</div>
</header>
<div class="main">
{{#if isUserLogged}}
<div class="user">
{{getUser.username}} 🚪
</div>
{{> form }}
<div class="filter">
<button id="hide-completed-button">
{{#if hideCompleted}}
Show All
{{else}}
Hide Completed
{{/if}}
</button>
</div>
{{#if isLoading}}
<div class="loading">loading...</div>
{{/if}}
<ul class="tasks">
{{#each tasks}}
{{> task}}
{{/each}}
</ul>
{{else}}
{{> login }}
{{/if}}
</div>
</div>
</template>
<template name="form">
<form class="task-form">
<input type="text" name="text" placeholder="Type to add new tasks" />
<button type="submit">Add Task</button>
</form>
</template>

103
imports/ui/App.js Normal file
View File

@@ -0,0 +1,103 @@
import { Template } from 'meteor/templating';
import { TasksCollection } from "../db/TasksCollection";
import { ReactiveDict } from 'meteor/reactive-dict';
import './App.html';
import './Task.js';
import "./Login.js";
const HIDE_COMPLETED_STRING = "hideCompleted";
const getUser = () => Meteor.user();
const isUserLogged = () => !!getUser();
const IS_LOADING_STRING = "isLoading";
const getTasksFilter = () => {
const user = getUser();
const hideCompletedFilter = { isChecked: { $ne: true } };
const userFilter = user ? { userId: user._id } : {};
const pendingOnlyFilter = { ...hideCompletedFilter, ...userFilter };
return { userFilter, pendingOnlyFilter };
}
Template.mainContainer.events({
"click #hide-completed-button"(event, instance) {
const currentHideCompleted = instance.state.get(HIDE_COMPLETED_STRING);
instance.state.set(HIDE_COMPLETED_STRING, !currentHideCompleted);
},
'click .user'() {
Meteor.logout();
}
});
Template.mainContainer.onCreated(function mainContainerOnCreated() {
this.state = new ReactiveDict();
});
Template.mainContainer.onCreated(function mainContainerOnCreated() {
this.state = new ReactiveDict();
const handler = Meteor.subscribe('tasks');
Tracker.autorun(() => {
this.state.set(IS_LOADING_STRING, !handler.ready());
});
});
Template.mainContainer.helpers({
tasks() {
const instance = Template.instance();
const hideCompleted = instance.state.get(HIDE_COMPLETED_STRING);
const { pendingOnlyFilter, userFilter } = getTasksFilter();
if (!isUserLogged()) {
return [];
}
return TasksCollection.find(hideCompleted ? pendingOnlyFilter : userFilter, {
sort: { createdAt: -1 },
}).fetch();
},
isLoading() {
const instance = Template.instance();
return instance.state.get(IS_LOADING_STRING);
},
hideCompleted() {
return Template.instance().state.get(HIDE_COMPLETED_STRING);
},
incompleteCount() {
if (!isUserLogged()) {
return '';
}
const { pendingOnlyFilter } = getTasksFilter();
const incompleteTasksCount = TasksCollection.find(pendingOnlyFilter).count();
return incompleteTasksCount ? `(${incompleteTasksCount})` : '';
},
isUserLogged() {
return isUserLogged();
},
getUser() {
return getUser();
}
});
Template.form.events({
"submit .task-form"(event) {
// Prevent default browser form submit
event.preventDefault();
// Get value from form element
const target = event.target;
const text = target.text.value;
// Insert a task into the collection
Meteor.call('tasks.insert', text);
// Clear form
target.text.value = '';
}
})

28
imports/ui/Login.html Normal file
View File

@@ -0,0 +1,28 @@
<template name="login">
<form class="login-form">
<div>
<label htmlFor="username">Username</label>
<input
type="text"
placeholder="Username"
name="username"
required
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
placeholder="Password"
name="password"
required
/>
</div>
<div>
<button type="submit">Log In</button>
</div>
</form>
</template>

16
imports/ui/Login.js Normal file
View File

@@ -0,0 +1,16 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './Login.html';
Template.login.events({
'submit .login-form'(e) {
e.preventDefault();
const target = e.target;
const username = target.username.value;
const password = target.password.value;
Meteor.loginWithPassword(username, password);
}
});

7
imports/ui/Task.html Normal file
View File

@@ -0,0 +1,7 @@
<template name="task">
<li>
<input type="checkbox" checked="{{isChecked}}" class="toggle-checked" />
<span>{{text}}</span>
<button class="delete">&times;</button>
</li>
</template>

15
imports/ui/Task.js Normal file
View File

@@ -0,0 +1,15 @@
import { Template } from 'meteor/templating';
import './Task.html';
Template.task.events({
'click .toggle-checked'() {
Meteor.call('tasks.setIsChecked', this._id, !this.isChecked);
},
'click .delete'() {
Meteor.call('tasks.remove', this._id);
},
});

1216
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "simple-todos-blaze",
"private": true,
"scripts": {
"start": "meteor run",
"test": "meteor test --once --driver-package meteortesting:mocha",
"test-app": "TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha",
"visualize": "meteor --production --extra-packages bundle-visualizer"
},
"dependencies": {
"@babel/runtime": "^7.15.4",
"bcrypt": "^5.0.1",
"jquery": "^3.6.0",
"meteor-node-stubs": "^1.1.0"
},
"meteor": {
"mainModule": {
"client": "client/main.js",
"server": "server/main.js"
},
"testModule": "tests/main.js"
}
}

38
server/main.js Normal file
View File

@@ -0,0 +1,38 @@
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
import '/imports/api/tasksPublications';
const insertTask = (taskText, user) =>
TasksCollection.insert({
text: taskText,
userId: user._id,
createdAt: new Date(),
});
const SEED_USERNAME = 'meteorite';
const SEED_PASSWORD = 'password';
Meteor.startup(() => {
if (!Accounts.findUserByUsername(SEED_USERNAME)) {
Accounts.createUser({
username: SEED_USERNAME,
password: SEED_PASSWORD,
});
}
const user = Accounts.findUserByUsername(SEED_USERNAME);
if (TasksCollection.find().count() === 0) {
[
'First Task',
'Second Task',
'Third Task',
'Fourth Task',
'Fifth Task',
'Sixth Task',
'Seventh Task',
].forEach(taskText => insertTask(taskText, user));
}
});

20
tests/main.js Normal file
View File

@@ -0,0 +1,20 @@
import assert from "assert";
describe("simple-todos-blaze", function () {
it("package.json has correct name", async function () {
const { name } = await import("../package.json");
assert.strictEqual(name, "simple-todos-blaze");
});
if (Meteor.isClient) {
it("client is not server", function () {
assert.strictEqual(Meteor.isServer, false);
});
}
if (Meteor.isServer) {
it("server is not client", function () {
assert.strictEqual(Meteor.isClient, false);
});
}
});