├── .gitignore ├── .meteor ├── .finished-upgraders ├── .gitignore ├── .id ├── cordova-plugins ├── packages ├── platforms ├── release ├── smart.json └── versions ├── LICENSE ├── README.md ├── both ├── NCSchemas.js ├── _config │ ├── _config.js │ ├── accounts.js │ ├── adminConfig.js │ ├── emails.js │ └── seo.js ├── app.js ├── collections │ ├── _schemas.js │ ├── attachments.js │ ├── posts.js │ ├── profile_pictures.js │ └── users.js └── router.js ├── client ├── customTemplates.js ├── index.html ├── layouts │ ├── home_layout │ │ └── home_layout.html │ └── master_layout │ │ ├── master_layout.html │ │ ├── master_layout.js │ │ ├── master_layout.less │ │ ├── navbar.less │ │ └── xs-sidebar.less ├── libraries │ ├── skrollr.js │ └── wow.min.js ├── shared │ ├── deps.js │ ├── favoriteButtons.html │ ├── footer.html │ ├── helpers.js │ ├── loading.html │ ├── navbar.html │ └── not_found.html ├── style │ ├── bootstrap-variables.less │ ├── libraries │ │ ├── animate.css │ │ └── bootstrap.less │ └── style.less ├── tracker.js └── views │ ├── account │ ├── account.html │ └── account.js │ ├── dashboard │ ├── dashboard.html │ └── dashboard.js │ ├── home │ ├── home.html │ ├── home.js │ └── home.less │ └── profile │ ├── profile.html │ └── profile.js ├── packages └── bootstrap │ ├── .gitignore │ ├── README.md │ ├── bootstrap.import.less │ ├── lib │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── js │ │ ├── affix.js │ │ ├── alert.js │ │ ├── button.js │ │ ├── carousel.js │ │ ├── collapse.js │ │ ├── dropdown.js │ │ ├── modal.js │ │ ├── popover.js │ │ ├── scrollspy.js │ │ ├── tab.js │ │ ├── tooltip.js │ │ └── transition.js │ └── less │ │ ├── alerts.import.less │ │ ├── badges.import.less │ │ ├── bootstrap.import.less │ │ ├── bootswatch.less │ │ ├── breadcrumbs.import.less │ │ ├── button-groups.import.less │ │ ├── buttons.import.less │ │ ├── carousel.import.less │ │ ├── close.import.less │ │ ├── code.import.less │ │ ├── component-animations.import.less │ │ ├── dropdowns.import.less │ │ ├── forms.import.less │ │ ├── glyphicons.import.less │ │ ├── grid.import.less │ │ ├── input-groups.import.less │ │ ├── jumbotron.import.less │ │ ├── labels.import.less │ │ ├── list-group.import.less │ │ ├── media.import.less │ │ ├── mixins.import.less │ │ ├── mixins │ │ ├── alerts.import.less │ │ ├── background-variant.import.less │ │ ├── border-radius.import.less │ │ ├── buttons.import.less │ │ ├── center-block.import.less │ │ ├── clearfix.import.less │ │ ├── forms.import.less │ │ ├── gradients.import.less │ │ ├── grid-framework.import.less │ │ ├── grid.import.less │ │ ├── hide-text.import.less │ │ ├── image.import.less │ │ ├── labels.import.less │ │ ├── list-group.import.less │ │ ├── nav-divider.import.less │ │ ├── nav-vertical-align.import.less │ │ ├── opacity.import.less │ │ ├── pagination.import.less │ │ ├── panels.import.less │ │ ├── progress-bar.import.less │ │ ├── reset-filter.import.less │ │ ├── resize.import.less │ │ ├── responsive-visibility.import.less │ │ ├── size.import.less │ │ ├── tab-focus.import.less │ │ ├── table-row.import.less │ │ ├── text-emphasis.import.less │ │ ├── text-overflow.import.less │ │ └── vendor-prefixes.import.less │ │ ├── modals.import.less │ │ ├── navbar.import.less │ │ ├── navs.import.less │ │ ├── normalize.import.less │ │ ├── pager.import.less │ │ ├── pagination.import.less │ │ ├── panels.import.less │ │ ├── popovers.import.less │ │ ├── print.import.less │ │ ├── progress-bars.import.less │ │ ├── responsive-embed.import.less │ │ ├── responsive-utilities.import.less │ │ ├── scaffolding.import.less │ │ ├── tables.import.less │ │ ├── theme.import.less │ │ ├── thumbnails.import.less │ │ ├── tooltip.import.less │ │ ├── type.import.less │ │ ├── utilities.import.less │ │ └── wells.import.less │ ├── package.js │ ├── versions.json │ ├── yogiben:bootstrap-tests.js │ └── yogiben:bootstrap.js ├── public └── img │ ├── background-intro.jpg │ ├── ipad.png │ └── screenshot.png ├── readme ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── account.png ├── config.png ├── like_comment.png ├── login.png ├── meteor-starter-5.png ├── meteor-starter-6.png ├── post.png └── profile.png └── server ├── allow.js ├── main.js ├── methods └── account.js └── publish ├── collections.js └── user.js /.gitignore: -------------------------------------------------------------------------------- 1 | .meteor/local 2 | .meteor/meteorite 3 | .idea 4 | -------------------------------------------------------------------------------- /.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | -------------------------------------------------------------------------------- /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | uzp5lwxs8rpki09vei 8 | -------------------------------------------------------------------------------- /.meteor/cordova-plugins: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | meteor-platform 7 | coffeescript 8 | less 9 | accounts-base 10 | accounts-password 11 | aldeed:autoform 12 | alanning:roles 13 | aldeed:collection2 14 | aldeed:simple-schema 15 | joshowens:accounts-entry 16 | chrismbeckett:fontawesome4 17 | raix:handlebar-helpers 18 | cfs:standard-packages 19 | cfs:gridfs 20 | aldeed:template-extension 21 | yogiben:alerts 22 | cfs:graphicsmagick 23 | yogiben:helpers 24 | yogiben:user-helpers 25 | iron:router@1.0.0 26 | yogiben:autoform-modals@0.2.8 27 | yogiben:favorites@0.0.4 28 | yogiben:comments@0.0.8 29 | yogiben:notifications@0.0.6 30 | yogiben:pretty-emails 31 | yogiben:pretty-email 32 | yogiben:autoform-file 33 | yogiben:admin 34 | yogiben:autoform-map@0.0.2 35 | multiply:iron-router-progress 36 | manuelschoebel:ms-seo 37 | spiderable 38 | accounts-facebook 39 | accounts-google 40 | accounts-twitter 41 | bootstrap 42 | accounts-ui 43 | 44 | jparker:gravatar 45 | -------------------------------------------------------------------------------- /.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1 2 | -------------------------------------------------------------------------------- /.meteor/smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "bootstrap3-less": {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.0 2 | accounts-facebook@1.0.4 3 | accounts-google@1.0.4 4 | accounts-oauth@1.1.5 5 | accounts-password@1.1.1 6 | accounts-twitter@1.0.4 7 | accounts-ui@1.1.5 8 | accounts-ui-unstyled@1.1.7 9 | alanning:roles@1.2.13 10 | aldeed:autoform@4.2.2 11 | aldeed:collection2@2.3.3 12 | aldeed:simple-schema@1.3.2 13 | aldeed:template-extension@3.4.3 14 | alethes:pages@1.7.2 15 | autoupdate@1.2.1 16 | base64@1.0.3 17 | binary-heap@1.0.3 18 | blaze@2.1.1 19 | blaze-tools@1.0.3 20 | boilerplate-generator@1.0.3 21 | bootstrap@0.3.1 22 | callback-hook@1.0.3 23 | cfs:access-point@0.1.46 24 | cfs:base-package@0.0.29 25 | cfs:collection@0.5.5 26 | cfs:collection-filters@0.2.4 27 | cfs:data-man@0.0.6 28 | cfs:file@0.1.17 29 | cfs:graphicsmagick@0.0.18 30 | cfs:gridfs@0.0.33 31 | cfs:http-methods@0.0.27 32 | cfs:http-publish@0.0.13 33 | cfs:power-queue@0.9.11 34 | cfs:reactive-list@0.0.9 35 | cfs:reactive-property@0.0.4 36 | cfs:standard-packages@0.5.7 37 | cfs:storage-adapter@0.2.2 38 | cfs:tempstore@0.1.5 39 | cfs:upload-http@0.0.20 40 | cfs:worker@0.1.4 41 | check@1.0.5 42 | chrismbeckett:fontawesome4@4.2.2 43 | cmather:handlebars-server@0.2.0 44 | coffeescript@1.0.6 45 | ddp@1.1.0 46 | deps@1.0.7 47 | ejson@1.0.6 48 | email@1.0.6 49 | facebook@1.2.0 50 | fastclick@1.0.3 51 | geojson-utils@1.0.3 52 | google@1.1.5 53 | handlebars@1.0.3 54 | html-tools@1.0.4 55 | htmljs@1.0.4 56 | http@1.1.0 57 | id-map@1.0.3 58 | iron:controller@1.0.7 59 | iron:core@1.0.7 60 | iron:dynamic-template@1.0.7 61 | iron:layout@1.0.7 62 | iron:location@1.0.7 63 | iron:middleware-stack@1.0.7 64 | iron:router@1.0.7 65 | iron:url@1.0.7 66 | joshowens:accounts-entry@1.0.3 67 | joshowens:simple-form@0.2.2 68 | jparker:crypto-core@0.1.0 69 | jparker:crypto-md5@0.1.1 70 | jparker:gravatar@0.3.1 71 | jquery@1.11.3_2 72 | json@1.0.3 73 | launch-screen@1.0.2 74 | less@1.0.14 75 | livedata@1.0.13 76 | localstorage@1.0.3 77 | logging@1.0.7 78 | manuelschoebel:ms-seo@0.4.1 79 | meteor@1.1.6 80 | meteor-platform@1.2.2 81 | minifiers@1.1.5 82 | minimongo@1.0.8 83 | mobile-status-bar@1.0.3 84 | momentjs:moment@2.9.0 85 | mongo@1.1.0 86 | mongo-livedata@1.0.8 87 | mpowaga:string-template@0.1.0 88 | mquandalle:jade@0.2.9 89 | mrt:googlemaps@0.0.2 90 | mrt:moment@2.8.1 91 | mrt:underscore-string-latest@2.3.3 92 | multiply:iron-router-progress@1.0.1 93 | npm-bcrypt@0.7.8_2 94 | oauth@1.1.4 95 | oauth1@1.1.4 96 | oauth2@1.1.3 97 | observe-sequence@1.0.6 98 | ordered-dict@1.0.3 99 | raix:eventemitter@0.1.2 100 | raix:handlebar-helpers@0.2.4 101 | random@1.0.3 102 | reactive-dict@1.1.0 103 | reactive-var@1.0.5 104 | reload@1.1.3 105 | retry@1.0.3 106 | reywood:publish-composite@1.3.6 107 | routepolicy@1.0.5 108 | schnie:uploader@2.0.3 109 | service-configuration@1.0.4 110 | session@1.1.0 111 | sha@1.0.3 112 | softwarerero:accounts-t9n@1.0.8 113 | spacebars@1.0.6 114 | spacebars-compiler@1.0.6 115 | spiderable@1.0.7 116 | srp@1.0.3 117 | templating@1.1.1 118 | tracker@1.0.7 119 | twitter@1.1.4 120 | ui@1.0.6 121 | underscore@1.0.3 122 | url@1.0.4 123 | webapp@1.2.0 124 | webapp-hashing@1.0.3 125 | yogiben:admin@1.1.0 126 | yogiben:alerts@0.0.2 127 | yogiben:autoform-file@0.2.0 128 | yogiben:autoform-map@0.0.3 129 | yogiben:autoform-modals@0.3.2 130 | yogiben:comments@0.1.0 131 | yogiben:favorites@0.0.4 132 | yogiben:helpers@0.0.5 133 | yogiben:notifications@0.0.6 134 | yogiben:pretty-email@0.0.3 135 | yogiben:pretty-emails@0.0.2 136 | yogiben:user-helpers@0.0.8 137 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Benjamin Peter Jones (ben.p.js@gmail.com) 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 | Meteor Starter (Pure Javascript) 2 | ============== 3 | 4 | A Meteor boilerplate with a lot packed in. Written in Coffeescript. Converted to Pure Javascript for non-coffee drinkers. 5 | 6 | [Origianl Github Repo](https://github.com/yogiben/meteor-starter) 7 | 8 | [Demo](http://starter.meteor.com) - For admin, log in with: `starter@meteorfactory.io` and `meteorrocks` 9 | 10 | [Tutorials](http://learn.meteorfactory.io/meteor-starter/) 11 | 12 | [MIT License](http://choosealicense.com/licenses/mit/) 13 | 14 | ### Setup #### 15 | 16 | Follow these steps to create your own project with an initialized local git repo then run the app 17 | ``` 18 | git clone https://github.com/willjleong/meteor-starter-pure-js.git myapp 19 | cd myapp 20 | rm -rf .git 21 | git init 22 | meteor 23 | ``` 24 | 25 | ### What's included ### 26 | #### Visitors#### 27 | * Sexy landing page 28 | 29 | ####Users#### 30 | * Login / Sign up etc. from [Accounts Entry](https://github.com/Differential/accounts-entry) 31 | * Sign in with Facebook etc. with automatic photo import 32 | * Profile Page - add a photo, location and other fields defined in schema 33 | * Have a username (or not) 34 | * Change their password and delete their account 35 | 36 | #### Admin #### 37 | * Manage everything via an [admin dahsboard](https://github.com/yogiben/meteor-admin/) (go to `/admin`) 38 | 39 | #### Interactions #### 40 | * Create / edit posts with image upload 41 | * Favorite / comment on posts 42 | 43 | ### Customisation ### 44 | Detailed tutorails coming soon. 45 | 46 | First steps: 47 | * Edit basic setting in `/both/_config/_config.js` 48 | * Delete / modify HTML in `/client/views/home.html` 49 | * Update colors in `/client/style/bootstrap-variables.less` 50 | * Add / edit collections in `/both/collections/` 51 | * Create routes and views in `/both/router.js` and `/client/views` folder 52 | 53 | ### Screenshots ### 54 | ![alt tag](https://raw.githubusercontent.com/yogiben/meteor-starter/master/readme/meteor-starter-5.png) 55 | ![alt tag](https://raw.githubusercontent.com/yogiben/meteor-starter/master/readme/login.png) 56 | ![alt tag](https://raw.githubusercontent.com/yogiben/meteor-starter/master/readme/profile.png) 57 | ![alt tag](https://raw.githubusercontent.com/yogiben/meteor-starter/master/readme/like_comment.png) 58 | -------------------------------------------------------------------------------- /both/NCSchemas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | NCSchemas = {}; 6 | 7 | NCSchemas.updatePassword = new SimpleSchema({ 8 | old: { 9 | type: String, 10 | label: "Current Password", 11 | max: 50 12 | }, 13 | "new": { 14 | type: String, 15 | min: 6, 16 | max: 20, 17 | label: "New Password" 18 | }, 19 | confirm: { 20 | type: String, 21 | min: 6, 22 | max: 20, 23 | label: "Confirm new Password" 24 | } 25 | }); -------------------------------------------------------------------------------- /both/_config/_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Config = { 6 | name: 'My App', 7 | title: 'Make Meteor Apps. Fast.', 8 | subtitle: 'A boilerplate from MeteorFactory.io', 9 | logo: function() { 10 | return '' + this.name + ''; 11 | }, 12 | footer: function() { 13 | return this.name + ' - Copyright ' + new Date().getFullYear(); 14 | }, 15 | emails: { 16 | from: 'noreply@' + Meteor.absoluteUrl() 17 | }, 18 | blog: 'http://meteorfactory.io', 19 | about: 'http://meteorfactory.io', 20 | username: false, 21 | homeRoute: '/', 22 | dashboardRoute: '/dashboard', 23 | socialMedia: { 24 | facebook: { 25 | url: 'http://facebook.com/benjaminpeterjones', 26 | icon: 'facebook' 27 | }, 28 | twitter: { 29 | url: 'http://twitter.com/BenPeterJones', 30 | icon: 'twitter' 31 | }, 32 | github: { 33 | url: 'http://github.com/yogiben', 34 | icon: 'github' 35 | }, 36 | info: { 37 | url: 'http://meteorfactory.io', 38 | icon: 'link' 39 | } 40 | }, 41 | publicRoutes: ['home'] 42 | }; -------------------------------------------------------------------------------- /both/_config/accounts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Accounts.config({ 6 | sendVerificationEmail: true 7 | }); 8 | 9 | if (Meteor.isClient) { 10 | Meteor.startup(function() { 11 | if (Config.username) { 12 | return AccountsEntry.config({ 13 | homeRoute: '/', 14 | dashboardRoute: '/dashboard', 15 | profileRoute: 'profile', 16 | passwordSignupFields: 'USERNAME_AND_EMAIL' 17 | }); 18 | } else { 19 | return AccountsEntry.config({ 20 | homeRoute: '/', 21 | dashboardRoute: '/dashboard', 22 | profileRoute: 'profile', 23 | passwordSignupFields: 'EMAIL_ONLY' 24 | }); 25 | } 26 | }); 27 | } -------------------------------------------------------------------------------- /both/_config/adminConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | var AdminConfig; 6 | 7 | AdminConfig = { 8 | name: Config.name, 9 | collections: { 10 | Posts: { 11 | color: 'red', 12 | icon: 'pencil', 13 | auxCollections: ['Attachments'], 14 | tableColumns: [ 15 | { 16 | label: 'Title', 17 | name: 'title' 18 | }, { 19 | label: 'User', 20 | name: 'owner', 21 | collection: 'Users' 22 | } 23 | ] 24 | }, 25 | Comments: { 26 | color: 'green', 27 | icon: 'comments', 28 | auxCollections: ['Posts'], 29 | tableColumns: [ 30 | { 31 | label: 'Content', 32 | name: 'content' 33 | }, { 34 | label: 'Post', 35 | name: 'doc', 36 | collection: 'Posts', 37 | collection_property: 'title' 38 | }, { 39 | label: 'User', 40 | name: 'owner', 41 | collection: 'Users' 42 | } 43 | ] 44 | } 45 | }, 46 | dashboard: { 47 | homeUrl: '/dashboard' 48 | }, 49 | autoForm: { 50 | omitFields: ['createdAt', 'updatedAt'] 51 | } 52 | }; 53 | 54 | if (Meteor.isClient) { 55 | window.AdminConfig = AdminConfig; 56 | } else if (Meteor.isServer) { 57 | global.AdminConfig = AdminConfig; 58 | } -------------------------------------------------------------------------------- /both/_config/emails.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | if (Meteor.isServer) { 6 | PrettyEmail.options = { 7 | facebook: 'http://facebook.com/onthecode', 8 | twitter: 'http://twitter.com/BenPeterJones', 9 | website: 'http://benjaminpeterjones.com', 10 | siteName: 'Meteor Starter', 11 | companyAddress: 'Jessnerstrasse 18, 12047 Berlin', 12 | companyName: 'Code to Create', 13 | companyUrl: 'http://code2create.com' 14 | }; 15 | } -------------------------------------------------------------------------------- /both/_config/seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Meteor.startup(function() { 6 | if (Meteor.isClient) { 7 | return SEO.config({ 8 | title: this.Config.name, 9 | meta: { 10 | description: this.Config.subtitle 11 | } 12 | }); 13 | } 14 | }); -------------------------------------------------------------------------------- /both/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | App = { 6 | alertSuccess: function(message) { 7 | Session.set('alertSuccess', message); 8 | return Session.set('alertError', ''); 9 | }, 10 | alertError: function(message) { 11 | Session.set('alertError', message); 12 | return Session.set('alertSuccess', ''); 13 | } 14 | }; -------------------------------------------------------------------------------- /both/collections/_schemas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Schemas = {}; -------------------------------------------------------------------------------- /both/collections/attachments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Attachments = new FS.Collection("Attachments", { 6 | stores: [ 7 | new FS.Store.GridFS("attachments", { 8 | transformWrite: function(fileObj, readStream, writeStream) { 9 | if (gm.isAvailable) { 10 | if (fileObj.original.type.substr(0, 5) === 'image') { 11 | return gm(readStream, fileObj.name()).autoOrient().stream().pipe(writeStream); 12 | } else { 13 | return readStream.pipe(writeStream); 14 | } 15 | } else { 16 | return readStream.pipe(writeStream); 17 | } 18 | } 19 | }) 20 | ] 21 | }); -------------------------------------------------------------------------------- /both/collections/posts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Posts = new Meteor.Collection('posts'); 6 | 7 | Schemas.Posts = new SimpleSchema({ 8 | title: { 9 | type: String, 10 | max: 60 11 | }, 12 | content: { 13 | type: String, 14 | autoform: { 15 | rows: 5 16 | } 17 | }, 18 | createdAt: { 19 | type: Date, 20 | autoValue: function() { 21 | if (this.isInsert) { 22 | return new Date(); 23 | } 24 | } 25 | }, 26 | updatedAt: { 27 | type: Date, 28 | optional: true, 29 | autoValue: function() { 30 | if (this.isUpdate) { 31 | return new Date(); 32 | } 33 | } 34 | }, 35 | picture: { 36 | type: String, 37 | autoform: { 38 | afFieldInput: { 39 | type: 'fileUpload', 40 | collection: 'Attachments' 41 | } 42 | } 43 | }, 44 | owner: { 45 | type: String, 46 | regEx: SimpleSchema.RegEx.Id, 47 | autoValue: function() { 48 | if (this.isInsert) { 49 | return Meteor.userId(); 50 | } 51 | }, 52 | autoform: { 53 | options: function() { 54 | return _.map(Meteor.users.find().fetch(), function(user) { 55 | return { 56 | label: user.emails[0].address, 57 | value: user._id 58 | }; 59 | }); 60 | } 61 | } 62 | } 63 | }); 64 | 65 | Posts.attachSchema(Schemas.Posts); -------------------------------------------------------------------------------- /both/collections/profile_pictures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | ProfilePictures = new FS.Collection("profilePictures", { 6 | stores: [ 7 | new FS.Store.GridFS("images", { 8 | transformWrite: function(fileObj, readStream, writeStream) { 9 | if (gm.isAvailable) { 10 | return gm(readStream, fileObj.name()).autoOrient().stream().pipe(writeStream); 11 | } else { 12 | return readStream.pipe(writeStream); 13 | } 14 | } 15 | }), new FS.Store.GridFS("thumbs", { 16 | transformWrite: function(fileObj, readStream, writeStream) { 17 | var size; 18 | if (gm.isAvailable) { 19 | size = { 20 | width: 100, 21 | height: 100 22 | }; 23 | return gm(readStream, fileObj.name()).autoOrient().resize(size.width + "^>", size.height + "^>").gravity("Center").extent(size.width, size.height).stream().pipe(writeStream); 24 | } else { 25 | return readStream.pipe(writeStream); 26 | } 27 | } 28 | }) 29 | ], 30 | filter: { 31 | allow: { 32 | contentTypes: ['image/*'] 33 | } 34 | } 35 | }); -------------------------------------------------------------------------------- /both/collections/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Schemas.UserProfile = new SimpleSchema({ 6 | picture: { 7 | type: String, 8 | optional: true, 9 | label: 'Profile picture', 10 | autoform: { 11 | afFieldInput: { 12 | type: 'fileUpload', 13 | collection: 'ProfilePictures' 14 | } 15 | } 16 | }, 17 | firstName: { 18 | type: String, 19 | optional: true 20 | }, 21 | lastName: { 22 | type: String, 23 | optional: true 24 | }, 25 | birthday: { 26 | type: Date, 27 | optional: true 28 | }, 29 | bio: { 30 | type: String, 31 | optional: true, 32 | autoform: { 33 | rows: 4 34 | } 35 | }, 36 | location: { 37 | type: String, 38 | optional: true, 39 | autoform: { 40 | type: 'map', 41 | geolocation: true, 42 | searchBox: true, 43 | autolocate: true 44 | } 45 | }, 46 | country: { 47 | type: String, 48 | label: 'Nationality', 49 | allowedValues: ["Select Country", "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Anguilla", "Antigua & Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia & Herzegovina", "Botswana", "Brazil", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Cape Verde", "Cayman Islands", "Chad", "Chile", "China", "Colombia", "Congo", "Cook Islands", "Costa Rica", "Cote D Ivoire", "Croatia", "Cruise Ship", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Estonia", "Ethiopia", "Falkland Islands", "Faroe Islands", "Fiji", "Finland", "France", "French Polynesia", "French West Indies", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea Bissau", "Guyana", "Haiti", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kuwait", "Kyrgyz Republic", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Mauritania", "Mauritius", "Mexico", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Namibia", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russia", "Rwanda", "Saint Pierre & Miquelon", "Samoa", "San Marino", "Satellite", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Sri Lanka", "St Kitts & Nevis", "St Lucia", "St Vincent", "St. Lucia", "Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor L'Este", "Togo", "Tonga", "Trinidad & Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks & Caicos", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Uzbekistan", "Venezuela", "Vietnam", "Virgin Islands (US)", "Yemen", "Zambia", "Zimbabwe"], 50 | optional: true 51 | } 52 | }); 53 | 54 | Schemas.User = new SimpleSchema({ 55 | username: { 56 | type: String, 57 | regEx: /^[a-z0-9A-Z_]{3,15}$/, 58 | optional: true 59 | }, 60 | emails: { 61 | type: [Object], 62 | optional: true 63 | }, 64 | "emails.$.address": { 65 | type: String, 66 | regEx: SimpleSchema.RegEx.Email 67 | }, 68 | "emails.$.verified": { 69 | type: Boolean 70 | }, 71 | createdAt: { 72 | type: Date 73 | }, 74 | profile: { 75 | type: Schemas.UserProfile, 76 | optional: true 77 | }, 78 | services: { 79 | type: Object, 80 | optional: true, 81 | blackbox: true 82 | }, 83 | roles: { 84 | type: [String], 85 | blackbox: true, 86 | optional: true 87 | } 88 | }); 89 | 90 | Meteor.users.attachSchema(Schemas.User); 91 | 92 | StarterSchemas = Schemas; -------------------------------------------------------------------------------- /both/router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | var prepareView, publicRoutes, saveRedirectUrl, signInProhibited, signInRequired; 6 | 7 | Router.configure({ 8 | layoutTemplate: "masterLayout", 9 | loadingTemplate: "loading", 10 | notFoundTemplate: "notFound", 11 | routeControllerNameConverter: "camelCase", 12 | onBeforeAction: function() { 13 | if (Config.username && Meteor.userId() && !Meteor.user().username) { 14 | this.redirect('/setUserName'); 15 | } 16 | return this.next(); 17 | } 18 | }); 19 | 20 | Router.map(function() { 21 | this.route("home", { 22 | path: "/", 23 | layoutTemplate: "homeLayout" 24 | }); 25 | this.route("dashboard", { 26 | path: "/dashboard", 27 | waitOn: function() { 28 | return [Meteor.subscribe('posts'), Meteor.subscribe('favorites'), Meteor.subscribe('comments'), Meteor.subscribe('attachments')]; 29 | }, 30 | onBeforeAction: function() { 31 | var url; 32 | url = Session.get('redirectToAfterSignIn'); 33 | if (url) { 34 | Session.set('redirectToAfterSignIn', null); 35 | Router.go(url); 36 | } 37 | return this.next(); 38 | }, 39 | data: function() { 40 | return { 41 | Posts: Posts.find({}, { 42 | sort: { 43 | createdAt: -1 44 | } 45 | }).fetch() 46 | }; 47 | } 48 | }); 49 | this.route("profile", { 50 | path: "/profile", 51 | waitOn: function() { 52 | return Meteor.subscribe('profilePictures'); 53 | } 54 | }); 55 | this.route("account", { 56 | path: "/account", 57 | onStop: function() { 58 | return Alert.clear(); 59 | } 60 | }); 61 | return this.route("setUserName", { 62 | path: "/setUserName", 63 | onBeforeAction: function() { 64 | if (!Config.username || (Meteor.userId() && Meteor.user().username)) { 65 | this.redirect('/dashboard'); 66 | } 67 | return this.next(); 68 | } 69 | }); 70 | }); 71 | 72 | Router.waitOn(function() { 73 | Meteor.subscribe('user'); 74 | return Meteor.subscribe('userPicture'); 75 | }); 76 | 77 | prepareView = function() { 78 | var $bd,$body; 79 | $bd = $('.modal-backdrop'); 80 | $body = $('body'); 81 | window.scrollTo(0, 0); 82 | $body.removeClass('sidebar-active'); 83 | $bd.removeClass('in'); 84 | setTimeout(function() { 85 | return $bd.remove(); 86 | }, 300); 87 | return $body.attr('style', ''); 88 | }; 89 | 90 | Router.onAfterAction(prepareView); 91 | 92 | signInRequired = function() { 93 | AccountsEntry.signInRequired(this); 94 | if (this.next) { 95 | return this.next(); 96 | } 97 | }; 98 | 99 | saveRedirectUrl = function() { 100 | if (!Meteor.loggingIn()) { 101 | if (!Meteor.user()) { 102 | Session.set('redirectToAfterSignIn', this.url); 103 | } 104 | } 105 | return this.next(); 106 | }; 107 | 108 | publicRoutes = _.union(Config.publicRoutes, ['entrySignIn', 'entrySignUp', 'entryForgotPassword']); 109 | 110 | Router.onBeforeAction(saveRedirectUrl, { 111 | except: _.union(publicRoutes, ['entrySignOut']) 112 | }); 113 | 114 | Router.onBeforeAction(signInRequired, { 115 | except: publicRoutes 116 | }); 117 | 118 | signInProhibited = function() { 119 | if (Meteor.user()) { 120 | return Router.go('dashboard'); 121 | } else { 122 | if (this.next) { 123 | return this.next(); 124 | } 125 | } 126 | }; 127 | 128 | Router.onBeforeAction(signInProhibited, { 129 | only: ['entrySignUp', 'entrySignUp', 'entryForgotPassword'] 130 | }); -------------------------------------------------------------------------------- /client/customTemplates.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Template.myFavoriteButtonFavorited.replaces("favoriteButtonFavorited"); 6 | 7 | Template.myFavoriteButtonNotFavorited.replaces("favoriteButtonNotFavorited"); -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | Meteor Starter 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /client/layouts/home_layout/home_layout.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/layouts/master_layout/master_layout.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/layouts/master_layout/master_layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Template.masterLayout.events({ 6 | 'click .toggle-sidebar': function() { 7 | if ($('body').hasClass('sidebar-active')) { 8 | return $('body').removeClass('sidebar-active'); 9 | } else { 10 | return $('body').addClass('sidebar-active'); 11 | } 12 | }, 13 | 'click .open-sidebar': function() { 14 | return $('body').addClass('sidebar-active'); 15 | }, 16 | 'click .close-sidebar': function() { 17 | return $('body').removeClass('sidebar-active'); 18 | } 19 | }); -------------------------------------------------------------------------------- /client/layouts/master_layout/master_layout.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willjleong/meteor-starter-pure-js/7cdec3b7260ae0870b76fc8093887ecb463d2309/client/layouts/master_layout/master_layout.less -------------------------------------------------------------------------------- /client/layouts/master_layout/navbar.less: -------------------------------------------------------------------------------- 1 | .master-layout 2 | { 3 | .navbar-toggle 4 | { 5 | float: left; 6 | position: relative; 7 | 8 | i 9 | { 10 | color: white; 11 | cursor: pointer; 12 | position: absolute; 13 | font-size: 1.3em; 14 | font-weight: 100; 15 | top: 6px; 16 | } 17 | 18 | &.xs-mobile-cta 19 | { 20 | float: right; 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /client/layouts/master_layout/xs-sidebar.less: -------------------------------------------------------------------------------- 1 | @import "/client/style/bootstrap-variables.less"; 2 | @sidebar-width: 300px; 3 | 4 | @media (max-width: @screen-xs-max) { 5 | 6 | #xs-sidebar-overlay 7 | { 8 | position: absolute; 9 | top: -100%; 10 | left: 0; 11 | bottom:0; 12 | right:0; 13 | background-color: black; 14 | opacity: 0; 15 | display: none; 16 | cursor: pointer; 17 | 18 | 19 | -webkit-transition: opacity 0.4s; 20 | transition: opacity 0.4s; 21 | 22 | } 23 | 24 | 25 | .master-layout, .navbar-collapse 26 | { 27 | -webkit-transition: transform 0.4s; 28 | transition: transform 0.4s; 29 | } 30 | 31 | .navbar-collapse 32 | { 33 | 34 | position: fixed; 35 | // -webkit-transform: translate(-@sidebar-width,0px); 36 | -webkit-transform: translate(-301px,0px); 37 | top: 0; 38 | width: 300px; 39 | height: 100%; 40 | background-color: black; 41 | display: block !important; 42 | 43 | } 44 | 45 | body.sidebar-active 46 | { 47 | .navbar-collapse 48 | { 49 | display: inherit; 50 | } 51 | 52 | .master-layout 53 | { 54 | position: relative; 55 | -webkit-transform: translate(@sidebar-width,0px); 56 | } 57 | 58 | #xs-sidebar-overlay 59 | { 60 | display: block; 61 | opacity: 0.7; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /client/libraries/wow.min.js: -------------------------------------------------------------------------------- 1 | /*! WOW - v0.1.5 - 2014-03-05 2 | * Copyright (c) 2014 Matthieu Aussaguel; Licensed MIT */(function(){var a,b=function(a,b){return function(){return a.apply(b,arguments)}};a=function(){function a(){}return a.prototype.extend=function(a,b){var c,d;for(c in a)d=a[c],null!=d&&(b[c]=d);return b},a.prototype.isMobile=function(a){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a)},a}(),this.WOW=function(){function c(a){null==a&&(a={}),this.scrollCallback=b(this.scrollCallback,this),this.scrollHandler=b(this.scrollHandler,this),this.start=b(this.start,this),this.scrolled=!0,this.config=this.util().extend(a,this.defaults)}return c.prototype.defaults={boxClass:"wow",animateClass:"animated",offset:0,mobile:!0},c.prototype.init=function(){var a;return this.element=window.document.documentElement,"interactive"===(a=document.readyState)||"complete"===a?this.start():document.addEventListener("DOMContentLoaded",this.start)},c.prototype.start=function(){var a,b,c,d;if(this.boxes=this.element.getElementsByClassName(this.config.boxClass),this.boxes.length){if(this.disabled())return this.resetStyle();for(d=this.boxes,b=0,c=d.length;c>b;b++)a=d[b],this.applyStyle(a,!0);return window.addEventListener("scroll",this.scrollHandler,!1),window.addEventListener("resize",this.scrollHandler,!1),this.interval=setInterval(this.scrollCallback,50)}},c.prototype.stop=function(){return window.removeEventListener("scroll",this.scrollHandler,!1),window.removeEventListener("resize",this.scrollHandler,!1),null!=this.interval?clearInterval(this.interval):void 0},c.prototype.show=function(a){return this.applyStyle(a),a.className=""+a.className+" "+this.config.animateClass},c.prototype.applyStyle=function(a,b){var c,d,e;return d=a.getAttribute("data-wow-duration"),c=a.getAttribute("data-wow-delay"),e=a.getAttribute("data-wow-iteration"),a.setAttribute("style",this.customStyle(b,d,c,e))},c.prototype.resetStyle=function(){var a,b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(a.setAttribute("style","visibility: visible;"));return e},c.prototype.customStyle=function(a,b,c,d){var e;return e=a?"visibility: hidden; -webkit-animation-name: none; -moz-animation-name: none; animation-name: none;":"visibility: visible;",b&&(e+="-webkit-animation-duration: "+b+"; -moz-animation-duration: "+b+"; animation-duration: "+b+";"),c&&(e+="-webkit-animation-delay: "+c+"; -moz-animation-delay: "+c+"; animation-delay: "+c+";"),d&&(e+="-webkit-animation-iteration-count: "+d+"; -moz-animation-iteration-count: "+d+"; animation-iteration-count: "+d+";"),e},c.prototype.scrollHandler=function(){return this.scrolled=!0},c.prototype.scrollCallback=function(){var a;return this.scrolled&&(this.scrolled=!1,this.boxes=function(){var b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],a&&(this.isVisible(a)?this.show(a):e.push(a));return e}.call(this),!this.boxes.length)?this.stop():void 0},c.prototype.offsetTop=function(a){var b;for(b=a.offsetTop;a=a.offsetParent;)b+=a.offsetTop;return b},c.prototype.isVisible=function(a){var b,c,d,e,f;return c=a.getAttribute("data-wow-offset")||this.config.offset,f=window.pageYOffset,e=f+this.element.clientHeight-c,d=this.offsetTop(a),b=d+a.clientHeight,e>=d&&b>=f},c.prototype.util=function(){return this._util||(this._util=new a)},c.prototype.disabled=function(){return!this.config.mobile&&this.util().isMobile(navigator.userAgent)},c}()}).call(this); -------------------------------------------------------------------------------- /client/shared/deps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Deps.autorun(function() { 6 | if (Session.get('language')) { 7 | Session.set('Language', Languages[Session.get('language')]); 8 | } else { 9 | Session.set('Language', null); 10 | } 11 | if (Meteor.user()) { 12 | Session.set('learning', Meteor.user().learning); 13 | } 14 | if (Session.get('learning')) { 15 | Session.set('Learning', _.map(Session.get('learning'), function(string) { 16 | return Languages[string]; 17 | })); 18 | } 19 | if (Meteor.userId() && !_.isNull(Router.current()) && Router.current().route.name === 'entrySignIn') { 20 | Router.go('dashboard'); 21 | } 22 | if (Meteor.userId() && !_.isNull(Router.current()) && Router.current().route.name === 'entrySignUp') { 23 | return Router.go('dashboard'); 24 | } 25 | }); -------------------------------------------------------------------------------- /client/shared/favoriteButtons.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /client/shared/footer.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/shared/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Template.registerHelper('Config', function() { 6 | return Config; 7 | }); 8 | 9 | Template.registerHelper('NCSchemas', function() { 10 | return NCSchemas; 11 | }); 12 | 13 | Template.registerHelper('socialMedia', function() { 14 | return _.map(Config.socialMedia, function(obj) { 15 | return obj; 16 | }); 17 | }); -------------------------------------------------------------------------------- /client/shared/loading.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/shared/navbar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/shared/not_found.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/style/libraries/bootstrap.less: -------------------------------------------------------------------------------- 1 | @import "/client/style/bootstrap-variables.less"; 2 | @import "/packages/bootstrap/bootstrap.import.less"; -------------------------------------------------------------------------------- /client/style/style.less: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Lato:300,400,700,300italic"); 2 | @import "/client/style/bootstrap-variables.less"; 3 | 4 | @footer-height: 250px; 5 | 6 | html, body 7 | { 8 | height:100%; 9 | } 10 | 11 | .layout { 12 | min-height: 100%; 13 | height: auto !important; /* This line and the next line are not necessary unless you need IE6 support */ 14 | height: 100%; 15 | margin: 0 auto -@footer-height; /* the bottom margin is the negative value of the footer's height */ 16 | } 17 | 18 | 19 | 20 | footer, .push { 21 | clear: both; 22 | } 23 | 24 | footer, .push 25 | { 26 | height: @footer-height; 27 | } 28 | 29 | footer 30 | { margin-top: 150px; 31 | background-color: #333; 32 | padding-top: 30px; 33 | color: white; 34 | 35 | ul 36 | { 37 | padding-left: 0; 38 | li 39 | { 40 | display: inline-block; 41 | } 42 | } 43 | } 44 | 45 | button.navbar-toggle { 46 | background: inherit; 47 | } 48 | 49 | .navbar { 50 | margin-left: 0; 51 | } 52 | 53 | #iron-router-progress { 54 | background-color : darken(@brand-primary,20%); 55 | box-shadow : 0 0 5px darken(@brand-primary,20%); 56 | } 57 | 58 | #iron-router-progress.spinner:before { 59 | border-color : darken(@brand-primary,20%); 60 | } -------------------------------------------------------------------------------- /client/tracker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Tracker.autorun(function() { 6 | if (Meteor.userId() && !_.isNull(Router.current()) && Router.current().route.name === 'entrySignIn') { 7 | Router.go('dashboard'); 8 | } 9 | if (Meteor.userId() && !_.isNull(Router.current()) && Router.current().route.name === 'entrySignUp') { 10 | Router.go('dashboard'); 11 | } 12 | Meteor.subscribe('user'); 13 | Meteor.subscribe('users'); 14 | return Meteor.subscribe('userPicture'); 15 | }); -------------------------------------------------------------------------------- /client/views/account/account.html: -------------------------------------------------------------------------------- 1 | 46 | 47 | 60 | 61 | -------------------------------------------------------------------------------- /client/views/account/account.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | AutoForm.hooks({ 6 | updatePassword: { 7 | onSubmit: function(insertDoc, updateDoc, currentDoc) { 8 | if (insertDoc["new"] !== insertDoc.confirm) { 9 | Alert.error('Passwords do not match'); 10 | return false; 11 | } 12 | Accounts.changePassword(insertDoc.old, insertDoc["new"], function(e) { 13 | $('.btn-primary').attr('disabled', null); 14 | if (e) { 15 | return Alert.error(e.message); 16 | } else { 17 | return Alert.success('Password Updated'); 18 | } 19 | }); 20 | return false; 21 | } 22 | } 23 | }); 24 | 25 | Template.account.events({ 26 | 'click .js-delete-account': function() { 27 | return Meteor.call('deleteAccount', Meteor.userId()); 28 | } 29 | }); 30 | 31 | Template.setUserName.helpers({ 32 | user: function() { 33 | return Meteor.user(); 34 | } 35 | }); -------------------------------------------------------------------------------- /client/views/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 35 | 36 | 46 | 47 | 92 | 93 | -------------------------------------------------------------------------------- /client/views/dashboard/dashboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | AutoForm.hooks({ 6 | add: { 7 | onError: function(operation, error) { 8 | return App.alertError(error); 9 | } 10 | } 11 | }); -------------------------------------------------------------------------------- /client/views/home/home.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | Template.home.rendered = function() { 6 | var options, w, winHeight, winWidth; 7 | w = new WOW().init(); 8 | winWidth = $(window).width(); 9 | winHeight = $(window).height(); 10 | $("#intro").css({ 11 | width: winWidth, 12 | height: winHeight 13 | }); 14 | $(window).resize(function() { 15 | return $("#intro").css({ 16 | width: $(window).width(), 17 | height: $(window).height() 18 | }); 19 | }); 20 | if (/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent || navigator.vendor || window.opera)) { 21 | $('#intro').addClass('mobile'); 22 | } else { 23 | options = { 24 | forceHeight: false, 25 | smoothScrolling: false 26 | }; 27 | skrollr.init(options).refresh(); 28 | } 29 | return $(document).ready(function() { 30 | return $('#loading-overlay').fadeOut(800); 31 | }); 32 | }; -------------------------------------------------------------------------------- /client/views/home/home.less: -------------------------------------------------------------------------------- 1 | @import "/client/style/bootstrap-variables.less"; 2 | 3 | @home-intro-bg: url(img/background-intro.jpg); 4 | @home-colored-section-bg: @brand-primary; 5 | @home-colored-section-color: white; 6 | 7 | .home-layout 8 | { 9 | @media (max-width: 768px) 10 | { 11 | #ipad { 12 | h3 { 13 | padding-top: 0; 14 | } 15 | } 16 | } 17 | #intro 18 | { 19 | padding-top: 0; 20 | padding-bottom: 200px; 21 | background-color: black; 22 | h1 23 | { 24 | padding-top: 100px; 25 | } 26 | &.mobile 27 | { 28 | background: @home-intro-bg; 29 | background-size: cover; 30 | } 31 | } 32 | #how-it-works 33 | { 34 | .fa 35 | { 36 | margin-top: 60px; 37 | margin-bottom: 30px; 38 | } 39 | } 40 | #ipad 41 | { 42 | padding-bottom: 0 !important; 43 | overflow-x: hidden; 44 | h3 { 45 | padding-top: 100px; 46 | } 47 | } 48 | #final 49 | { 50 | padding-bottom: 0; 51 | .btn 52 | { 53 | margin-top: 20px; 54 | } 55 | } 56 | 57 | .navbar { 58 | background-color: transparent; 59 | border: 0; 60 | } 61 | #main-nav 62 | { 63 | border: none; 64 | box-shadow: none; 65 | } 66 | section { 67 | padding-top: 100px; 68 | padding-bottom: 100px; 69 | &.colored { 70 | background: @home-colored-section-bg; 71 | color: @home-colored-section-color; 72 | } 73 | } 74 | header { 75 | z-index: 1000; 76 | position: relative; 77 | width: 100%; 78 | padding-top: 50px; 79 | } 80 | .social-links { 81 | float: right; 82 | #home-login li 83 | { 84 | position: relative; 85 | bottom: 4px; 86 | } 87 | li 88 | { 89 | float: left; 90 | display: inline-block; 91 | padding-left: 10px; 92 | padding-top: 20px; 93 | } 94 | } 95 | .social-links a li { 96 | color: #ffffff; 97 | font-size: 18px; 98 | -webkit-transition: color 0.2s; 99 | -moz-transition: color 0.2s; 100 | transition: color 0.2s; 101 | } 102 | .social-links a li:hover { 103 | text-decoration: none; 104 | color: #717171; 105 | } 106 | #footer-home 107 | { 108 | margin-top: 50px; 109 | } 110 | } 111 | 112 | 113 | //Mobile 114 | @media (max-width: 768px) { 115 | .home-layout 116 | { 117 | #ipad 118 | { 119 | h3 120 | { 121 | padding-top:0; 122 | } 123 | .btn 124 | { 125 | margin-bottom: 100px; 126 | } 127 | } 128 | } 129 | 130 | } 131 | 132 | // Skrollr 133 | #skroll-background-intro 134 | { 135 | opacity: 0.7; 136 | 137 | &>div 138 | { 139 | background-image: @home-intro-bg; 140 | } 141 | } 142 | .parallax-image-wrapper { 143 | position:fixed; 144 | left:0; 145 | width:100%; 146 | overflow:hidden; 147 | } 148 | 149 | .parallax-image-wrapper-50 { 150 | height:50%; 151 | top:-50%; 152 | } 153 | 154 | .parallax-image-wrapper-100 { 155 | height:100%; 156 | top:-100%; 157 | } 158 | 159 | .parallax-image { 160 | display:none; 161 | position:absolute; 162 | bottom:0; 163 | left:0; 164 | width:100%; 165 | background-repeat:no-repeat; 166 | background-position:center; 167 | background-size:cover; 168 | } 169 | 170 | .parallax-image-50 { 171 | height:200%; 172 | top:-50%; 173 | } 174 | 175 | .parallax-image-100 { 176 | height:100%; 177 | top:0; 178 | } 179 | 180 | .parallax-image.skrollable-between { 181 | display:block; 182 | } 183 | 184 | .no-skrollr .parallax-image-wrapper { 185 | display:none !important; 186 | } 187 | 188 | #skrollr-body { 189 | height:100%; 190 | overflow:visible; 191 | position:relative; 192 | } 193 | 194 | .gap { 195 | background:transparent center no-repeat; 196 | background-size:cover; 197 | } 198 | 199 | .skrollr .gap { 200 | background:transparent !important; 201 | } 202 | 203 | .gap-50 { 204 | height:50%; 205 | } 206 | 207 | .gap-100 { 208 | height:100%; 209 | } 210 | 211 | .header, .content { 212 | background:#fff; 213 | padding:1em; 214 | 215 | -webkit-box-sizing:border-box; 216 | -moz-box-sizing:border-box; 217 | box-sizing:border-box; 218 | } 219 | 220 | .content-full { 221 | height:100%; 222 | } 223 | 224 | #done { 225 | height:100%; 226 | } -------------------------------------------------------------------------------- /client/views/profile/profile.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/views/profile/profile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by williamleong on 4/20/15. 3 | */ 4 | 5 | AutoForm.hooks({ 6 | updateProfile: { 7 | onSuccess: function(operation, result, template) { 8 | return Alert.success('Profile picture updated'); 9 | }, 10 | onError: function(operation, error, template) { 11 | return Alert.error('Profile picture updated'); 12 | } 13 | }, 14 | updatePicture: { 15 | onSuccess: function(operation, result, template) { 16 | return App.alertSuccess('Picture Updated'); 17 | }, 18 | onError: function(operation, error, template) { 19 | return App.alertError(error); 20 | } 21 | } 22 | }); -------------------------------------------------------------------------------- /packages/bootstrap/.gitignore: -------------------------------------------------------------------------------- 1 | .build* 2 | -------------------------------------------------------------------------------- /packages/bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # Bootstrap 3 easily customisable variables 2 | 3 | **The code has to be manually added to your packages folder. The core concept is [not supported by the packaging system](https://github.com/meteor/meteor/issues/2796).** 4 | 5 | ### Set up ### 6 | 1. `cd` to your `packages` folder 7 | 2. `git clone https://github.com/yogiben/meteor-bootstrap/edit/master/README.md yogiben:bootstrap` 8 | 3. `rm -R -f yogiben:bootstrap/.git` (so you can commit the code) 9 | 2. Choose a starting theme from [Bootswatch](http://bootswatch.com/) like [Flatly](http://bootswatch.com/flatly/) 10 | 3. Download the `variables.less` file into your client folder or a subfolder e.g. `/client/variables.less` 11 | 4. Create a less file in your client folder referencing the `variables.less` file 12 | 13 | e.g. `/client/bootstrap.less` 14 | ``` 15 | @import "/client/variables.less"; 16 | @import "/packages/yogiben:bootstrap/bootstrap.import.less"; 17 | ``` 18 | ### Fonts ### 19 | Copy the font import line from the `bootswatch.less` download from Bootswatch.com into your `bootstrap.less` file. 20 | 21 | ### Result ### 22 | A bootstrap that is completely in your control. 23 | -------------------------------------------------------------------------------- /packages/bootstrap/bootstrap.import.less: -------------------------------------------------------------------------------- 1 | // Defines where the package is 2 | @bootstrap3-less-root: "/packages/bootstrap"; 3 | 4 | // Import Bootstrap core variables and mixin 5 | // --------------------------------------- 6 | // (you need these first, to get the default setup for bootstrap) 7 | 8 | @import "@{bootstrap3-less-root}/lib/less/mixins.import.less"; 9 | 10 | // Application Overrides (config) 11 | // --------------------------------------- 12 | // (Here you can override any variables defined in the default set above) 13 | 14 | @icon-font-path: "@{bootstrap3-less-root}/lib/fonts/"; 15 | 16 | 17 | // Import Bootstrap 18 | // --------------------------------------- 19 | // (This is the full set of package files, include only what you need) 20 | 21 | // Reset and dependencies 22 | @import "@{bootstrap3-less-root}/lib/less/normalize.import.less"; 23 | @import "@{bootstrap3-less-root}/lib/less/print.import.less"; 24 | @import "@{bootstrap3-less-root}/lib/less/glyphicons.import.less"; 25 | 26 | // Core CSS 27 | @import "@{bootstrap3-less-root}/lib/less/scaffolding.import.less"; 28 | @import "@{bootstrap3-less-root}/lib/less/type.import.less"; 29 | @import "@{bootstrap3-less-root}/lib/less/code.import.less"; 30 | @import "@{bootstrap3-less-root}/lib/less/grid.import.less"; 31 | @import "@{bootstrap3-less-root}/lib/less/tables.import.less"; 32 | @import "@{bootstrap3-less-root}/lib/less/forms.import.less"; 33 | @import "@{bootstrap3-less-root}/lib/less/buttons.import.less"; 34 | 35 | // Components 36 | @import "@{bootstrap3-less-root}/lib/less/component-animations.import.less"; 37 | @import "@{bootstrap3-less-root}/lib/less/dropdowns.import.less"; 38 | @import "@{bootstrap3-less-root}/lib/less/button-groups.import.less"; 39 | @import "@{bootstrap3-less-root}/lib/less/input-groups.import.less"; 40 | @import "@{bootstrap3-less-root}/lib/less/navs.import.less"; 41 | @import "@{bootstrap3-less-root}/lib/less/navbar.import.less"; 42 | @import "@{bootstrap3-less-root}/lib/less/breadcrumbs.import.less"; 43 | @import "@{bootstrap3-less-root}/lib/less/pagination.import.less"; 44 | @import "@{bootstrap3-less-root}/lib/less/pager.import.less"; 45 | @import "@{bootstrap3-less-root}/lib/less/labels.import.less"; 46 | @import "@{bootstrap3-less-root}/lib/less/badges.import.less"; 47 | @import "@{bootstrap3-less-root}/lib/less/jumbotron.import.less"; 48 | @import "@{bootstrap3-less-root}/lib/less/thumbnails.import.less"; 49 | @import "@{bootstrap3-less-root}/lib/less/alerts.import.less"; 50 | @import "@{bootstrap3-less-root}/lib/less/progress-bars.import.less"; 51 | @import "@{bootstrap3-less-root}/lib/less/media.import.less"; 52 | @import "@{bootstrap3-less-root}/lib/less/list-group.import.less"; 53 | @import "@{bootstrap3-less-root}/lib/less/panels.import.less"; 54 | @import "@{bootstrap3-less-root}/lib/less/responsive-embed.import.less"; 55 | @import "@{bootstrap3-less-root}/lib/less/wells.import.less"; 56 | @import "@{bootstrap3-less-root}/lib/less/close.import.less"; 57 | 58 | // Components w/ JavaScript 59 | @import "@{bootstrap3-less-root}/lib/less/modals.import.less"; 60 | @import "@{bootstrap3-less-root}/lib/less/tooltip.import.less"; 61 | @import "@{bootstrap3-less-root}/lib/less/popovers.import.less"; 62 | @import "@{bootstrap3-less-root}/lib/less/carousel.import.less"; 63 | 64 | // Utility classes 65 | @import "@{bootstrap3-less-root}/lib/less/utilities.import.less"; 66 | @import "@{bootstrap3-less-root}/lib/less/responsive-utilities.import.less"; 67 | 68 | -------------------------------------------------------------------------------- /packages/bootstrap/lib/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willjleong/meteor-starter-pure-js/7cdec3b7260ae0870b76fc8093887ecb463d2309/packages/bootstrap/lib/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /packages/bootstrap/lib/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willjleong/meteor-starter-pure-js/7cdec3b7260ae0870b76fc8093887ecb463d2309/packages/bootstrap/lib/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /packages/bootstrap/lib/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willjleong/meteor-starter-pure-js/7cdec3b7260ae0870b76fc8093887ecb463d2309/packages/bootstrap/lib/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /packages/bootstrap/lib/js/affix.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: affix.js v3.2.0 3 | * http://getbootstrap.com/javascript/#affix 4 | * ======================================================================== 5 | * Copyright 2011-2014 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // AFFIX CLASS DEFINITION 14 | // ====================== 15 | 16 | var Affix = function (element, options) { 17 | this.options = $.extend({}, Affix.DEFAULTS, options) 18 | 19 | this.$target = $(this.options.target) 20 | .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) 21 | .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) 22 | 23 | this.$element = $(element) 24 | this.affixed = 25 | this.unpin = 26 | this.pinnedOffset = null 27 | 28 | this.checkPosition() 29 | } 30 | 31 | Affix.VERSION = '3.2.0' 32 | 33 | Affix.RESET = 'affix affix-top affix-bottom' 34 | 35 | Affix.DEFAULTS = { 36 | offset: 0, 37 | target: window 38 | } 39 | 40 | Affix.prototype.getPinnedOffset = function () { 41 | if (this.pinnedOffset) return this.pinnedOffset 42 | this.$element.removeClass(Affix.RESET).addClass('affix') 43 | var scrollTop = this.$target.scrollTop() 44 | var position = this.$element.offset() 45 | return (this.pinnedOffset = position.top - scrollTop) 46 | } 47 | 48 | Affix.prototype.checkPositionWithEventLoop = function () { 49 | setTimeout($.proxy(this.checkPosition, this), 1) 50 | } 51 | 52 | Affix.prototype.checkPosition = function () { 53 | if (!this.$element.is(':visible')) return 54 | 55 | var scrollHeight = $(document).height() 56 | var scrollTop = this.$target.scrollTop() 57 | var position = this.$element.offset() 58 | var offset = this.options.offset 59 | var offsetTop = offset.top 60 | var offsetBottom = offset.bottom 61 | 62 | if (typeof offset != 'object') offsetBottom = offsetTop = offset 63 | if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) 64 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) 65 | 66 | var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false : 67 | offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' : 68 | offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false 69 | 70 | if (this.affixed === affix) return 71 | if (this.unpin != null) this.$element.css('top', '') 72 | 73 | var affixType = 'affix' + (affix ? '-' + affix : '') 74 | var e = $.Event(affixType + '.bs.affix') 75 | 76 | this.$element.trigger(e) 77 | 78 | if (e.isDefaultPrevented()) return 79 | 80 | this.affixed = affix 81 | this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null 82 | 83 | this.$element 84 | .removeClass(Affix.RESET) 85 | .addClass(affixType) 86 | .trigger($.Event(affixType.replace('affix', 'affixed'))) 87 | 88 | if (affix == 'bottom') { 89 | this.$element.offset({ 90 | top: scrollHeight - this.$element.height() - offsetBottom 91 | }) 92 | } 93 | } 94 | 95 | 96 | // AFFIX PLUGIN DEFINITION 97 | // ======================= 98 | 99 | function Plugin(option) { 100 | return this.each(function () { 101 | var $this = $(this) 102 | var data = $this.data('bs.affix') 103 | var options = typeof option == 'object' && option 104 | 105 | if (!data) $this.data('bs.affix', (data = new Affix(this, options))) 106 | if (typeof option == 'string') data[option]() 107 | }) 108 | } 109 | 110 | var old = $.fn.affix 111 | 112 | $.fn.affix = Plugin 113 | $.fn.affix.Constructor = Affix 114 | 115 | 116 | // AFFIX NO CONFLICT 117 | // ================= 118 | 119 | $.fn.affix.noConflict = function () { 120 | $.fn.affix = old 121 | return this 122 | } 123 | 124 | 125 | // AFFIX DATA-API 126 | // ============== 127 | 128 | $(window).on('load', function () { 129 | $('[data-spy="affix"]').each(function () { 130 | var $spy = $(this) 131 | var data = $spy.data() 132 | 133 | data.offset = data.offset || {} 134 | 135 | if (data.offsetBottom) data.offset.bottom = data.offsetBottom 136 | if (data.offsetTop) data.offset.top = data.offsetTop 137 | 138 | Plugin.call($spy, data) 139 | }) 140 | }) 141 | 142 | }(jQuery); 143 | -------------------------------------------------------------------------------- /packages/bootstrap/lib/js/alert.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: alert.js v3.2.0 3 | * http://getbootstrap.com/javascript/#alerts 4 | * ======================================================================== 5 | * Copyright 2011-2014 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // ALERT CLASS DEFINITION 14 | // ====================== 15 | 16 | var dismiss = '[data-dismiss="alert"]' 17 | var Alert = function (el) { 18 | $(el).on('click', dismiss, this.close) 19 | } 20 | 21 | Alert.VERSION = '3.2.0' 22 | 23 | Alert.prototype.close = function (e) { 24 | var $this = $(this) 25 | var selector = $this.attr('data-target') 26 | 27 | if (!selector) { 28 | selector = $this.attr('href') 29 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 30 | } 31 | 32 | var $parent = $(selector) 33 | 34 | if (e) e.preventDefault() 35 | 36 | if (!$parent.length) { 37 | $parent = $this.hasClass('alert') ? $this : $this.parent() 38 | } 39 | 40 | $parent.trigger(e = $.Event('close.bs.alert')) 41 | 42 | if (e.isDefaultPrevented()) return 43 | 44 | $parent.removeClass('in') 45 | 46 | function removeElement() { 47 | // detach from parent, fire event then clean up data 48 | $parent.detach().trigger('closed.bs.alert').remove() 49 | } 50 | 51 | $.support.transition && $parent.hasClass('fade') ? 52 | $parent 53 | .one('bsTransitionEnd', removeElement) 54 | .emulateTransitionEnd(150) : 55 | removeElement() 56 | } 57 | 58 | 59 | // ALERT PLUGIN DEFINITION 60 | // ======================= 61 | 62 | function Plugin(option) { 63 | return this.each(function () { 64 | var $this = $(this) 65 | var data = $this.data('bs.alert') 66 | 67 | if (!data) $this.data('bs.alert', (data = new Alert(this))) 68 | if (typeof option == 'string') data[option].call($this) 69 | }) 70 | } 71 | 72 | var old = $.fn.alert 73 | 74 | $.fn.alert = Plugin 75 | $.fn.alert.Constructor = Alert 76 | 77 | 78 | // ALERT NO CONFLICT 79 | // ================= 80 | 81 | $.fn.alert.noConflict = function () { 82 | $.fn.alert = old 83 | return this 84 | } 85 | 86 | 87 | // ALERT DATA-API 88 | // ============== 89 | 90 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 91 | 92 | }(jQuery); 93 | -------------------------------------------------------------------------------- /packages/bootstrap/lib/js/button.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: button.js v3.2.0 3 | * http://getbootstrap.com/javascript/#buttons 4 | * ======================================================================== 5 | * Copyright 2011-2014 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // BUTTON PUBLIC CLASS DEFINITION 14 | // ============================== 15 | 16 | var Button = function (element, options) { 17 | this.$element = $(element) 18 | this.options = $.extend({}, Button.DEFAULTS, options) 19 | this.isLoading = false 20 | } 21 | 22 | Button.VERSION = '3.2.0' 23 | 24 | Button.DEFAULTS = { 25 | loadingText: 'loading...' 26 | } 27 | 28 | Button.prototype.setState = function (state) { 29 | var d = 'disabled' 30 | var $el = this.$element 31 | var val = $el.is('input') ? 'val' : 'html' 32 | var data = $el.data() 33 | 34 | state = state + 'Text' 35 | 36 | if (data.resetText == null) $el.data('resetText', $el[val]()) 37 | 38 | $el[val](data[state] == null ? this.options[state] : data[state]) 39 | 40 | // push to event loop to allow forms to submit 41 | setTimeout($.proxy(function () { 42 | if (state == 'loadingText') { 43 | this.isLoading = true 44 | $el.addClass(d).attr(d, d) 45 | } else if (this.isLoading) { 46 | this.isLoading = false 47 | $el.removeClass(d).removeAttr(d) 48 | } 49 | }, this), 0) 50 | } 51 | 52 | Button.prototype.toggle = function () { 53 | var changed = true 54 | var $parent = this.$element.closest('[data-toggle="buttons"]') 55 | 56 | if ($parent.length) { 57 | var $input = this.$element.find('input') 58 | if ($input.prop('type') == 'radio') { 59 | if ($input.prop('checked') && this.$element.hasClass('active')) changed = false 60 | else $parent.find('.active').removeClass('active') 61 | } 62 | if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') 63 | } 64 | 65 | if (changed) this.$element.toggleClass('active') 66 | } 67 | 68 | 69 | // BUTTON PLUGIN DEFINITION 70 | // ======================== 71 | 72 | function Plugin(option) { 73 | return this.each(function () { 74 | var $this = $(this) 75 | var data = $this.data('bs.button') 76 | var options = typeof option == 'object' && option 77 | 78 | if (!data) $this.data('bs.button', (data = new Button(this, options))) 79 | 80 | if (option == 'toggle') data.toggle() 81 | else if (option) data.setState(option) 82 | }) 83 | } 84 | 85 | var old = $.fn.button 86 | 87 | $.fn.button = Plugin 88 | $.fn.button.Constructor = Button 89 | 90 | 91 | // BUTTON NO CONFLICT 92 | // ================== 93 | 94 | $.fn.button.noConflict = function () { 95 | $.fn.button = old 96 | return this 97 | } 98 | 99 | 100 | // BUTTON DATA-API 101 | // =============== 102 | 103 | $(document).on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 104 | var $btn = $(e.target) 105 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 106 | Plugin.call($btn, 'toggle') 107 | e.preventDefault() 108 | }) 109 | 110 | }(jQuery); 111 | -------------------------------------------------------------------------------- /packages/bootstrap/lib/js/collapse.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: collapse.js v3.2.0 3 | * http://getbootstrap.com/javascript/#collapse 4 | * ======================================================================== 5 | * Copyright 2011-2014 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // COLLAPSE PUBLIC CLASS DEFINITION 14 | // ================================ 15 | 16 | var Collapse = function (element, options) { 17 | this.$element = $(element) 18 | this.options = $.extend({}, Collapse.DEFAULTS, options) 19 | this.transitioning = null 20 | 21 | if (this.options.parent) this.$parent = $(this.options.parent) 22 | if (this.options.toggle) this.toggle() 23 | } 24 | 25 | Collapse.VERSION = '3.2.0' 26 | 27 | Collapse.DEFAULTS = { 28 | toggle: true 29 | } 30 | 31 | Collapse.prototype.dimension = function () { 32 | var hasWidth = this.$element.hasClass('width') 33 | return hasWidth ? 'width' : 'height' 34 | } 35 | 36 | Collapse.prototype.show = function () { 37 | if (this.transitioning || this.$element.hasClass('in')) return 38 | 39 | var startEvent = $.Event('show.bs.collapse') 40 | this.$element.trigger(startEvent) 41 | if (startEvent.isDefaultPrevented()) return 42 | 43 | var actives = this.$parent && this.$parent.find('> .panel > .in') 44 | 45 | if (actives && actives.length) { 46 | var hasData = actives.data('bs.collapse') 47 | if (hasData && hasData.transitioning) return 48 | Plugin.call(actives, 'hide') 49 | hasData || actives.data('bs.collapse', null) 50 | } 51 | 52 | var dimension = this.dimension() 53 | 54 | this.$element 55 | .removeClass('collapse') 56 | .addClass('collapsing')[dimension](0) 57 | 58 | this.transitioning = 1 59 | 60 | var complete = function () { 61 | this.$element 62 | .removeClass('collapsing') 63 | .addClass('collapse in')[dimension]('') 64 | this.transitioning = 0 65 | this.$element 66 | .trigger('shown.bs.collapse') 67 | } 68 | 69 | if (!$.support.transition) return complete.call(this) 70 | 71 | var scrollSize = $.camelCase(['scroll', dimension].join('-')) 72 | 73 | this.$element 74 | .one('bsTransitionEnd', $.proxy(complete, this)) 75 | .emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize]) 76 | } 77 | 78 | Collapse.prototype.hide = function () { 79 | if (this.transitioning || !this.$element.hasClass('in')) return 80 | 81 | var startEvent = $.Event('hide.bs.collapse') 82 | this.$element.trigger(startEvent) 83 | if (startEvent.isDefaultPrevented()) return 84 | 85 | var dimension = this.dimension() 86 | 87 | this.$element[dimension](this.$element[dimension]())[0].offsetHeight 88 | 89 | this.$element 90 | .addClass('collapsing') 91 | .removeClass('collapse') 92 | .removeClass('in') 93 | 94 | this.transitioning = 1 95 | 96 | var complete = function () { 97 | this.transitioning = 0 98 | this.$element 99 | .trigger('hidden.bs.collapse') 100 | .removeClass('collapsing') 101 | .addClass('collapse') 102 | } 103 | 104 | if (!$.support.transition) return complete.call(this) 105 | 106 | this.$element 107 | [dimension](0) 108 | .one('bsTransitionEnd', $.proxy(complete, this)) 109 | .emulateTransitionEnd(350) 110 | } 111 | 112 | Collapse.prototype.toggle = function () { 113 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 114 | } 115 | 116 | 117 | // COLLAPSE PLUGIN DEFINITION 118 | // ========================== 119 | 120 | function Plugin(option) { 121 | return this.each(function () { 122 | var $this = $(this) 123 | var data = $this.data('bs.collapse') 124 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 125 | 126 | if (!data && options.toggle && option == 'show') option = !option 127 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 128 | if (typeof option == 'string') data[option]() 129 | }) 130 | } 131 | 132 | var old = $.fn.collapse 133 | 134 | $.fn.collapse = Plugin 135 | $.fn.collapse.Constructor = Collapse 136 | 137 | 138 | // COLLAPSE NO CONFLICT 139 | // ==================== 140 | 141 | $.fn.collapse.noConflict = function () { 142 | $.fn.collapse = old 143 | return this 144 | } 145 | 146 | 147 | // COLLAPSE DATA-API 148 | // ================= 149 | 150 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 151 | var href 152 | var $this = $(this) 153 | var target = $this.attr('data-target') 154 | || e.preventDefault() 155 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 156 | var $target = $(target) 157 | var data = $target.data('bs.collapse') 158 | var option = data ? 'toggle' : $this.data() 159 | var parent = $this.attr('data-parent') 160 | var $parent = parent && $(parent) 161 | 162 | if (!data || !data.transitioning) { 163 | if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed') 164 | $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') 165 | } 166 | 167 | Plugin.call($target, option) 168 | }) 169 | 170 | }(jQuery); 171 | -------------------------------------------------------------------------------- /packages/bootstrap/lib/js/dropdown.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: dropdown.js v3.2.0 3 | * http://getbootstrap.com/javascript/#dropdowns 4 | * ======================================================================== 5 | * Copyright 2011-2014 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // DROPDOWN CLASS DEFINITION 14 | // ========================= 15 | 16 | var backdrop = '.dropdown-backdrop' 17 | var toggle = '[data-toggle="dropdown"]' 18 | var Dropdown = function (element) { 19 | $(element).on('click.bs.dropdown', this.toggle) 20 | } 21 | 22 | Dropdown.VERSION = '3.2.0' 23 | 24 | Dropdown.prototype.toggle = function (e) { 25 | var $this = $(this) 26 | 27 | if ($this.is('.disabled, :disabled')) return 28 | 29 | var $parent = getParent($this) 30 | var isActive = $parent.hasClass('open') 31 | 32 | clearMenus() 33 | 34 | if (!isActive) { 35 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 36 | // if mobile we use a backdrop because click events don't delegate 37 | $('