diff --git a/History.md b/History.md index e8eee99985..b436392698 100644 --- a/History.md +++ b/History.md @@ -1,39 +1,65 @@ -## batch-plugins - -Backwards-incompatible change: static assets in package.js files must -be explicitly declared with `{isAsset: true}`. See the batch-plugins merge -message for more details. - - ## v.NEXT +## in progress: v1.2 + +There are quite a lot of changes in Meteor 1.2. See the +[Wiki](https://github.com/meteor/meteor/wiki/Breaking-changes-in-Meteor-1.2) for +a shorter list of breaking changes you should be aware of when upgrading. + +### Core Packages + +* `meteor-platform` has been deprecated in favor of the smaller `meteor-base`, + with apps listing their other dependencies explicitly. The v1.2 upgrader + will rewrite `meteor-platform` in existing apps. `meteor-base` puts fewer + symbols in the global namepsace, so it's no longer true that all apps + have symbols like `Random` and `EJSON` in the global namespace. + +* New packages: `ecmascript`, `es5-shim`, `ecmascript-collections`, `promise`, + `static-html`, `jshint`, `babel-compiler` + * No longer include the `json` package by default, which contains code for `JSON.parse` and `JSON.stringify`. (The last browser to not support JSON natively was Internet Explorer 7.) -### Utilities - -* New `beforeSend` option to `HTTP.call` on the client allows you to directly - access the `XMLHttpRequest` object and abort the call. #4419 #3243 #3266 - -* Parse `application/javascript` and `application/x-javascript` HTTP replies as - JSON too. #4595 - +* `autoupdate` has been renamed `hot-code-push` ### Meteor Accounts -* `loginWithPassword` now matches username or email in a case insensitive manner. If there are multiple users with a username or email only differing in case, a case sensitive match is required. #550 +* Login attempts are now rate-limited by default. This can be turned off + using `Accounts.removeDefaultRateLimit()`. -* `loginWithGithub` now requests `user:email` scope by default, and attempts to fetch the user's emails. If no public email has been set, we use the primary email instead. We also store the complete list of emails. #4545 +* `loginWithPassword` now matches username or email in a case insensitive + manner. If there are multiple users with a username or email only differing + in case, a case sensitive match is required. #550 + +* `loginWithGithub` now requests `user:email` scope by default, and attempts + to fetch the user's emails. If no public email has been set, we use the + primary email instead. We also store the complete list of emails. #4545 + +* When an account's email address is verified, deactivate other verification + tokens. #4626 + +* Fix bug where blank page is shown when an expired login token is + present. #4825 * Fix `OAuth1Binding.prototype.call` when making requests to Twitter with a large parameter set. +* Directions for setting up Google OAuth in accounts-ui have been updated to + match Google's new requirements. -### DDP +* Add `Accounts.oauth.unregisterService` method, and ensure that users can only + log in with currently registered services. #4014 -* `sub.ready()` should return true inside that subscription's `onReady` - callback. #4614 +* The `accounts-base` now defines reusable `AccountsClient` and + `AccountsServer` constructors, so that users can create multiple + independent instances of the `Accounts` namespace. #4233 + +* Create an index for `Meteor.users` on + `services.email.verificationTokens.token` (instead of + `emails.validationTokens.token`, which never was used for anything). #4482 + +* Remove an IE7-specific workaround from accounts-ui. #4485 ### Livequery @@ -95,15 +121,17 @@ message for more details. iOS Simulator. As a workaround, you can `meteor run ios-device` to open the project in Xcode and watch the output there. -## in progress: v.1.1.1 +### Templates/Blaze -### Blaze +* New syntax: Handlebars sub-expressions are now supported -- as in, + `{{helper (anotherHelper arg1 arg2)}}` -- as well as new block helper forms + `#each .. in ..` and `#let x=y`. See + https://github.com/meteor/meteor/tree/devel/packages/spacebars -* Preparatory work for the yet-unreleased `react-template-helper` - package -- don't let templates use {{> React}} with siblings since - `React.render` assumes it's being rendered into an empty container - element. (This lets us throw the error when compiling templates - rather than when the app runs.) +* Add a special case for the new `react-template-helper` package -- don't let + templates use {{> React}} with siblings since `React.render` assumes it's + being rendered into an empty container element. (This lets us throw the error + when compiling templates rather than when the app runs.) * Improve parsing of ` + + + + + diff --git a/docs/client/names.json b/docs/client/names.json index f7d426ca7d..74f5dfab5f 100644 --- a/docs/client/names.json +++ b/docs/client/names.json @@ -1,18 +1,23 @@ [ "Accounts", "Accounts", + "Accounts.addEmail", "Accounts.changePassword", "Accounts.createUser", "Accounts.emailTemplates", + "Accounts.findUserByEmail", + "Accounts.findUserByUsername", "Accounts.forgotPassword", "Accounts.onEmailVerificationLink", "Accounts.onEnrollmentLink", "Accounts.onResetPasswordLink", + "Accounts.removeEmail", "Accounts.resetPassword", "Accounts.sendEnrollmentEmail", "Accounts.sendResetPasswordEmail", "Accounts.sendVerificationEmail", "Accounts.setPassword", + "Accounts.setUsername", "Accounts.ui", "Accounts.ui.config", "Accounts.verifyEmail", @@ -210,6 +215,7 @@ "Package.onUse", "Package.registerBuildPlugin", "PackageAPI", + "PackageAPI#addAssets", "PackageAPI#addFiles", "PackageAPI#export", "PackageAPI#imply", @@ -275,6 +281,8 @@ "Tracker.onInvalidate", "check", "currentUser", + "execFileAsync", + "execFileSync", "getExtension", "loggingIn" ] \ No newline at end of file diff --git a/examples/clock/.meteor/.finished-upgraders b/examples/clock/.meteor/.finished-upgraders index 8a761038c5..61ee313230 100644 --- a/examples/clock/.meteor/.finished-upgraders +++ b/examples/clock/.meteor/.finished-upgraders @@ -6,3 +6,7 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/clock/.meteor/release b/examples/clock/.meteor/release index 315c635bf3..712ef798f5 100644 --- a/examples/clock/.meteor/release +++ b/examples/clock/.meteor/release @@ -1 +1 @@ -METEOR@1.1.0.3 +METEOR@1.2 diff --git a/examples/clock/.meteor/versions b/examples/clock/.meteor/versions index 410e1d9e93..bb7a176293 100644 --- a/examples/clock/.meteor/versions +++ b/examples/clock/.meteor/versions @@ -1,48 +1,59 @@ -autopublish@1.0.3 -autoupdate@1.2.1 -base64@1.0.3 -binary-heap@1.0.3 -blaze@2.1.2 -blaze-tools@1.0.3 -boilerplate-generator@1.0.3 -callback-hook@1.0.3 -check@1.0.5 -ddp@1.1.0 -deps@1.0.7 -ejson@1.0.6 -fastclick@1.0.3 -geojson-utils@1.0.3 -html-tools@1.0.4 -htmljs@1.0.4 -http@1.1.0 -id-map@1.0.3 -insecure@1.0.3 -jquery@1.11.3_2 -json@1.0.3 -launch-screen@1.0.2 -livedata@1.0.13 -logging@1.0.7 -meteor@1.1.6 -meteor-platform@1.2.2 -minifiers@1.1.5 -minimongo@1.0.8 -mobile-status-bar@1.0.3 -mongo@1.1.0 -observe-sequence@1.0.6 -ordered-dict@1.0.3 -random@1.0.3 -reactive-dict@1.1.0 -reactive-var@1.0.5 -reload@1.1.3 -retry@1.0.3 -routepolicy@1.0.5 -session@1.1.0 -spacebars@1.0.6 -spacebars-compiler@1.0.6 -templating@1.1.1 -tracker@1.0.7 -ui@1.0.6 -underscore@1.0.3 -url@1.0.4 -webapp@1.2.0 -webapp-hashing@1.0.3 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +jquery@1.11.4 +livedata@1.0.14 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mongo@1.1.1 +mongo-id@1.0.1 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +session@1.1.1 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/examples/leaderboard/.meteor/.finished-upgraders b/examples/leaderboard/.meteor/.finished-upgraders index 97b81c4a60..ea6003a014 100644 --- a/examples/leaderboard/.meteor/.finished-upgraders +++ b/examples/leaderboard/.meteor/.finished-upgraders @@ -8,3 +8,7 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/leaderboard/.meteor/cordova-plugins b/examples/leaderboard/.meteor/cordova-plugins index 8b13789179..e69de29bb2 100644 --- a/examples/leaderboard/.meteor/cordova-plugins +++ b/examples/leaderboard/.meteor/cordova-plugins @@ -1 +0,0 @@ - diff --git a/examples/leaderboard/.meteor/release b/examples/leaderboard/.meteor/release index 315c635bf3..712ef798f5 100644 --- a/examples/leaderboard/.meteor/release +++ b/examples/leaderboard/.meteor/release @@ -1 +1 @@ -METEOR@1.1.0.3 +METEOR@1.2 diff --git a/examples/leaderboard/.meteor/versions b/examples/leaderboard/.meteor/versions index 410e1d9e93..b53231e255 100644 --- a/examples/leaderboard/.meteor/versions +++ b/examples/leaderboard/.meteor/versions @@ -1,48 +1,61 @@ -autopublish@1.0.3 -autoupdate@1.2.1 -base64@1.0.3 -binary-heap@1.0.3 -blaze@2.1.2 -blaze-tools@1.0.3 -boilerplate-generator@1.0.3 -callback-hook@1.0.3 -check@1.0.5 -ddp@1.1.0 -deps@1.0.7 -ejson@1.0.6 -fastclick@1.0.3 -geojson-utils@1.0.3 -html-tools@1.0.4 -htmljs@1.0.4 -http@1.1.0 -id-map@1.0.3 -insecure@1.0.3 -jquery@1.11.3_2 -json@1.0.3 -launch-screen@1.0.2 -livedata@1.0.13 -logging@1.0.7 -meteor@1.1.6 -meteor-platform@1.2.2 -minifiers@1.1.5 -minimongo@1.0.8 -mobile-status-bar@1.0.3 -mongo@1.1.0 -observe-sequence@1.0.6 -ordered-dict@1.0.3 -random@1.0.3 -reactive-dict@1.1.0 -reactive-var@1.0.5 -reload@1.1.3 -retry@1.0.3 -routepolicy@1.0.5 -session@1.1.0 -spacebars@1.0.6 -spacebars-compiler@1.0.6 -templating@1.1.1 -tracker@1.0.7 -ui@1.0.6 -underscore@1.0.3 -url@1.0.4 -webapp@1.2.0 -webapp-hashing@1.0.3 +autopublish@1.0.4 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +insecure@1.0.4 +jquery@1.11.4 +livedata@1.0.14 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mongo@1.1.1 +mongo-id@1.0.1 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +session@1.1.1 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/examples/localmarket/.meteor/.finished-upgraders b/examples/localmarket/.meteor/.finished-upgraders index 8a761038c5..61ee313230 100644 --- a/examples/localmarket/.meteor/.finished-upgraders +++ b/examples/localmarket/.meteor/.finished-upgraders @@ -6,3 +6,7 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/localmarket/.meteor/packages b/examples/localmarket/.meteor/packages index 95d6fa41e3..2889a2f574 100644 --- a/examples/localmarket/.meteor/packages +++ b/examples/localmarket/.meteor/packages @@ -5,7 +5,6 @@ # but you can also edit it by hand. # Packages from the release: -meteor-platform standard-minifiers less email @@ -24,3 +23,16 @@ percolate:velocityjs percolate:wireframing mdg:camera mdg:geolocation +meteor-base +mobile-experience +mongo +blaze-html-templates +session +jquery +tracker +logging +reload +random +ejson +spacebars +check diff --git a/examples/localmarket/.meteor/release b/examples/localmarket/.meteor/release index 315c635bf3..712ef798f5 100644 --- a/examples/localmarket/.meteor/release +++ b/examples/localmarket/.meteor/release @@ -1 +1 @@ -METEOR@1.1.0.3 +METEOR@1.2 diff --git a/examples/localmarket/.meteor/versions b/examples/localmarket/.meteor/versions index 7d92b0f04c..bafc271fa3 100644 --- a/examples/localmarket/.meteor/versions +++ b/examples/localmarket/.meteor/versions @@ -1,67 +1,86 @@ -accounts-base@1.2.0 -accounts-oauth@1.1.5 -accounts-twitter@1.0.4 -autoupdate@1.2.1 -base64@1.0.3 -binary-heap@1.0.3 -blaze@2.1.2 -blaze-tools@1.0.3 -boilerplate-generator@1.0.3 -callback-hook@1.0.3 -check@1.0.5 -coffeescript@1.0.6 -ddp@1.1.0 -deps@1.0.7 -ejson@1.0.6 -email@1.0.6 -fastclick@1.0.3 -geojson-utils@1.0.3 -html-tools@1.0.4 -htmljs@1.0.4 -http@1.1.0 -id-map@1.0.3 +accounts-base@1.2.1 +accounts-oauth@1.1.6 +accounts-twitter@1.0.5 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +coffeescript@1.0.8 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-rate-limiter@1.0.0 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +email@1.0.7 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 iron:core@0.3.4 iron:dynamic-template@0.4.1 iron:layout@0.4.1 iron:router@0.9.4 -jquery@1.11.3_2 -jquery-waypoints@1.0.3 -json@1.0.3 -launch-screen@1.0.2 -less@1.0.14 -livedata@1.0.13 -localstorage@1.0.3 -logging@1.0.7 -mdg:camera@1.1.4 -mdg:geolocation@1.0.2 -meteor@1.1.6 -meteor-platform@1.2.2 -minifiers@1.1.5 -minimongo@1.0.8 -mobile-status-bar@1.0.3 -mongo@1.1.0 -oauth@1.1.4 -oauth1@1.1.4 -observe-sequence@1.0.6 -ordered-dict@1.0.3 +jquery@1.11.4 +jquery-waypoints@1.0.4 +launch-screen@1.0.3 +less@2.5.0_1 +livedata@1.0.14 +localstorage@1.0.4 +logging@1.0.8 +mdg:camera@1.2.0 +mdg:geolocation@1.1.0 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.1 +mongo-id@1.0.1 +npm-mongo@1.4.39_1 +oauth@1.1.6 +oauth1@1.1.5 +observe-sequence@1.0.7 +ordered-dict@1.0.4 percolate:velocityjs@1.2.1_1 percolate:wireframing@0.2.0 -random@1.0.3 -reactive-dict@1.1.0 -reactive-var@1.0.5 -reload@1.1.3 -retry@1.0.3 -routepolicy@1.0.5 -service-configuration@1.0.4 -session@1.1.0 -spacebars@1.0.6 -spacebars-compiler@1.0.6 -templating@1.1.1 -tmeasday:publish-counts@0.3.4 -tracker@1.0.7 -twitter@1.1.4 -ui@1.0.6 -underscore@1.0.3 -url@1.0.4 -webapp@1.2.0 -webapp-hashing@1.0.3 +promise@0.4.8 +random@1.0.4 +rate-limit@1.0.0 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +service-configuration@1.0.5 +session@1.1.1 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tmeasday:publish-counts@0.7.2 +tracker@1.0.8 +twitter@1.1.5 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/tools/tests/apps/lint-on-publish/.meteor/.finished-upgraders b/examples/simple-todos-angular/.meteor/.finished-upgraders similarity index 80% rename from tools/tests/apps/lint-on-publish/.meteor/.finished-upgraders rename to examples/simple-todos-angular/.meteor/.finished-upgraders index a806d4c88c..61ee313230 100644 --- a/tools/tests/apps/lint-on-publish/.meteor/.finished-upgraders +++ b/examples/simple-todos-angular/.meteor/.finished-upgraders @@ -7,3 +7,6 @@ notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/simple-todos-angular/.meteor/.gitignore b/examples/simple-todos-angular/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/simple-todos-angular/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/simple-todos-angular/.meteor/packages b/examples/simple-todos-angular/.meteor/packages new file mode 100644 index 0000000000..3ad42dd1b6 --- /dev/null +++ b/examples/simple-todos-angular/.meteor/packages @@ -0,0 +1,23 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +accounts-ui +accounts-password +standard-minifiers +meteor-base +mobile-experience +mongo +blaze-html-templates +session +jquery +tracker +logging +reload +random +ejson +spacebars +check +angular diff --git a/examples/simple-todos-angular/.meteor/platforms b/examples/simple-todos-angular/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/examples/simple-todos-angular/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/examples/simple-todos-angular/.meteor/release b/examples/simple-todos-angular/.meteor/release new file mode 100644 index 0000000000..712ef798f5 --- /dev/null +++ b/examples/simple-todos-angular/.meteor/release @@ -0,0 +1 @@ +METEOR@1.2 diff --git a/examples/simple-todos-angular/.meteor/versions b/examples/simple-todos-angular/.meteor/versions new file mode 100644 index 0000000000..2e5f529bbd --- /dev/null +++ b/examples/simple-todos-angular/.meteor/versions @@ -0,0 +1,80 @@ +accounts-base@1.2.1 +accounts-password@1.1.2 +accounts-ui@1.1.6 +accounts-ui-unstyled@1.1.8 +angular@1.0.0-rc.6 +angular:angular@1.4.6 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +dburles:mongo-collection-instances@0.3.4 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-rate-limiter@1.0.0 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +email@1.0.7 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +jquery@1.11.4 +lai:collection-extensions@0.1.4 +launch-screen@1.0.3 +less@2.5.0_1 +livedata@1.0.14 +localstorage@1.0.4 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.1 +mongo-id@1.0.1 +npm-bcrypt@0.7.8_2 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +rate-limit@1.0.0 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +service-configuration@1.0.5 +session@1.1.1 +sha@1.0.4 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +srp@1.0.4 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/examples/simple-todos-angular/README.md b/examples/simple-todos-angular/README.md new file mode 100644 index 0000000000..258e5f8975 --- /dev/null +++ b/examples/simple-todos-angular/README.md @@ -0,0 +1,9 @@ +# Simple Todo List + +The Meteor Tutorial app, angular-meteor version. + +Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. + +Learn how to build this app by following the [Meteor Tutorial - Angular version](https://www.meteor.com/tutorials/angular/creating-an-app). + +![screenshot](https://github.com/meteor/simple-todos/blob/master/screenshot.png) diff --git a/examples/simple-todos-angular/simple-todos-angular.css b/examples/simple-todos-angular/simple-todos-angular.css new file mode 100644 index 0000000000..0bca5d2ada --- /dev/null +++ b/examples/simple-todos-angular/simple-todos-angular.css @@ -0,0 +1,127 @@ +/* CSS declarations go here */ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} + diff --git a/examples/simple-todos-angular/simple-todos-angular.html b/examples/simple-todos-angular/simple-todos-angular.html new file mode 100644 index 0000000000..e43eceb5c9 --- /dev/null +++ b/examples/simple-todos-angular/simple-todos-angular.html @@ -0,0 +1,7 @@ + + Todo List + + + + diff --git a/examples/simple-todos-angular/simple-todos-angular.js b/examples/simple-todos-angular/simple-todos-angular.js new file mode 100644 index 0000000000..1d371fb3ca --- /dev/null +++ b/examples/simple-todos-angular/simple-todos-angular.js @@ -0,0 +1,113 @@ +Tasks = new Mongo.Collection('tasks'); + +if (Meteor.isClient) { + + Accounts.ui.config({ + passwordSignupFields: "USERNAME_ONLY" + }); + + // This code only runs on the client + angular.module('simple-todos',['angular-meteor']); + + function onReady() { + angular.bootstrap(document, ['simple-todos']); + } + + if (Meteor.isCordova) + angular.element(document).on('deviceready', onReady); + else + angular.element(document).ready(onReady); + + angular.module('simple-todos').controller('TodosListCtrl', ['$scope', '$meteor', + function ($scope, $meteor) { + + $scope.$meteorSubscribe('tasks'); + + $scope.tasks = $meteor.collection(function() { + return Tasks.find($scope.getReactively('query'), {sort: {createdAt: -1}}) + }); + + $scope.addTask = function (newTask) { + $meteor.call('addTask', newTask); + }; + + $scope.deleteTask = function (task) { + $meteor.call('deleteTask', task._id); + }; + + $scope.setChecked = function (task) { + $meteor.call('setChecked', task._id, !task.checked); + }; + + $scope.setPrivate = function (task) { + $meteor.call('setPrivate', task._id, ! task.private); + }; + + $scope.$watch('hideCompleted', function() { + if ($scope.hideCompleted) + $scope.query = {checked: {$ne: true}}; + else + $scope.query = {}; + }); + + $scope.incompleteCount = function () { + return Tasks.find({ checked: {$ne: true} }).count(); + }; + + }]); +} + +Meteor.methods({ + addTask: function (text) { + // Make sure the user is logged in before inserting a task + if (! Meteor.userId()) { + throw new Meteor.Error('not-authorized'); + } + + Tasks.insert({ + text: text, + createdAt: new Date(), + owner: Meteor.userId(), + username: Meteor.user().username + }); + }, + deleteTask: function (taskId) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can delete it + throw new Meteor.Error('not-authorized'); + } + + Tasks.remove(taskId); + }, + setChecked: function (taskId, setChecked) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can check it off + throw new Meteor.Error('not-authorized'); + } + + Tasks.update(taskId, { $set: { checked: setChecked} }); + }, + setPrivate: function (taskId, setToPrivate) { + var task = Tasks.findOne(taskId); + + // Make sure only the task owner can make a task private + if (task.owner !== Meteor.userId()) { + throw new Meteor.Error('not-authorized'); + } + + Tasks.update(taskId, { $set: { private: setToPrivate } }); + } +}); + +if (Meteor.isServer) { + Meteor.publish('tasks', function () { + return Tasks.find({ + $or: [ + { private: {$ne: true} }, + { owner: this.userId } + ] + }); + }); +} diff --git a/examples/simple-todos-angular/todos-list.ng.html b/examples/simple-todos-angular/todos-list.ng.html new file mode 100644 index 0000000000..1799364849 --- /dev/null +++ b/examples/simple-todos-angular/todos-list.ng.html @@ -0,0 +1,39 @@ +
+
+

