1
0
mirror of https://github.com/fazo96/homework.git synced 2025-01-09 12:10:08 +01:00

major refractor and bunch of new features

This commit is contained in:
fazo96 2014-05-27 15:33:50 +02:00
parent 8de8f3739e
commit dcec227896
9 changed files with 342 additions and 120 deletions

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Enrico Fasoli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,8 +1,32 @@
# Homework
New homework app rewritten using Meteor because I realized the MEAN stack was
a bad idea: meteor is just better (or at least faster to write).
Schoolwork management application for students.
**requires Meteorite** and `bootstrap-3` and `fontawesome` Meteorite packages.
### Try it
[the app is hosted online!](http://homework.meteor.com)
### Development
Clone the repo, [install meteor](http://meteor.com) and **meteorite**.
Install dependent packages and then run `meteor`. That's it.
### License
GPLv3
The MIT License (MIT)
Copyright (c) 2014 Enrico Fasoli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,111 +0,0 @@
notes = new Meteor.Collection "notes"
# Server
if Meteor.isServer
Accounts.config {
sendVerificationEmail: false
loginExpirationInDays: 1
}
Meteor.publish "my-notes", ->
notes.find( { userId: @userId } ) unless not @userId
# Authentication
Accounts.validateNewUser (user) ->
if user.email and Meteor.check(user.email,String) is yes and user.email.contains '@' is yes and user.email.endsWith '.' is no and user.email.endsWith '@' is no
return yes
else throw new Meteor.Error 403, "Invalid Email"
if user.password and Meteor.check(user.password,String) is yes and user.password.length > 7
return yes
else throw new Meteor.Error 403, "Password invalid"
# Client
if Meteor.isClient
Deps.autorun -> Meteor.subscribe "my-notes" unless not Meteor.userId()
# User Interface
Template.userInfo.events {
'click #logout': (e,template) -> Meteor.logout()
'keypress #newNote': (e,template) ->
if e.keyCode is 13
notes.insert {
title: template.find('#newNote').value
content: "..."
userId: Meteor.userId()
}
template.find('#newNote').value = ""
}
Template.userInfo.in = -> Meteor.user().emails[0].address
# Notes template
Template.notes.truncateNoteDesc = (s) ->
if s.length > 52 then s.slice(0,48)+"..." else s
Template.notes.notes = ->
d = notes.find().fetch();
#d.splice d.indexOf(Session.get('note')), 1 ; d
Template.notes.events {
'click .close-note': -> notes.remove @_id
'click .edit-note': -> Session.set 'note', this
}
# Note Editor
Template.editor.note = -> Session.get 'note'
saveCurrentNote = (t,e) ->
if e and e.keyCode isnt 13 then return;
notes.update Session.get('note')._id,
$set:
title: t.find('.title').value
content: t.find('.area').value
Template.editor.events
'click .close-editor': -> Session.set 'note', undefined
'click .save-editor': (e,t) -> saveCurrentNote t
#'keypress .edit-note': (e,t) -> saveCurrentNote t, e # Doesnt work??
'keypress .title': (e,t) -> saveCurrentNote t, e
# Notifications
alerts = []
alertDep = new Deps.Dependency
errCallback = (err) -> notify { msg: err.reason }
# Show a notification
notify = (data) ->
alerts.push {
title: data.title
msg: data.msg
id: data.id or alerts.length
type: data.type or "danger"
}; alertDep.changed()
# Clear all notifications
clearNotifications = -> alerts.clear(); alertDep.changed()
# Get all the notifications
Template.notifications.notification = -> alertDep.depend(); alerts
Template.notifications.events {
'click .close-notification': (e,template) ->
alerts.splice alerts.indexOf(this), 1
alertDep.changed()
}
# Login and Register
pressLogin = (template) ->
mail = template.find('#mail').value; pass = template.find('#pass').value
Meteor.loginWithPassword mail, pass, (err) ->
errCallback err
Template.auth.working = -> Meteor.loggingIn()
Template.auth.events {
'keypress .login': (e,template) ->
if e.keyCode is 13 then pressLogin template
# Login
'click #login': (e,template) -> pressLogin template
# Register
'click #register': (e,template) ->
mail = template.find('#mail').value; pass = template.find('#pass').value
if not mail or mail.contains '@' is no or mail.endsWith '.' is yes or mail.endsWith '@' is yes
notify { msg: "Invalid Email" }
else
try
Accounts.createUser {
email: mail,
password: pass
}, (e) -> errCallback e
catch err
notify { msg: err }
}

94
client/client.coffee Normal file
View File

@ -0,0 +1,94 @@
# Homework - Client Side
notes = new Meteor.Collection "notes"
Deps.autorun -> Meteor.subscribe "my-notes" unless not Meteor.userId()
#Meteor.subscribe "my-notes"
# User Interface
Template.userInfo.events {
'click #logout': (e,template) -> Meteor.logout()
}
Template.userInfo.in = -> Meteor.user().emails[0].address
# Notes template
Template.notes.truncateNoteDesc = (s) ->
if s.length > 52 then s.slice(0,48)+"..." else s
Template.notes.notes = ->
d = notes.find().fetch()
Template.notes.events {
'click .close-note': -> notes.remove @_id
'click .edit-note': -> Session.set 'note', this
'keypress #newNote': (e,template) ->
if e.keyCode is 13
notes.insert {
title: template.find('#newNote').value
content: "..."
userId: Meteor.userId()
}
template.find('#newNote').value = ""
}
# Note Editor
Template.editor.note = -> Session.get 'note'
saveCurrentNote = (t,e) ->
if e and e.keyCode isnt 13 then return;
notes.update Session.get('note')._id,
$set:
title: t.find('.title').value
content: t.find('.area').value
Template.editor.events
'click .close-editor': -> Session.set 'note', undefined
'click .save-editor': (e,t) -> saveCurrentNote t
#'keypress .edit-note': (e,t) -> saveCurrentNote t, e # Doesnt work??
'keypress .title': (e,t) -> saveCurrentNote t, e
# Notifications
alerts = []
alertDep = new Deps.Dependency
errCallback = (err) -> notify { msg: err.reason }
# Show a notification
notify = (data) ->
alerts.push {
title: data.title
msg: data.msg
id: data.id or alerts.length
type: data.type or "danger"
}; alertDep.changed()
# Clear all notifications
clearNotifications = -> alerts.clear(); alertDep.changed()
# Get all the notifications
Template.notifications.notification = -> alertDep.depend(); alerts
Template.notifications.events {
'click .close-notification': (e,template) ->
alerts.splice alerts.indexOf(this), 1
alertDep.changed()
}
# Login and Register
pressLogin = (template) ->
mail = template.find('#mail').value; pass = template.find('#pass').value
Meteor.loginWithPassword mail, pass, (err) ->
errCallback err
Template.auth.working = -> Meteor.loggingIn()
Template.auth.events {
'keypress .login': (e,template) ->
if e.keyCode is 13 then pressLogin template
# Login
'click #login': (e,template) -> pressLogin template
# Register
'click #register': (e,template) ->
mail = template.find('#mail').value; pass = template.find('#pass').value
if not mail
notify { msg: "Please enter an Email" }
else if not pass
notify { msg: "Please enter a password" }
else if pass.length < 8
notify { msg: "Password too short" }
else # Sending actual registration request
try
Accounts.createUser {
email: mail,
password: pass
}, (e) -> errCallback e
catch err
notify { msg: err }
}

View File

@ -3,19 +3,21 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
{{> ribbon}}
<div class="container">
<div class="page-header">
<h1 id="title">Homework<br>
<small id="small">management for students</small>
</h1>
</div>
<div class="center-block" id="quicknotes">
<div class="center-block" id="ui-container">
{{#if currentUser}}
{{> editor}} {{> notes}} {{> userInfo}}
{{else}}
{{> auth}}
{{/if}}
</div>
<div class="center-block" align="center">{{> footer}}</div>
</div>
</body>
@ -29,6 +31,9 @@
</li>
{{/each}}
</ul>
<div align="center">
<input type="text" id="newNote" class="form-control" placeholder="Add new note">
</div>
</template>
<template name="auth">
@ -52,10 +57,9 @@
</div>
</template>
<template name="userInfo">
<template name="userInfo"><hr>
<div align="center">
<input type="text" id="newNote" class="form-control" placeholder="Add new note">
<p>Logged in as {{in}}</p>
<p>{{in}}</p>
<button type="button" id="logout" class="btn btn-danger">Logout</button>
</div>
</template>
@ -87,3 +91,20 @@
</div>
{{/each}}
</template>
<template name="footer">
<hr>
<p>This app is <a href="https://en.wikipedia.org/wiki/Free_software">Free Software</a>, under the <a href="http://opensource.org/licenses/MIT">MIT License</a></p>
<p>Built by Enrico Fasoli</p>
<a class="custom-link" href="http://www.linkedin.com/profile/view?id=292450419"><i class="fa fa-linkedin fa-2x"></i></a>
<a href="http://twitter.com/fazo96"><i class="fa fa-twitter fa-2x footer-center-icon"></i></a>
<a class="custom-link" href="http://github.com/fazo96"><i class="fa fa-github fa-2x"></i></a>
</template>
<template name="ribbon">
<div class="github-fork-ribbon-wrapper right">
<div class="github-fork-ribbon">
<a href="http://github.com/fazo96/homework">Fork me on GitHub</a>
</div>
</div>
</template>

142
client/ribbon.css Normal file
View File

@ -0,0 +1,142 @@
/*
This cool github ribbon was developed by:
https://github.com/simonwhitaker
All credit for this file goes to him!
Repo for github-ribbon-css: https://github.com/simonwhitaker/github-fork-ribbon-css
*/
/* Left will inherit from right (so we don't need to duplicate code) */
.github-fork-ribbon {
/* The right and left classes determine the side we attach our banner to */
position: absolute;
/* Add a bit of padding to give some substance outside the "stitching" */
padding: 2px 0;
/* Set the base colour */
background-color: #a00;
/* Set a gradient: transparent black at the top to almost-transparent black at the bottom */
background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.15)));
background-image: -webkit-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -moz-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -ms-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: -o-linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15));
/* Add a drop shadow */
-webkit-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.5);
/* Set the font */
font: 700 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
z-index: 9999;
pointer-events: auto;
}
.github-fork-ribbon a,
.github-fork-ribbon a:hover {
/* Set the text properties */
color: #fff;
text-decoration: none;
text-shadow: 0 -1px rgba(0, 0, 0, 0.5);
text-align: center;
/* Set the geometry. If you fiddle with these you'll also need
to tweak the top and right values in .github-fork-ribbon. */
width: 200px;
line-height: 20px;
/* Set the layout properties */
display: inline-block;
padding: 2px 0;
/* Add "stitching" effect */
border-width: 1px 0;
border-style: dotted;
border-color: #fff;
border-color: rgba(255, 255, 255, 0.7);
}
.github-fork-ribbon-wrapper {
width: 150px;
height: 150px;
position: absolute;
overflow: hidden;
top: 0;
z-index: 9999;
pointer-events: none;
}
.github-fork-ribbon-wrapper.fixed {
position: fixed;
}
.github-fork-ribbon-wrapper.left {
left: 0;
}
.github-fork-ribbon-wrapper.right {
right: 0;
}
.github-fork-ribbon-wrapper.left-bottom {
position: fixed;
top: inherit;
bottom: 0;
left: 0;
}
.github-fork-ribbon-wrapper.right-bottom {
position: fixed;
top: inherit;
bottom: 0;
right: 0;
}
.github-fork-ribbon-wrapper.right .github-fork-ribbon {
top: 42px;
right: -43px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.github-fork-ribbon-wrapper.left .github-fork-ribbon {
top: 42px;
left: -43px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.github-fork-ribbon-wrapper.left-bottom .github-fork-ribbon {
top: 80px;
left: -43px;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.github-fork-ribbon-wrapper.right-bottom .github-fork-ribbon {
top: 80px;
right: -43px;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
}

View File

@ -15,6 +15,9 @@ input {
clear:both;
}
.custom-link { color: #999; }
.custom-link:hover { color: #101010;}
.subtitle { color: #999; }
/* Custom Classes */
@ -64,7 +67,12 @@ input {
/* IDs */
#quicknotes {
.footer-center-icon {
margin-left: 10px;
margin-right: 10px
}
#ui-container {
max-width: 500px;
}

18
server/server.coffee Normal file
View File

@ -0,0 +1,18 @@
# Homework - Server Side
notes = new Meteor.Collection "notes"
Accounts.config {
sendVerificationEmail: true
loginExpirationInDays: 1
}
Meteor.publish "my-notes", ->
notes.find( { userId: @userId } ) unless not @userId
# Authentication
Accounts.validateNewUser (user) ->
if Match.test(user.email, String) and validateEmail user.email is yes
if user.password and Match.test(user.password,String) is yes and user.password.length > 7
return yes
else throw new Meteor.Error 403, "Invalid Password"
else throw new Meteor.Error 403, "Invalid Email"

5
server/util.coffee Normal file
View File

@ -0,0 +1,5 @@
# Utility Stuff for Homework
validateEmail = (email) ->
re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
re.test email