Todo List ( {{ incompleteCount() }} )

+ + + + + + +
+ +
+
+ + +
diff --git a/tools/tests/apps/custom-minifier/.meteor/.finished-upgraders b/examples/simple-todos-react/.meteor/.finished-upgraders similarity index 80% rename from tools/tests/apps/custom-minifier/.meteor/.finished-upgraders rename to examples/simple-todos-react/.meteor/.finished-upgraders index a806d4c88c..61ee313230 100644 --- a/tools/tests/apps/custom-minifier/.meteor/.finished-upgraders +++ b/examples/simple-todos-react/.meteor/.finished-upgraders @@ -7,3 +7,6 @@ notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/simple-todos-react/.meteor/.gitignore b/examples/simple-todos-react/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/simple-todos-react/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/simple-todos-react/.meteor/packages b/examples/simple-todos-react/.meteor/packages new file mode 100644 index 0000000000..39114be330 --- /dev/null +++ b/examples/simple-todos-react/.meteor/packages @@ -0,0 +1,23 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +react +accounts-ui +accounts-password +standard-minifiers +meteor-base +mobile-experience +mongo +blaze-html-templates +session +jquery +tracker +logging +reload +random +ejson +spacebars +check diff --git a/examples/simple-todos-react/.meteor/platforms b/examples/simple-todos-react/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/examples/simple-todos-react/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/examples/simple-todos-react/.meteor/release b/examples/simple-todos-react/.meteor/release new file mode 100644 index 0000000000..712ef798f5 --- /dev/null +++ b/examples/simple-todos-react/.meteor/release @@ -0,0 +1 @@ +METEOR@1.2 diff --git a/examples/simple-todos-react/.meteor/versions b/examples/simple-todos-react/.meteor/versions new file mode 100644 index 0000000000..ba31aa75e7 --- /dev/null +++ b/examples/simple-todos-react/.meteor/versions @@ -0,0 +1,84 @@ +accounts-base@1.2.1 +accounts-password@1.1.2 +accounts-ui@1.1.6 +accounts-ui-unstyled@1.1.8 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +coffeescript@1.0.8 +cosmos:browserify@0.5.1 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-rate-limiter@1.0.0 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +email@1.0.7 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +jquery@1.11.4 +jsx@0.2.1 +launch-screen@1.0.3 +less@2.5.0_1 +livedata@1.0.14 +localstorage@1.0.4 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.1 +mongo-id@1.0.1 +npm-bcrypt@0.7.8_2 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +rate-limit@1.0.0 +react@0.1.13 +react-meteor-data@0.1.9 +react-runtime@0.13.3_7 +react-runtime-dev@0.13.3_7 +react-runtime-prod@0.13.3_6 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +service-configuration@1.0.5 +session@1.1.1 +sha@1.0.4 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +srp@1.0.4 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/examples/simple-todos-react/AccountsUIWrapper.jsx b/examples/simple-todos-react/AccountsUIWrapper.jsx new file mode 100644 index 0000000000..87fd3b3ea8 --- /dev/null +++ b/examples/simple-todos-react/AccountsUIWrapper.jsx @@ -0,0 +1,15 @@ +AccountsUIWrapper = React.createClass({ + componentDidMount() { + // Use Meteor Blaze to render login buttons + this.view = Blaze.render(Template.loginButtons, + React.findDOMNode(this.refs.container)); + }, + componentWillUnmount() { + // Clean up Blaze view + Blaze.remove(this.view); + }, + render() { + // Just render a placeholder container that will be filled in + return ; + } +}); diff --git a/examples/simple-todos-react/App.jsx b/examples/simple-todos-react/App.jsx new file mode 100644 index 0000000000..82915ec4f5 --- /dev/null +++ b/examples/simple-todos-react/App.jsx @@ -0,0 +1,93 @@ +// App component - represents the whole app +App = React.createClass({ + + // This mixin makes the getMeteorData method work + mixins: [ReactMeteorData], + + getInitialState() { + return { + hideCompleted: false + } + }, + + // Loads items from the Tasks collection and puts them on this.data.tasks + getMeteorData() { + let query = {}; + + if (this.state.hideCompleted) { + // If hide completed is checked, filter tasks + query = {checked: {$ne: true}}; + } + + return { + tasks: Tasks.find(query, {sort: {createdAt: -1}}).fetch(), + incompleteCount: Tasks.find({checked: {$ne: true}}).count(), + currentUser: Meteor.user() + }; + }, + + renderTasks() { + // Get tasks from this.data.tasks + return this.data.tasks.map((task) => { + const currentUserId = this.data.currentUser && this.data.currentUser._id; + const showPrivateButton = task.owner === currentUserId; + + return ; + }); + }, + + handleSubmit(event) { + event.preventDefault(); + + // Find the text field via the React ref + var text = React.findDOMNode(this.refs.textInput).value.trim(); + + Meteor.call("addTask", text); + + // Clear form + React.findDOMNode(this.refs.textInput).value = ""; + }, + + toggleHideCompleted() { + this.setState({ + hideCompleted: ! this.state.hideCompleted + }); + }, + + render() { + return ( +
+
+

Todo List ({this.data.incompleteCount})

+ + + + + + { this.data.currentUser ? +
+ +
: '' + } +
+ +
    + {this.renderTasks()} +
+
+ ); + } +}); diff --git a/examples/simple-todos-react/LICENSE b/examples/simple-todos-react/LICENSE new file mode 100644 index 0000000000..e41973a6e1 --- /dev/null +++ b/examples/simple-todos-react/LICENSE @@ -0,0 +1,22 @@ +======================================== +Meteor is licensed under the MIT License +======================================== + +Copyright (C) 2011--2015 Meteor Development Group + +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. diff --git a/examples/simple-todos-react/README.md b/examples/simple-todos-react/README.md new file mode 100644 index 0000000000..214dd3a445 --- /dev/null +++ b/examples/simple-todos-react/README.md @@ -0,0 +1,9 @@ +# Simple Todo List + +The Meteor Tutorial app. + +Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. + +Learn how to build this app by following the [Meteor Tutorial](http://www.meteor.com/install). + +![screenshot](screenshot.png) \ No newline at end of file diff --git a/examples/simple-todos-react/Task.jsx b/examples/simple-todos-react/Task.jsx new file mode 100644 index 0000000000..37eadd38b8 --- /dev/null +++ b/examples/simple-todos-react/Task.jsx @@ -0,0 +1,52 @@ +// Task component - represents a single todo item +Task = React.createClass({ + propTypes: { + task: React.PropTypes.object.isRequired, + showPrivateButton: React.PropTypes.bool.isRequired + }, + + toggleChecked() { + // Set the checked property to the opposite of its current value + Meteor.call("setChecked", this.props.task._id, ! this.props.task.checked); + }, + + deleteThisTask() { + Meteor.call("removeTask", this.props.task._id); + }, + + togglePrivate() { + Meteor.call("setPrivate", this.props.task._id, ! this.props.task.private); + }, + + render() { + // Give tasks a different className when they are checked off, + // so that we can style them nicely in CSS + // Add "checked" and/or "private" to the className when needed + const taskClassName = (this.props.task.checked ? "checked" : "") + " " + + (this.props.task.private ? "private" : ""); + + return ( +
  • + + + + + { this.props.showPrivateButton ? ( + + ) : ''} + + + {this.props.task.username}: {this.props.task.text} + +
  • + ); + } +}); diff --git a/examples/simple-todos-react/screenshot.png b/examples/simple-todos-react/screenshot.png new file mode 100644 index 0000000000..a9a716d24a Binary files /dev/null and b/examples/simple-todos-react/screenshot.png differ diff --git a/examples/simple-todos-react/simple-todos-react.css b/examples/simple-todos-react/simple-todos-react.css new file mode 100644 index 0000000000..cec3ae619a --- /dev/null +++ b/examples/simple-todos-react/simple-todos-react.css @@ -0,0 +1,126 @@ +/* CSS declarations go here */ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} \ No newline at end of file diff --git a/examples/simple-todos-react/simple-todos-react.html b/examples/simple-todos-react/simple-todos-react.html new file mode 100644 index 0000000000..1aae2d4736 --- /dev/null +++ b/examples/simple-todos-react/simple-todos-react.html @@ -0,0 +1,7 @@ + + Todo List + + + +
    + diff --git a/examples/simple-todos-react/simple-todos-react.jsx b/examples/simple-todos-react/simple-todos-react.jsx new file mode 100644 index 0000000000..2b4ada2b4b --- /dev/null +++ b/examples/simple-todos-react/simple-todos-react.jsx @@ -0,0 +1,75 @@ +// Define a collection to hold our tasks +Tasks = new Mongo.Collection("tasks"); + +if (Meteor.isClient) { + // This code is executed on the client only + Accounts.ui.config({ + passwordSignupFields: "USERNAME_ONLY" + }); + + Meteor.subscribe("tasks"); + + Meteor.startup(function () { + // Use Meteor.startup to render the component after the page is ready + React.render(, document.getElementById("render-target")); + }); +} + +if (Meteor.isServer) { + // Only publish tasks that are public or belong to the current user + Meteor.publish("tasks", function () { + return Tasks.find({ + $or: [ + { private: {$ne: true} }, + { owner: this.userId } + ] + }); + }); +} + +Meteor.methods({ + addTask(text) { + // Make sure the user is logged in before inserting a task + if (! Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.insert({ + text: text, + createdAt: new Date(), + owner: Meteor.userId(), + username: Meteor.user().username + }); + }, + + removeTask(taskId) { + const task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can delete it + throw new Meteor.Error("not-authorized"); + } + + Tasks.remove(taskId); + }, + + setChecked(taskId, setChecked) { + const task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can check it off + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { checked: setChecked} }); + }, + + setPrivate(taskId, setToPrivate) { + const task = Tasks.findOne(taskId); + + // Make sure only the task owner can make a task private + if (task.owner !== Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { private: setToPrivate } }); + } +}); diff --git a/tools/tests/apps/build-plugin-throws-error/.meteor/.finished-upgraders b/examples/simple-todos/.meteor/.finished-upgraders similarity index 71% rename from tools/tests/apps/build-plugin-throws-error/.meteor/.finished-upgraders rename to examples/simple-todos/.meteor/.finished-upgraders index 8a761038c5..61ee313230 100644 --- a/tools/tests/apps/build-plugin-throws-error/.meteor/.finished-upgraders +++ b/examples/simple-todos/.meteor/.finished-upgraders @@ -6,3 +6,7 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/simple-todos/.meteor/.gitignore b/examples/simple-todos/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/examples/simple-todos/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/examples/simple-todos/.meteor/packages b/examples/simple-todos/.meteor/packages new file mode 100644 index 0000000000..a79948c614 --- /dev/null +++ b/examples/simple-todos/.meteor/packages @@ -0,0 +1,22 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +accounts-ui +accounts-password +standard-minifiers +meteor-base +mobile-experience +mongo +blaze-html-templates +session +jquery +tracker +logging +reload +random +ejson +spacebars +check diff --git a/examples/simple-todos/.meteor/platforms b/examples/simple-todos/.meteor/platforms new file mode 100644 index 0000000000..efeba1b50c --- /dev/null +++ b/examples/simple-todos/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/examples/simple-todos/.meteor/release b/examples/simple-todos/.meteor/release new file mode 100644 index 0000000000..712ef798f5 --- /dev/null +++ b/examples/simple-todos/.meteor/release @@ -0,0 +1 @@ +METEOR@1.2 diff --git a/examples/simple-todos/.meteor/versions b/examples/simple-todos/.meteor/versions new file mode 100644 index 0000000000..a27b60685f --- /dev/null +++ b/examples/simple-todos/.meteor/versions @@ -0,0 +1,76 @@ +accounts-base@1.2.1 +accounts-password@1.1.2 +accounts-ui@1.1.6 +accounts-ui-unstyled@1.1.8 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-rate-limiter@1.0.0 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +email@1.0.7 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +jquery@1.11.4 +launch-screen@1.0.3 +less@2.5.0_1 +livedata@1.0.14 +localstorage@1.0.4 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.1 +mongo-id@1.0.1 +npm-bcrypt@0.7.8_2 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +rate-limit@1.0.0 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +service-configuration@1.0.5 +session@1.1.1 +sha@1.0.4 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +srp@1.0.4 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/examples/simple-todos/LICENSE b/examples/simple-todos/LICENSE new file mode 100644 index 0000000000..e41973a6e1 --- /dev/null +++ b/examples/simple-todos/LICENSE @@ -0,0 +1,22 @@ +======================================== +Meteor is licensed under the MIT License +======================================== + +Copyright (C) 2011--2015 Meteor Development Group + +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. diff --git a/examples/simple-todos/README.md b/examples/simple-todos/README.md new file mode 100644 index 0000000000..214dd3a445 --- /dev/null +++ b/examples/simple-todos/README.md @@ -0,0 +1,9 @@ +# Simple Todo List + +The Meteor Tutorial app. + +Use it to share a single todo list with your friends. The list updates on everyone's screen in real time, and you can make tasks private if you don't want others to see them. + +Learn how to build this app by following the [Meteor Tutorial](http://www.meteor.com/install). + +![screenshot](screenshot.png) \ No newline at end of file diff --git a/examples/simple-todos/screenshot.png b/examples/simple-todos/screenshot.png new file mode 100644 index 0000000000..a9a716d24a Binary files /dev/null and b/examples/simple-todos/screenshot.png differ diff --git a/examples/simple-todos/simple-todos.css b/examples/simple-todos/simple-todos.css new file mode 100644 index 0000000000..ed5e77ec5d --- /dev/null +++ b/examples/simple-todos/simple-todos.css @@ -0,0 +1,126 @@ +/* CSS declarations go here */ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} diff --git a/examples/simple-todos/simple-todos.html b/examples/simple-todos/simple-todos.html new file mode 100644 index 0000000000..08854e2ffe --- /dev/null +++ b/examples/simple-todos/simple-todos.html @@ -0,0 +1,50 @@ + + Todo List + + + +
    +
    +

    Todo List ({{incompleteCount}})

    + + + + {{> loginButtons}} + + {{#if currentUser}} +
    + +
    + {{/if}} +
    + +
      + {{#each tasks}} + {{> task}} + {{/each}} +
    +
    + + + diff --git a/examples/simple-todos/simple-todos.js b/examples/simple-todos/simple-todos.js new file mode 100644 index 0000000000..9bf41bbfc8 --- /dev/null +++ b/examples/simple-todos/simple-todos.js @@ -0,0 +1,123 @@ +Tasks = new Mongo.Collection("tasks"); + +if (Meteor.isServer) { + // This code only runs on the server + // Only publish tasks that are public or belong to the current user + Meteor.publish("tasks", function () { + return Tasks.find({ + $or: [ + { private: {$ne: true} }, + { owner: this.userId } + ] + }); + }); +} + +if (Meteor.isClient) { + // This code only runs on the client + Meteor.subscribe("tasks"); + + Template.body.helpers({ + tasks: function () { + if (Session.get("hideCompleted")) { + // If hide completed is checked, filter tasks + return Tasks.find({checked: {$ne: true}}, {sort: {createdAt: -1}}); + } else { + // Otherwise, return all of the tasks + return Tasks.find({}, {sort: {createdAt: -1}}); + } + }, + hideCompleted: function () { + return Session.get("hideCompleted"); + }, + incompleteCount: function () { + return Tasks.find({checked: {$ne: true}}).count(); + } + }); + + Template.body.events({ + "submit .new-task": function (event) { + // Prevent default browser form submit + event.preventDefault(); + + // Get value from form element + var text = event.target.text.value; + + // Insert a task into the collection + Meteor.call("addTask", text); + + // Clear form + event.target.text.value = ""; + }, + "change .hide-completed input": function (event) { + Session.set("hideCompleted", event.target.checked); + } + }); + + Template.task.helpers({ + isOwner: function () { + return this.owner === Meteor.userId(); + } + }); + + Template.task.events({ + "click .toggle-checked": function () { + // Set the checked property to the opposite of its current value + Meteor.call("setChecked", this._id, ! this.checked); + }, + "click .delete": function () { + Meteor.call("deleteTask", this._id); + }, + "click .toggle-private": function () { + Meteor.call("setPrivate", this._id, ! this.private); + } + }); + + Accounts.ui.config({ + passwordSignupFields: "USERNAME_ONLY" + }); +} + +Meteor.methods({ + addTask: function (text) { + // Make sure the user is logged in before inserting a task + if (! Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.insert({ + text: text, + createdAt: new Date(), + owner: Meteor.userId(), + username: Meteor.user().username + }); + }, + deleteTask: function (taskId) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can delete it + throw new Meteor.Error("not-authorized"); + } + + Tasks.remove(taskId); + }, + setChecked: function (taskId, setChecked) { + var task = Tasks.findOne(taskId); + if (task.private && task.owner !== Meteor.userId()) { + // If the task is private, make sure only the owner can check it off + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { checked: setChecked} }); + }, + setPrivate: function (taskId, setToPrivate) { + var task = Tasks.findOne(taskId); + + // Make sure only the task owner can make a task private + if (task.owner !== Meteor.userId()) { + throw new Meteor.Error("not-authorized"); + } + + Tasks.update(taskId, { $set: { private: setToPrivate } }); + } +}); diff --git a/examples/todos/.meteor/.finished-upgraders b/examples/todos/.meteor/.finished-upgraders index 8a761038c5..61ee313230 100644 --- a/examples/todos/.meteor/.finished-upgraders +++ b/examples/todos/.meteor/.finished-upgraders @@ -6,3 +6,7 @@ notices-for-0.9.0 notices-for-0.9.1 0.9.4-platform-file notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes diff --git a/examples/todos/.meteor/release b/examples/todos/.meteor/release index 315c635bf3..712ef798f5 100644 --- a/examples/todos/.meteor/release +++ b/examples/todos/.meteor/release @@ -1 +1 @@ -METEOR@1.1.0.3 +METEOR@1.2 diff --git a/examples/todos/.meteor/versions b/examples/todos/.meteor/versions index e68384f4ac..b90a704c66 100644 --- a/examples/todos/.meteor/versions +++ b/examples/todos/.meteor/versions @@ -1,60 +1,79 @@ -accounts-base@1.2.0 -accounts-password@1.1.1 -autoupdate@1.2.1 -base64@1.0.3 -binary-heap@1.0.3 -blaze@2.1.2 -blaze-tools@1.0.3 -boilerplate-generator@1.0.3 -callback-hook@1.0.3 -check@1.0.5 -ddp@1.1.0 -deps@1.0.7 -ejson@1.0.6 -email@1.0.6 -fastclick@1.0.3 -geojson-utils@1.0.3 -html-tools@1.0.4 -htmljs@1.0.4 -http@1.1.0 -id-map@1.0.3 -insecure@1.0.3 +accounts-base@1.2.1 +accounts-password@1.1.2 +autoupdate@1.2.3 +babel-compiler@5.8.24 +babel-runtime@0.1.4 +base64@1.0.4 +binary-heap@1.0.4 +blaze@2.1.3 +blaze-html-templates@1.0.1 +blaze-tools@1.0.4 +boilerplate-generator@1.0.4 +caching-compiler@1.0.0 +caching-html-compiler@1.0.1 +callback-hook@1.0.4 +check@1.0.6 +ddp@1.2.1 +ddp-client@1.2.1 +ddp-common@1.2.1 +ddp-rate-limiter@1.0.0 +ddp-server@1.2.1 +deps@1.0.8 +diff-sequence@1.0.1 +ecmascript@0.1.3 +ecmascript-collections@0.1.6 +ejson@1.0.7 +email@1.0.7 +fastclick@1.0.7 +geojson-utils@1.0.4 +hot-code-push@1.0.0 +html-tools@1.0.5 +htmljs@1.0.5 +http@1.1.1 +id-map@1.0.4 +insecure@1.0.4 iron:core@0.3.4 iron:dynamic-template@0.4.1 iron:layout@0.4.1 iron:router@0.9.4 -jquery@1.11.3_2 -json@1.0.3 -launch-screen@1.0.2 -less@1.0.14 -livedata@1.0.13 -localstorage@1.0.3 -logging@1.0.7 -meteor@1.1.6 -meteor-platform@1.2.2 -minifiers@1.1.5 -minimongo@1.0.8 -mobile-status-bar@1.0.3 -mongo@1.1.0 +jquery@1.11.4 +launch-screen@1.0.3 +less@2.5.0_1 +livedata@1.0.14 +localstorage@1.0.4 +logging@1.0.8 +meteor@1.1.7 +meteor-base@1.0.1 +minifiers@1.1.6 +minimongo@1.0.9 +mobile-experience@1.0.1 +mobile-status-bar@1.0.6 +mongo@1.1.1 +mongo-id@1.0.1 npm-bcrypt@0.7.8_2 -observe-sequence@1.0.6 -ordered-dict@1.0.3 -random@1.0.3 -reactive-dict@1.1.0 -reactive-var@1.0.5 -reload@1.1.3 -retry@1.0.3 -routepolicy@1.0.5 -service-configuration@1.0.4 -session@1.1.0 -sha@1.0.3 -spacebars@1.0.6 -spacebars-compiler@1.0.6 -srp@1.0.3 -templating@1.1.1 -tracker@1.0.7 -ui@1.0.6 -underscore@1.0.3 -url@1.0.4 -webapp@1.2.0 -webapp-hashing@1.0.3 +npm-mongo@1.4.39_1 +observe-sequence@1.0.7 +ordered-dict@1.0.4 +promise@0.4.8 +random@1.0.4 +rate-limit@1.0.0 +reactive-dict@1.1.1 +reactive-var@1.0.6 +reload@1.1.4 +retry@1.0.4 +routepolicy@1.0.6 +service-configuration@1.0.5 +session@1.1.1 +sha@1.0.4 +spacebars@1.0.7 +spacebars-compiler@1.0.7 +srp@1.0.4 +standard-minifiers@1.0.0 +templating@1.1.2 +templating-tools@1.0.0 +tracker@1.0.8 +ui@1.0.7 +underscore@1.0.4 +url@1.0.5 +webapp@1.2.2 +webapp-hashing@1.0.4 diff --git a/packages/accounts-base/accounts_rate_limit.js b/packages/accounts-base/accounts_rate_limit.js index 67c81c13f0..77842f012b 100644 --- a/packages/accounts-base/accounts_rate_limit.js +++ b/packages/accounts-base/accounts_rate_limit.js @@ -5,7 +5,7 @@ Ap.removeDefaultRateLimit = function () { const resp = DDPRateLimiter.removeRule(defaultRateLimiterRuleId); defaultRateLimiterRuleId = null; return resp; -} +}; // Add a default rule of limiting logins, creating new users and password reset // to 5 times every 10 seconds per connection. @@ -24,6 +24,6 @@ Ap.addDefaultRateLimit = function () { } }, 5, 10000); } -} +}; -Ap.addDefaultRateLimit(); \ No newline at end of file +Ap.addDefaultRateLimit(); diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index f448932eb7..ed426fdef8 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "A user account system", - version: "1.2.1-plugins.0" + version: "1.2.1" }); Package.onUse(function (api) { diff --git a/packages/accounts-facebook/package.js b/packages/accounts-facebook/package.js index 96cac242a2..4f0c166948 100644 --- a/packages/accounts-facebook/package.js +++ b/packages/accounts-facebook/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Facebook accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function(api) { diff --git a/packages/accounts-github/package.js b/packages/accounts-github/package.js index 581b0a7657..7af5875dbe 100644 --- a/packages/accounts-github/package.js +++ b/packages/accounts-github/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Github accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function(api) { diff --git a/packages/accounts-google/package.js b/packages/accounts-google/package.js index 74834708da..8da49971c6 100644 --- a/packages/accounts-google/package.js +++ b/packages/accounts-google/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Google accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function(api) { diff --git a/packages/accounts-meetup/package.js b/packages/accounts-meetup/package.js index 588c7c9c57..53e25e8013 100644 --- a/packages/accounts-meetup/package.js +++ b/packages/accounts-meetup/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Meetup accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function(api) { diff --git a/packages/accounts-meteor-developer/package.js b/packages/accounts-meteor-developer/package.js index b8764c994d..f4506185f2 100644 --- a/packages/accounts-meteor-developer/package.js +++ b/packages/accounts-meteor-developer/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Meteor developer accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function (api) { diff --git a/packages/accounts-oauth/package.js b/packages/accounts-oauth/package.js index 4f39fa35f3..e17f3b6dde 100644 --- a/packages/accounts-oauth/package.js +++ b/packages/accounts-oauth/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Common code for OAuth-based login services", - version: "1.1.6-plugins.0" + version: "1.1.6" }); Package.onUse(function (api) { diff --git a/packages/accounts-password/package.js b/packages/accounts-password/package.js index b45b16f325..ec7dd94b83 100644 --- a/packages/accounts-password/package.js +++ b/packages/accounts-password/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Password support for accounts", - version: "1.1.2-plugins.0" + version: "1.1.2" }); Package.onUse(function(api) { diff --git a/packages/accounts-password/password_client.js b/packages/accounts-password/password_client.js index ab6cda5e1c..b6aa443695 100644 --- a/packages/accounts-password/password_client.js +++ b/packages/accounts-password/password_client.js @@ -130,8 +130,6 @@ Accounts.createUser = function (options, callback) { }); }; - - // Change password. Must be logged in. // // @param oldPassword {String|null} By default servers no longer allow diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index cc4a7335de..6e7c1f8f76 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -77,13 +77,7 @@ var checkPassword = Accounts._checkPassword; /// LOGIN /// -// Attempts to find a user from a user query. -// First tries to match username or email case sensitively; if that fails, it -// tries case insensitively; but if more than one user matches the case -// insensitive search, it returns null -// @param query {Object} with one of `id`, `username`, or `email`. -// @returns A user if found, else null -var findUserFromQuery = function (query) { +Accounts._findUserByQuery = function (query) { var user = null; if (query.id) { @@ -110,8 +104,6 @@ var findUserFromQuery = function (query) { // No match if multiple candidates are found if (candidateUsers.length === 1) { user = candidateUsers[0]; - } else { - console.error('Found multiple users with ' + fieldName + ' = ' + fieldValue + ' only differing in case. Requiring case sensitive login.'); } } } @@ -119,6 +111,35 @@ var findUserFromQuery = function (query) { return user; }; +/** + * @summary Finds the user with the specified username. + * First tries to match username case sensitively; if that fails, it + * tries case insensitively; but if more than one user matches the case + * insensitive search, it returns null. + * @locus Server + * @param {String} username The username to look for + * @returns {Object} A user if found, else null + */ +Accounts.findUserByUsername = function (username) { + return Accounts._findUserByQuery({ + username: username + }); +}; + +/** + * @summary Finds the user with the specified email. + * First tries to match email case sensitively; if that fails, it + * tries case insensitively; but if more than one user matches the case + * insensitive search, it returns null. + * @locus Server + * @param {String} email The email address to look for + * @returns {Object} A user if found, else null + */ +Accounts.findUserByEmail = function (email) { + return Accounts._findUserByQuery({ + email: email + }); +}; // Generates a MongoDB selector that can be used to perform a fast case // insensitive lookup for the given fieldName and string. Since MongoDB does @@ -164,6 +185,26 @@ var generateCasePermutationsForString = function (string) { return permutations; } +var checkForCaseInsensitiveDuplicates = function (fieldName, displayName, fieldValue, ownUserId) { + // Some tests need the ability to add users with the same case insensitive + // value, hence the _skipCaseInsensitiveChecksForTest check + var skipCheck = _.has(Accounts._skipCaseInsensitiveChecksForTest, fieldValue); + + if (fieldValue && !skipCheck) { + var matchedUsers = Meteor.users.find( + selectorForFastCaseInsensitiveLookup(fieldName, fieldValue)).fetch(); + + if (matchedUsers.length > 0 && + // If we don't have a userId yet, any match we find is a duplicate + (!ownUserId || + // Otherwise, check to see if there are multiple matches or a match + // that is not us + (matchedUsers.length > 1 || matchedUsers[0]._id !== ownUserId))) { + throw new Meteor.Error(403, displayName + " already exists."); + } + } +}; + // XXX maybe this belongs in the check package var NonEmptyString = Match.Where(function (x) { check(x, String); @@ -210,7 +251,7 @@ Accounts.registerLoginHandler("password", function (options) { }); - var user = findUserFromQuery(options.user); + var user = Accounts._findUserByQuery(options.user); if (!user) throw new Meteor.Error(403, "User not found"); @@ -276,7 +317,7 @@ Accounts.registerLoginHandler("password", function (options) { password: passwordValidator }); - var user = findUserFromQuery(options.user); + var user = Accounts._findUserByQuery(options.user); if (!user) throw new Meteor.Error(403, "User not found"); @@ -320,6 +361,40 @@ Accounts.registerLoginHandler("password", function (options) { /// CHANGING /// +/** + * @summary Change a user's username. Use this instead of updating the + * database directly. The operation will fail if there is an existing user + * with a username only differing in case. + * @locus Server + * @param {String} userId The ID of the user to update. + * @param {String} newUsername A new username for the user. + */ +Accounts.setUsername = function (userId, newUsername) { + check(userId, NonEmptyString); + check(newUsername, NonEmptyString); + + var user = Meteor.users.findOne(userId); + if (!user) + throw new Meteor.Error(403, "User not found"); + + var oldUsername = user.username; + + // Perform a case insensitive check fro duplicates before update + checkForCaseInsensitiveDuplicates('username', 'Username', newUsername, user._id); + + Meteor.users.update({_id: user._id}, {$set: {username: newUsername}}); + + // Perform another check after update, in case a matching user has been + // inserted in the meantime + try { + checkForCaseInsensitiveDuplicates('username', 'Username', newUsername, user._id); + } catch (ex) { + // Undo update if the check fails + Meteor.users.update({_id: user._id}, {$set: {username: oldUsername}}); + throw ex; + } +}; + // Let the user change their own password if they know the old // password. `oldPassword` and `newPassword` should be objects with keys // `digest` and `algorithm` (representing the SHA256 of the password). @@ -758,7 +833,111 @@ Meteor.methods({verifyEmail: function (token) { ); }}); +/** + * @summary Add an email address for a user. Use this instead of directly + * updating the database. The operation will fail if there is a different user + * with an email only differing in case. If the specified user has an existing + * email only differing in case however, we replace it. + * @locus Server + * @param {String} userId The ID of the user to update. + * @param {String} newEmail A new email address for the user. + * @param {Boolean} [verified] Optional - whether the new email address should + * be marked as verified. Defaults to false. + */ +Accounts.addEmail = function (userId, newEmail, verified) { + check(userId, NonEmptyString); + check(newEmail, NonEmptyString); + check(verified, Match.Optional(Boolean)); + if (_.isUndefined(verified)) { + verified = false; + } + + var user = Meteor.users.findOne(userId); + if (!user) + throw new Meteor.Error(403, "User not found"); + + // Allow users to change their own email to a version with a different case + + // We don't have to call checkForCaseInsensitiveDuplicates to do a case + // insensitive check across all emails in the database here because: (1) if + // there is no case-insensitive duplicate between this user and other users, + // then we are OK and (2) if this would create a conflict with other users + // then there would already be a case-insensitive duplicate and we can't fix + // that in this code anyway. + var caseInsensitiveRegExp = + new RegExp('^' + Meteor._escapeRegExp(newEmail) + '$', 'i'); + + var didUpdateOwnEmail = _.any(user.emails, function(email, index) { + if (caseInsensitiveRegExp.test(email.address)) { + Meteor.users.update({ + _id: user._id, + 'emails.address': email.address + }, {$set: { + 'emails.$.address': newEmail, + 'emails.$.verified': verified + }}); + return true; + } + + return false; + }); + + // In the other updates below, we have to do another call to + // checkForCaseInsensitiveDuplicates to make sure that no conflicting values + // were added to the database in the meantime. We don't have to do this for + // the case where the user is updating their email address to one that is the + // same as before, but only different because of capitalization. Read the + // big comment above to understand why. + + if (didUpdateOwnEmail) { + return; + } + + // Perform a case insensitive check for duplicates before update + checkForCaseInsensitiveDuplicates('emails.address', 'Email', newEmail, user._id); + + Meteor.users.update({ + _id: user._id + }, { + $addToSet: { + emails: { + address: newEmail, + verified: verified + } + } + }); + + // Perform another check after update, in case a matching user has been + // inserted in the meantime + try { + checkForCaseInsensitiveDuplicates('emails.address', 'Email', newEmail, user._id); + } catch (ex) { + // Undo update if the check fails + Meteor.users.update({_id: user._id}, + {$pull: {emails: {address: newEmail}}}); + throw ex; + } +} + +/** + * @summary Remove an email address for a user. Use this instead of updating + * the database directly. + * @locus Server + * @param {String} userId The ID of the user to update. + * @param {String} email The email address to remove. + */ +Accounts.removeEmail = function (userId, email) { + check(userId, NonEmptyString); + check(email, NonEmptyString); + + var user = Meteor.users.findOne(userId); + if (!user) + throw new Meteor.Error(403, "User not found"); + + Meteor.users.update({_id: user._id}, + {$pull: {emails: {address: email}}}); +} /// /// CREATING USERS @@ -794,34 +973,16 @@ var createUser = function (options) { if (email) user.emails = [{address: email, verified: false}]; - // Check if there is no other user with a username or email only differing - // in case. - var performCaseInsensitiveCheck = function () { - // Some tests need the ability to add users with the same case insensitive - // username or email, hence the _skipCaseInsensitiveChecksForTest check - - if (username && - !_.has(Accounts._skipCaseInsensitiveChecksForTest, username) && - Meteor.users.find(selectorForFastCaseInsensitiveLookup( - "username", username)).count() > 1) { - throw new Meteor.Error(403, "Username already exists."); - } - - if (email && - !_.has(Accounts._skipCaseInsensitiveChecksForTest, email) && - Meteor.users.find(selectorForFastCaseInsensitiveLookup( - "emails.address", email)).count() > 1) { - throw new Meteor.Error(403, "Email already exists."); - } - } - // Perform a case insensitive check before insert - performCaseInsensitiveCheck(); + checkForCaseInsensitiveDuplicates('username', 'Username', username); + checkForCaseInsensitiveDuplicates('emails.address', 'Email', email); + var userId = Accounts.insertUserDoc(options, user); // Perform another check after insert, in case a matching user has been // inserted in the meantime try { - performCaseInsensitiveCheck(); + checkForCaseInsensitiveDuplicates('username', 'Username', username, userId); + checkForCaseInsensitiveDuplicates('emails.address', 'Email', email, userId); } catch (ex) { // Remove inserted user if the check fails Meteor.users.remove(userId); diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js index 32c361d9bb..943062b74d 100644 --- a/packages/accounts-password/password_tests.js +++ b/packages/accounts-password/password_tests.js @@ -2,9 +2,6 @@ Accounts._noConnectionCloseDelayForTest = true; Accounts.removeDefaultRateLimit(); if (Meteor.isServer) { Meteor.methods({ - getUserId: function () { - return this.userId; - }, getResetToken: function () { var token = Meteor.users.findOne(this.userId).services.password.reset; return token; @@ -48,14 +45,29 @@ if (Meteor.isClient) (function () { }; var logoutStep = function (test, expect) { Meteor.logout(expect(function (error) { - test.equal(error, undefined); + if (error) { + test.fail(error.message); + } test.equal(Meteor.user(), null); })); }; var loggedInAs = function (someUsername, test, expect) { return expect(function (error) { - test.equal(error, undefined); - test.equal(Meteor.user().username, someUsername); + if (error) { + test.fail(error.message); + } + test.equal(Meteor.userId() && Meteor.user().username, someUsername); + }); + }; + var loggedInUserHasEmail = function (someEmail, test, expect) { + return expect(function (error) { + if (error) { + test.fail(error.message); + } + var user = Meteor.user(); + test.isTrue(user && _.some(user.emails, function(email) { + return email.address === someEmail; + })); }); }; var expectError = function (expectedError, test, expect) { @@ -74,17 +86,23 @@ if (Meteor.isClient) (function () { }; var invalidateLoginsStep = function (test, expect) { Meteor.call("testInvalidateLogins", 'fail', expect(function (error) { - test.isFalse(error); + if (error) { + test.fail(error.message); + } })); }; var hideActualLoginErrorStep = function (test, expect) { Meteor.call("testInvalidateLogins", 'hide', expect(function (error) { - test.isFalse(error); + if (error) { + test.fail(error.message); + } })); }; var validateLoginsStep = function (test, expect) { Meteor.call("testInvalidateLogins", false, expect(function (error) { - test.isFalse(error); + if (error) { + test.fail(error.message); + } })); }; @@ -206,7 +224,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive username with non-ASCII characters", [ + testAsyncMulti("passwords - logging in with case insensitive username " + + "with non-ASCII characters", [ function (test, expect) { // Hack because Tinytest does not clean the database between tests/runs this.randomSuffix = Random.id(10); @@ -226,7 +245,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive username should escape regex special characters", [ + testAsyncMulti("passwords - logging in with case insensitive username " + + "should escape regex special characters", [ createUserStep, logoutStep, // We shouldn't be able to log in with a regex expression for the username @@ -238,7 +258,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive username should require a match of the full string", [ + testAsyncMulti("passwords - logging in with case insensitive username " + + "should require a match of the full string", [ createUserStep, logoutStep, // We shouldn't be able to log in with a partial match for the username @@ -250,21 +271,22 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive username when there are multiple matches", [ + testAsyncMulti("passwords - logging in with case insensitive username when " + + "there are multiple matches", [ createUserStep, logoutStep, function (test, expect) { - this.otherUserName = 'Adalovelace' + this.randomSuffix; - addSkipCaseInsensitiveChecksForTest(this.otherUserName, test, expect); + this.otherUsername = 'Adalovelace' + this.randomSuffix; + addSkipCaseInsensitiveChecksForTest(this.otherUsername, test, expect); }, // Create another user with a username that only differs in case function (test, expect) { Accounts.createUser( - { username: this.otherUserName, password: this.password }, - loggedInAs(this.otherUserName, test, expect)); + { username: this.otherUsername, password: this.password }, + loggedInAs(this.otherUsername, test, expect)); }, function (test, expect) { - removeSkipCaseInsensitiveChecksForTest(this.otherUserName, test, expect); + removeSkipCaseInsensitiveChecksForTest(this.otherUsername, test, expect); }, // We shouldn't be able to log in with the username in lower case function (test, expect) { @@ -282,7 +304,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - creating users with the same case insensitive username", [ + testAsyncMulti("passwords - creating users with the same case insensitive " + + "username", [ createUserStep, logoutStep, // Attempting to create another user with a username that only differs in @@ -318,7 +341,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive email should escape regex special characters", [ + testAsyncMulti("passwords - logging in with case insensitive email should " + + "escape regex special characters", [ createUserStep, logoutStep, // We shouldn't be able to log in with a regex expression for the email @@ -330,7 +354,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive email should require a match of the full string", [ + testAsyncMulti("passwords - logging in with case insensitive email should " + + "require a match of the full string", [ createUserStep, logoutStep, // We shouldn't be able to log in with a partial match for the email @@ -342,24 +367,25 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - logging in with case insensitive email when there are multiple matches", [ + testAsyncMulti("passwords - logging in with case insensitive email when " + + "there are multiple matches", [ createUserStep, logoutStep, function (test, expect) { - this.otherUserName = 'AdaLovelace' + Random.id(10); + this.otherUsername = 'AdaLovelace' + Random.id(10); this.otherEmail = "ADA-intercept@lovelace.com" + this.randomSuffix; addSkipCaseInsensitiveChecksForTest(this.otherEmail, test, expect); }, // Create another user with an email that only differs in case function (test, expect) { Accounts.createUser( - { username: this.otherUserName, + { username: this.otherUsername, email: this.otherEmail, password: this.password }, - loggedInAs(this.otherUserName, test, expect)); + loggedInAs(this.otherUsername, test, expect)); }, function (test, expect) { - removeSkipCaseInsensitiveChecksForTest(this.otherUserName, test, expect); + removeSkipCaseInsensitiveChecksForTest(this.otherUsername, test, expect); }, logoutStep, // We shouldn't be able to log in with the email in lower case @@ -378,7 +404,8 @@ if (Meteor.isClient) (function () { } ]); - testAsyncMulti("passwords - creating users with the same case insensitive email", [ + testAsyncMulti("passwords - creating users with the same case insensitive " + + "email", [ createUserStep, logoutStep, // Attempting to create another user with an email that only differs in @@ -1229,7 +1256,8 @@ if (Meteor.isServer) (function () { }); }); - // XXX would be nice to test Accounts.config({forbidClientAccountCreation: true}) + // XXX would be nice to test + // Accounts.config({forbidClientAccountCreation: true}) Tinytest.addAsync( 'passwords - login token observes get cleaned up', @@ -1295,7 +1323,8 @@ if (Meteor.isServer) (function () { Accounts.sendResetPasswordEmail(userId, email); - var resetPasswordEmailOptions = Meteor.call("getInterceptedEmails", email)[0]; + var resetPasswordEmailOptions = + Meteor.call("getInterceptedEmails", email)[0]; var re = new RegExp(Meteor.absoluteUrl() + "#/reset-password/(\\S*)"); var match = resetPasswordEmailOptions.text.match(re); @@ -1312,4 +1341,164 @@ if (Meteor.isServer) (function () { Meteor.call("login", {user: {username: username}, password: "new-password"}); }, /Incorrect password/); }); + + // We should be able to change the username + Tinytest.add("passwords - change username", function (test) { + var username = Random.id(); + var userId = Accounts.createUser({ + username: username + }); + + test.isTrue(userId); + + var newUsername = Random.id(); + Accounts.setUsername(userId, newUsername); + + test.equal(Accounts._findUserByQuery({id: userId}).username, newUsername); + + // Test findUserByUsername as well while we're here + test.equal(Accounts.findUserByUsername(newUsername)._id, userId); + }); + + Tinytest.add("passwords - change username to a new one only differing " + + "in case", function (test) { + var username = Random.id() + "user"; + var userId = Accounts.createUser({ + username: username.toUpperCase() + }); + + test.isTrue(userId); + + var newUsername = username.toLowerCase(); + Accounts.setUsername(userId, newUsername); + + test.equal(Accounts._findUserByQuery({id: userId}).username, newUsername); + }); + + // We should not be able to change the username to one that only + // differs in case from an existing one + Tinytest.add("passwords - change username should fail when there are " + + "existing users with a username only differing in case", function (test) { + var username = Random.id() + "user"; + var usernameUpper = username.toUpperCase(); + + var userId1 = Accounts.createUser({ + username: username + }); + + var user2OriginalUsername = Random.id(); + var userId2 = Accounts.createUser({ + username: user2OriginalUsername + }); + + test.isTrue(userId1); + test.isTrue(userId2); + + test.throws(function () { + Accounts.setUsername(userId2, usernameUpper); + }, /Username already exists/); + + test.equal(Accounts._findUserByQuery({id: userId2}).username, + user2OriginalUsername); + }); + + Tinytest.add("passwords - add email", function (test) { + var origEmail = Random.id() + "@turing.com"; + var userId = Accounts.createUser({ + email: origEmail + }); + + var newEmail = Random.id() + "@turing.com"; + Accounts.addEmail(userId, newEmail); + + var thirdEmail = Random.id() + "@turing.com"; + Accounts.addEmail(userId, thirdEmail, true); + + test.equal(Accounts._findUserByQuery({id: userId}).emails, [ + { address: origEmail, verified: false }, + { address: newEmail, verified: false }, + { address: thirdEmail, verified: true } + ]); + + // Test findUserByEmail as well while we're here + test.equal(Accounts.findUserByEmail(origEmail)._id, userId); + }); + + Tinytest.add("passwords - add email when the user has an existing email " + + "only differing in case", function (test) { + var origEmail = Random.id() + "@turing.com"; + var userId = Accounts.createUser({ + email: origEmail + }); + + var newEmail = Random.id() + "@turing.com"; + Accounts.addEmail(userId, newEmail); + + var thirdEmail = origEmail.toUpperCase(); + Accounts.addEmail(userId, thirdEmail, true); + + test.equal(Accounts._findUserByQuery({id: userId}).emails, [ + { address: thirdEmail, verified: true }, + { address: newEmail, verified: false } + ]); + }); + + Tinytest.add("passwords - add email should fail when there is an existing " + + "user with an email only differing in case", function (test) { + var user1Email = Random.id() + "@turing.com"; + var userId1 = Accounts.createUser({ + email: user1Email + }); + + var user2Email = Random.id() + "@turing.com"; + var userId2 = Accounts.createUser({ + email: user2Email + }); + + var dupEmail = user1Email.toUpperCase(); + test.throws(function () { + Accounts.addEmail(userId2, dupEmail); + }, /Email already exists/); + + test.equal(Accounts._findUserByQuery({id: userId1}).emails, [ + { address: user1Email, verified: false } + ]); + + test.equal(Accounts._findUserByQuery({id: userId2}).emails, [ + { address: user2Email, verified: false } + ]); + }); + + Tinytest.add("passwords - remove email", function (test) { + var origEmail = Random.id() + "@turing.com"; + var userId = Accounts.createUser({ + email: origEmail + }); + + var newEmail = Random.id() + "@turing.com"; + Accounts.addEmail(userId, newEmail); + + var thirdEmail = Random.id() + "@turing.com"; + Accounts.addEmail(userId, thirdEmail, true); + + test.equal(Accounts._findUserByQuery({id: userId}).emails, [ + { address: origEmail, verified: false }, + { address: newEmail, verified: false }, + { address: thirdEmail, verified: true } + ]); + + Accounts.removeEmail(userId, newEmail); + + test.equal(Accounts._findUserByQuery({id: userId}).emails, [ + { address: origEmail, verified: false }, + { address: thirdEmail, verified: true } + ]); + + Accounts.removeEmail(userId, origEmail); + + test.equal(Accounts._findUserByQuery({id: userId}).emails, [ + { address: thirdEmail, verified: true } + ]); + }); + }) (); diff --git a/packages/accounts-twitter/package.js b/packages/accounts-twitter/package.js index c7bcab74f5..db98ddcaf9 100644 --- a/packages/accounts-twitter/package.js +++ b/packages/accounts-twitter/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Twitter accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function(api) { diff --git a/packages/accounts-ui-unstyled/login_buttons_dropdown.js b/packages/accounts-ui-unstyled/login_buttons_dropdown.js index 85c112e14e..74d33d3052 100644 --- a/packages/accounts-ui-unstyled/login_buttons_dropdown.js +++ b/packages/accounts-ui-unstyled/login_buttons_dropdown.js @@ -146,13 +146,18 @@ Template._loginButtonsLoggedOutDropdown.events({ // force the ui to update so that we have the approprate fields to fill in Tracker.flush(); - if (document.getElementById('login-username')) + if (document.getElementById('login-username') && username !== null) document.getElementById('login-username').value = username; - if (document.getElementById('login-email')) + if (document.getElementById('login-email') && email !== null) document.getElementById('login-email').value = email; - if (document.getElementById('login-username-or-email')) - document.getElementById('login-username-or-email').value = email || username; + var usernameOrEmailInput = document.getElementById('login-username-or-email'); + if (usernameOrEmailInput) { + if (email !== null) + usernameOrEmailInput.value = email; + if (username !== null) + usernameOrEmailInput.value = username; + } if (password !== null) document.getElementById('login-password').value = password; diff --git a/packages/accounts-ui-unstyled/package.js b/packages/accounts-ui-unstyled/package.js index 102eda9637..6aa9a1e521 100644 --- a/packages/accounts-ui-unstyled/package.js +++ b/packages/accounts-ui-unstyled/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Unstyled version of login widgets", - version: "1.1.8-plugins.0" + version: "1.1.8" }); Package.onUse(function (api) { diff --git a/packages/accounts-ui/package.js b/packages/accounts-ui/package.js index cfd5c10ae3..e16d3e1e11 100644 --- a/packages/accounts-ui/package.js +++ b/packages/accounts-ui/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Simple templates to add login widgets to an app", - version: "1.1.6-plugins.1" + version: "1.1.6" }); Package.onUse(function (api) { diff --git a/packages/accounts-weibo/package.js b/packages/accounts-weibo/package.js index a6a68110cd..0a2e3b3392 100644 --- a/packages/accounts-weibo/package.js +++ b/packages/accounts-weibo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Sina Weibo accounts", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function(api) { diff --git a/packages/appcache/package.js b/packages/appcache/package.js index 327a7b89f0..4de2fee503 100644 --- a/packages/appcache/package.js +++ b/packages/appcache/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Enable the application cache in the browser", - version: "1.0.5-plugins.0" + version: "1.0.5" }); Package.onUse(function (api) { diff --git a/packages/audit-argument-checks/package.js b/packages/audit-argument-checks/package.js index 33987cde86..54ff0ca13d 100644 --- a/packages/audit-argument-checks/package.js +++ b/packages/audit-argument-checks/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Try to detect inadequate input sanitization", - version: '1.0.4-plugins.0' + version: '1.0.4' }); // This package is empty; its presence is detected by livedata. diff --git a/packages/autopublish/package.js b/packages/autopublish/package.js index 483e6a4473..f5e68c7ff3 100644 --- a/packages/autopublish/package.js +++ b/packages/autopublish/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "(For prototyping only) Publish the entire database to all clients", - version: '1.0.4-plugins.0' + version: '1.0.4' }); // This package is empty; its presence is detected by several other packages diff --git a/packages/autoupdate/QA.md b/packages/autoupdate/QA.md index 0845c4e27f..62afdc6cdf 100644 --- a/packages/autoupdate/QA.md +++ b/packages/autoupdate/QA.md @@ -64,11 +64,11 @@ Undo previous changes made, such as by using `git checkout .` Reload the client, which will cause the browser to stop using the app cache. It's hard to see the `newClientAvailable` reactive variable when the -client automatically reloads. Remove the `reload` package so you can +client automatically reloads. Remove the `hot-code-push` package so you can see the variable without having the client also reload. - $ meteor remove hot-code-push - $ meteor add autoupdate + $ meteor remove meteor-base + $ meteor add meteor webapp ddp autoupdate Add to leaderboard.js: diff --git a/packages/autoupdate/package.js b/packages/autoupdate/package.js index fc5b389110..eea8df73bf 100644 --- a/packages/autoupdate/package.js +++ b/packages/autoupdate/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Update the client when new client code is available", - version: '1.2.2-plugins.1' + version: '1.2.3' }); Cordova.depends({ diff --git a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json index a131c8d211..981b99d72b 100644 --- a/packages/babel-compiler/.npm/package/npm-shrinkwrap.json +++ b/packages/babel-compiler/.npm/package/npm-shrinkwrap.json @@ -30,25 +30,22 @@ "asap": { "version": "2.0.3" }, - "ast-types": { - "version": "0.8.5" - }, "ast-traverse": { "version": "0.1.1" }, + "ast-types": { + "version": "0.8.5" + }, "async": { "version": "0.2.10" }, "babel-plugin-constant-folding": { "version": "1.0.1" }, - "babel-plugin-eval": { - "version": "1.0.1" - }, "babel-plugin-dead-code-elimination": { "version": "1.0.2" }, - "babel-plugin-inline-environment-variables": { + "babel-plugin-eval": { "version": "1.0.1" }, "babel-plugin-jscript": { @@ -57,6 +54,9 @@ "babel-plugin-member-expression-literals": { "version": "1.0.1" }, + "babel-plugin-inline-environment-variables": { + "version": "1.0.1" + }, "babel-plugin-property-literals": { "version": "1.0.1" }, @@ -66,12 +66,12 @@ "babel-plugin-react-constant-elements": { "version": "1.0.3" }, - "babel-plugin-react-display-name": { - "version": "1.0.3" - }, "babel-plugin-remove-console": { "version": "1.0.1" }, + "babel-plugin-react-display-name": { + "version": "1.0.3" + }, "babel-plugin-remove-debugger": { "version": "1.0.1" }, @@ -99,12 +99,12 @@ "breakable": { "version": "1.0.0" }, - "camelcase": { - "version": "1.2.1" - }, "chalk": { "version": "1.1.1" }, + "camelcase": { + "version": "1.2.1" + }, "character-parser": { "version": "1.2.1" }, @@ -132,24 +132,24 @@ "css-stringify": { "version": "1.0.5" }, - "debug": { - "version": "2.2.0" - }, "decamelize": { "version": "1.0.0" }, + "debug": { + "version": "2.2.0" + }, "detect-indent": { "version": "3.0.1" }, "diff": { "version": "1.4.0" }, - "escape-string-regexp": { - "version": "1.0.3" - }, "esprima-fb": { "version": "15001.1.0-dev-harmony-fb" }, + "escape-string-regexp": { + "version": "1.0.3" + }, "esutils": { "version": "2.0.2" }, @@ -159,12 +159,12 @@ "get-stdin": { "version": "4.0.1" }, - "globals": { - "version": "6.4.1" - }, "graceful-fs": { "version": "3.0.8" }, + "globals": { + "version": "6.4.1" + }, "graceful-readlink": { "version": "1.0.1" }, @@ -174,27 +174,27 @@ "has-ansi": { "version": "2.0.0" }, - "home-or-tmp": { - "version": "1.0.0" - }, "iconv-lite": { "version": "0.4.11" }, + "home-or-tmp": { + "version": "1.0.0" + }, "inflight": { "version": "1.0.4" }, "inherits": { "version": "2.0.1" }, - "is-finite": { - "version": "1.0.1" - }, "install": { "version": "0.1.8" }, "is-integer": { "version": "1.0.6" }, + "is-finite": { + "version": "1.0.1" + }, "is-promise": { "version": "2.1.0" }, @@ -219,24 +219,24 @@ "lodash": { "version": "3.10.1" }, - "lru-cache": { - "version": "2.7.0" - }, "minimatch": { "version": "2.0.10" }, + "lru-cache": { + "version": "2.7.0" + }, "minimist": { "version": "1.2.0" }, "ms": { "version": "0.7.1" }, - "number-is-nan": { - "version": "1.0.0" - }, "once": { "version": "1.3.2" }, + "number-is-nan": { + "version": "1.0.0" + }, "optimist": { "version": "0.3.7" }, @@ -285,12 +285,12 @@ "resolve": { "version": "1.1.6" }, - "shebang-regex": { - "version": "1.0.0" - }, "sigmund": { "version": "1.0.1" }, + "shebang-regex": { + "version": "1.0.0" + }, "simple-fmt": { "version": "0.1.0" }, @@ -306,12 +306,12 @@ "stable": { "version": "0.1.5" }, - "stringmap": { - "version": "0.2.2" - }, "stringset": { "version": "0.2.1" }, + "stringmap": { + "version": "0.2.2" + }, "strip-ansi": { "version": "3.0.0" }, @@ -373,14 +373,6 @@ } } }, - "glob": { - "version": "4.2.2", - "dependencies": { - "minimatch": { - "version": "1.0.0" - } - } - }, "jade": { "version": "1.11.0", "dependencies": { @@ -389,6 +381,14 @@ } } }, + "glob": { + "version": "4.2.2", + "dependencies": { + "minimatch": { + "version": "1.0.0" + } + } + }, "jstransformer": { "version": "0.0.2", "dependencies": { diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index 33b5a03eca..e5b5ffd778 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -3,7 +3,7 @@ Package.describe({ summary: "Parser/transpiler for ECMAScript 2015+ syntax", // Tracks the npm version below. Use wrap numbers to increment // without incrementing the npm version. - version: '5.8.24-plugins.0' + version: '5.8.24' }); Npm.depends({ diff --git a/packages/base64/package.js b/packages/base64/package.js index 0f1f39ed42..6e37b01884 100644 --- a/packages/base64/package.js +++ b/packages/base64/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Base64 encoding and decoding", - version: '1.0.4-plugins.0' + version: '1.0.4' }); Package.onUse(function (api) { diff --git a/packages/binary-heap/package.js b/packages/binary-heap/package.js index 03534af583..da8ec81e1c 100644 --- a/packages/binary-heap/package.js +++ b/packages/binary-heap/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Binary Heap datastructure implementation", - version: '1.0.4-plugins.0' + version: '1.0.4' }); Package.onUse(function (api) { diff --git a/packages/blaze-html-templates/package.js b/packages/blaze-html-templates/package.js index 12f570a747..2268dbe766 100644 --- a/packages/blaze-html-templates/package.js +++ b/packages/blaze-html-templates/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'blaze-html-templates', - version: '1.0.0', + version: '1.0.1', // Brief, one-line summary of the package. summary: 'Compile HTML templates into reactive UI with Meteor Blaze', // By default, Meteor will default to using README.md for documentation. diff --git a/packages/blaze-tools/package.js b/packages/blaze-tools/package.js index c72e2061a1..e7f5199b79 100644 --- a/packages/blaze-tools/package.js +++ b/packages/blaze-tools/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Compile-time tools for Blaze", - version: '1.0.4-plugins.0' + version: '1.0.4' }); Package.onUse(function (api) { diff --git a/packages/blaze/builtins.js b/packages/blaze/builtins.js index 3608baf9d1..4d883bcd97 100644 --- a/packages/blaze/builtins.js +++ b/packages/blaze/builtins.js @@ -171,16 +171,15 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) { }, { addedAt: function (id, item, index) { Tracker.nonreactive(function () { - var newDataContext; + var newItemView; if (eachView.variableName) { // new-style #each (as in {{#each item in items}}) - // the new data context is the same - newDataContext = Blaze.getData(eachView); + // doesn't create a new data context + newItemView = Blaze.View('item', eachView.contentFunc); } else { - newDataContext = item; + newItemView = Blaze.With(item, eachView.contentFunc); } - var newItemView = Blaze.With(newDataContext, eachView.contentFunc); eachView.numItems++; var bindings = {}; diff --git a/packages/blaze/package.js b/packages/blaze/package.js index f4e05e0471..b3e602233d 100644 --- a/packages/blaze/package.js +++ b/packages/blaze/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor Reactive Templating library", - version: '2.1.3-plugins.0' + version: '2.1.3' }); Package.onUse(function (api) { diff --git a/packages/boilerplate-generator/package.js b/packages/boilerplate-generator/package.js index a0ae8f1044..ec27371d49 100644 --- a/packages/boilerplate-generator/package.js +++ b/packages/boilerplate-generator/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Generates the boilerplate html from program's manifest", - version: '1.0.4-plugins.0' + version: '1.0.4' }); Package.onUse(function (api) { @@ -12,7 +12,8 @@ Package.onUse(function (api) { // spacebars compiler rather than letting the 'templating' package (which // isn't fully supported on the server yet) handle it. That also means that // they don't contain the outer " + +