├── .DS_Store ├── src ├── .DS_Store ├── public │ ├── styles │ │ ├── typography.scss │ │ ├── ads.scss │ │ ├── header.scss │ │ ├── layout.scss │ │ ├── profile.scss │ │ ├── timeline.scss │ │ ├── index.scss │ │ ├── signup.scss │ │ └── base.scss │ ├── .DS_Store │ ├── images │ │ ├── ad-bacon.jpg │ │ ├── ad-bear.jpeg │ │ ├── ad-hair.jpg │ │ ├── profile1.jpg │ │ ├── profile2.jpg │ │ ├── ad-dundee.png │ │ ├── ad-russia.jpg │ │ ├── profile1a.jpg │ │ ├── ad-hasslehoff.png │ │ └── ad-goldbathroom.jpg │ ├── .eslintrc │ ├── scripts │ │ ├── userModel.js │ │ ├── adModel.js │ │ ├── rantModel.js │ │ ├── userView.js │ │ ├── index.js │ │ ├── addRantView.js │ │ ├── analytics.js │ │ ├── adListView.js │ │ └── rantListView.js │ ├── vendor │ │ ├── analytics.js │ │ ├── lodash.js │ │ ├── backbone.js │ │ ├── tracker.js │ │ └── moment.js │ ├── signup.html │ └── index.html ├── controllers │ ├── analyticsController.js │ ├── usersController.js │ ├── adsController.js │ └── rantsController.js └── index.js ├── .gitignore ├── test └── e2e │ ├── .eslintrc │ └── ranting.spec.js ├── gulpfile.js ├── exercises ├── 2_clear_rant_text.md ├── 8_Network_Not_Defined.md ├── 4_Substr_of_Null.md ├── 7_Third_Party.md ├── 6_Load_Performance.md ├── charles │ ├── CharlesThrottle-3GSubway.xml │ └── CharlesRewrite-AnalyticsFail.xml ├── 5_Memory_Leak.md ├── 3_400_Response.md └── 1_delete_rant.md ├── package.json ├── LICENSE ├── README.md ├── .eslintrc └── import.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/public/styles/typography.scss: -------------------------------------------------------------------------------- 1 | 2 | .text-sm { 3 | font-size: 12px; 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | log 3 | node_modules 4 | 5 | # Built Assets 6 | src/public/index.css 7 | -------------------------------------------------------------------------------- /src/public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/.DS_Store -------------------------------------------------------------------------------- /src/public/images/ad-bacon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-bacon.jpg -------------------------------------------------------------------------------- /src/public/images/ad-bear.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-bear.jpeg -------------------------------------------------------------------------------- /src/public/images/ad-hair.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-hair.jpg -------------------------------------------------------------------------------- /src/public/images/profile1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/profile1.jpg -------------------------------------------------------------------------------- /src/public/images/profile2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/profile2.jpg -------------------------------------------------------------------------------- /src/public/images/ad-dundee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-dundee.png -------------------------------------------------------------------------------- /src/public/images/ad-russia.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-russia.jpg -------------------------------------------------------------------------------- /src/public/images/profile1a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/profile1a.jpg -------------------------------------------------------------------------------- /src/public/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "$": true, 4 | "Backbone": true, 5 | "moment": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/public/images/ad-hasslehoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-hasslehoff.png -------------------------------------------------------------------------------- /src/public/images/ad-goldbathroom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toddhgardner/getrantr/HEAD/src/public/images/ad-goldbathroom.jpg -------------------------------------------------------------------------------- /src/public/scripts/userModel.js: -------------------------------------------------------------------------------- 1 | var UserModel = Backbone.Model.extend({ 2 | idAttribute: '_id', 3 | url: '/api/user/' 4 | }); 5 | -------------------------------------------------------------------------------- /test/e2e/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | env: { 3 | browser: true, 4 | jasmine: true 5 | }, 6 | globals: { 7 | browser: true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/controllers/analyticsController.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (app, db) => { 3 | 4 | app.get('/api/analytics/*', (req, res, next) => { 5 | res.json({}); 6 | next(); 7 | }); 8 | 9 | }; 10 | -------------------------------------------------------------------------------- /src/public/scripts/adModel.js: -------------------------------------------------------------------------------- 1 | var AdModel = Backbone.Model.extend({ 2 | idAttribute: '_id' 3 | }); 4 | 5 | var AdCollection = Backbone.Collection.extend({ 6 | url: '/api/ads/', 7 | model: AdModel 8 | }); 9 | -------------------------------------------------------------------------------- /src/public/scripts/rantModel.js: -------------------------------------------------------------------------------- 1 | var RantModel = Backbone.Model.extend({ 2 | idAttribute: '_id' 3 | }); 4 | 5 | var RantCollection = Backbone.Collection.extend({ 6 | url: '/api/rants/', 7 | comparator: 'timestamp', 8 | model: RantModel 9 | }); 10 | -------------------------------------------------------------------------------- /src/public/styles/ads.scss: -------------------------------------------------------------------------------- 1 | #ads { 2 | margin: 20px 5px 0 0; 3 | position: fixed; 4 | 5 | .ad { 6 | border: 1px solid #ccc; 7 | background-color: lighten(#ccc, 15%); 8 | border-radius: 3px; 9 | padding: 10px; 10 | margin: 10px 0 20px; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/controllers/usersController.js: -------------------------------------------------------------------------------- 1 | module.exports = (app, db) => { 2 | 3 | app.get('/api/user', function(req, res, next) { 4 | db.users 5 | .find({}) 6 | .sort({ timestamp: 1 }) 7 | .exec((err, docs) => { 8 | res.json(docs[0]); 9 | next(); 10 | }); 11 | }); 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /test/e2e/ranting.spec.js: -------------------------------------------------------------------------------- 1 | describe('getRANTr', () => { 2 | 3 | // Smoke test case 4 | it('has correct page title', () => { 5 | return browser 6 | .url('http://www.getRANTr.com:9000/') 7 | .getTitle() 8 | .then((title) => { 9 | expect(title).toBe('getRANTr'); 10 | }); 11 | }); 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /src/public/styles/header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | height: $header-height; 3 | width: 100%; 4 | margin: 0; 5 | position: fixed; 6 | background-color: $color-complement-4; 7 | color: white; 8 | z-index: 2; 9 | 10 | .titlebar { 11 | padding: 10px 20px; 12 | h1 { 13 | margin: 0; padding: 0; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/public/styles/layout.scss: -------------------------------------------------------------------------------- 1 | .col { 2 | float: left; 3 | margin-top: $header-height; 4 | margin-bottom: $spacing-unit; 5 | padding-right: $spacing-unit; 6 | 7 | &.col-1 { 8 | width: 200px; 9 | } 10 | 11 | &.col-2 { 12 | width: calc(60% - 230px + (#{$spacing-unit} * 2)); 13 | } 14 | 15 | &.col-3 { 16 | width: calc(40% - 230px + (#{$spacing-unit} * 2)); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/public/styles/profile.scss: -------------------------------------------------------------------------------- 1 | .profile { 2 | padding: 20px; 3 | position: fixed; 4 | top: $header-height; 5 | bottom: 0; 6 | background-color: lighten($color-complement-2, 20%); 7 | 8 | img { 9 | width: 160px; 10 | border-radius: 15px; 11 | padding-bottom: 10px; 12 | } 13 | 14 | .name { 15 | font-size: 18px; 16 | } 17 | 18 | .join { 19 | font-size: 16px; 20 | color: $text-color-weak; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var nodemon = require('gulp-nodemon'); 3 | var gulpSass = require('gulp-sass')(require('sass')); 4 | 5 | function sass() { 6 | return gulp.src('./src/public/styles/index.scss') 7 | .pipe(gulpSass({ outputStyle: 'compressed' }).on('error', gulpSass.logError)) 8 | .pipe(gulp.dest('./src/public/')); 9 | } 10 | 11 | exports.build = sass; 12 | 13 | exports.start = gulp.series(exports.build, () => { 14 | return nodemon({ 15 | script: 'src/index.js', 16 | tasks: ['build'], 17 | ext: 'js html scss', 18 | env: { 'NODE_ENV': 'development' } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/public/scripts/userView.js: -------------------------------------------------------------------------------- 1 | var UserView = Backbone.View.extend({ 2 | 3 | template: function(user) { 4 | return `
5 | ${user.name} 6 |
Ranting as
${user.name}
7 |
Since ${moment(user.joinedOn).format('MMMM YYYY')}
8 |
`; 9 | }, 10 | 11 | initialize: function() { 12 | this.model.on('change', this.render, this); 13 | }, 14 | 15 | render: function() { 16 | this.$el.html(this.template(this.model.toJSON())); 17 | return this; 18 | } 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /src/public/scripts/index.js: -------------------------------------------------------------------------------- 1 | /* global AdCollection, AdListView, AddRantView, RantCollection, RantListView, UserModel, UserView */ 2 | 3 | $(function() { 4 | 'use strict'; 5 | 6 | console.log('I feel a rant coming on.'); 7 | 8 | var ads = new AdCollection(); 9 | var rants = new RantCollection(); 10 | rants.user = new UserModel(); 11 | 12 | var adList = new AdListView({ 13 | el: $('#ads'), 14 | collection: ads 15 | }); 16 | 17 | var userView = new UserView({ 18 | el: $('#user'), 19 | model: rants.user 20 | }); 21 | 22 | var addRant = new AddRantView({ 23 | el: $('#add-rant'), 24 | collection: rants 25 | }); 26 | 27 | var timeline = new RantListView({ 28 | el: $('#timeline'), 29 | collection: rants 30 | }); 31 | 32 | rants.user.fetch(); 33 | rants.fetch(); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var Datastore = require('nedb'); 2 | var express = require('express'); 3 | 4 | var app = express(); 5 | var db = {}; 6 | db.ads = new Datastore({ filename: './data/ads.db', autoload: true }); 7 | db.users = new Datastore({ filename: './data/users.db', autoload: true }); 8 | db.rants = new Datastore({ filename: './data/rants.db', autoload: true }); 9 | 10 | // app.use(function(req, res, next) { 11 | // res.header('Access-Control-Allow-Origin', 'http://www.getrantr.com:9000'); 12 | // next(); 13 | // }); 14 | 15 | require('./controllers/adsController.js')(app, db); 16 | require('./controllers/analyticsController.js')(app, db); 17 | require('./controllers/rantsController.js')(app, db); 18 | require('./controllers/usersController.js')(app, db); 19 | 20 | app.use('/', express.static(__dirname + '/public/')); 21 | 22 | app.listen(9000); 23 | -------------------------------------------------------------------------------- /src/public/scripts/addRantView.js: -------------------------------------------------------------------------------- 1 | var AddRantView = Backbone.View.extend({ 2 | 3 | initialize: function() { 4 | this.$('textarea').on('keyup', this.onChange.bind(this)); 5 | this.$('form').on('submit', this.onSubmit.bind(this)); 6 | var text = localStorage.getItem('next-rant'); 7 | if (text) { 8 | this.$('textarea').val(text); 9 | } 10 | }, 11 | 12 | onChange: function(evt) { 13 | var text = (evt.target || {}).value; 14 | if (text) { 15 | localStorage.setItem('next-rant', text); 16 | } 17 | }, 18 | 19 | onSubmit: function(evt) { 20 | evt.preventDefault(); 21 | var form = evt.target; 22 | var rant = { 23 | text: form.rant.value 24 | }; 25 | 26 | analytics.trackConversion(); 27 | this.collection.create(rant, { wait: true }); 28 | 29 | form.rant.value = ''; 30 | } 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /src/public/vendor/analytics.js: -------------------------------------------------------------------------------- 1 | /* global window, XMLHttpRequest */ 2 | 3 | (function() { 4 | 'use strict'; 5 | 6 | function trackConversion(callback) { 7 | var xhr = new XMLHttpRequest(); 8 | xhr.open('GET', 'http://www.getrantr.com:9000/api/analytics/conversion'); 9 | xhr.addEventListener('readystatechange', function() { 10 | if (xhr.readyState === 4) { 11 | if (callback) callback(); 12 | } 13 | }); 14 | xhr.send(); 15 | } 16 | 17 | // New Feature! Now we can automatically Intercept all form submissions as a conversion event. 18 | window.addEventListener('submit', function(evt) { 19 | var form = evt.target; 20 | if (form.hasAttribute('ignore')) { return; } 21 | 22 | evt.preventDefault(); 23 | 24 | trackConversion(function() { 25 | form.submit(); 26 | }); 27 | 28 | }, true); 29 | 30 | window.analytics = { 31 | 32 | trackConversion: trackConversion 33 | 34 | }; 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /src/public/scripts/analytics.js: -------------------------------------------------------------------------------- 1 | /* global window, XMLHttpRequest */ 2 | 3 | (function() { 4 | 'use strict'; 5 | 6 | function trackConversion(callback) { 7 | var xhr = new XMLHttpRequest(); 8 | xhr.open('GET', 'http://www.getrantr.com:9000/api/analytics/conversion'); 9 | xhr.addEventListener('readystatechange', function() { 10 | if (xhr.readyState === 4) { 11 | if (callback) callback(); 12 | } 13 | }); 14 | xhr.send(); 15 | } 16 | 17 | // New Feature! Now we can automatically Intercept all form submissions as a conversion event. 18 | window.addEventListener('submit', function(evt) { 19 | var form = evt.target; 20 | if (form.hasAttribute('ignore')) { return; } 21 | 22 | evt.preventDefault(); 23 | 24 | trackConversion(function() { 25 | form.submit(); 26 | }); 27 | 28 | }, true); 29 | 30 | window.analytics = { 31 | 32 | trackConversion: trackConversion 33 | 34 | }; 35 | 36 | })(); 37 | -------------------------------------------------------------------------------- /exercises/2_clear_rant_text.md: -------------------------------------------------------------------------------- 1 | Exercise 2 - Clear Rant Text 2 | ================ 3 | 4 | # Goal 5 | Page should clear draft rant text when cleared. 6 | 7 | 8 | # Evidence 9 | - User enters some text into the rant box. 10 | - User select-all, and deletes the text 11 | - User reloads the page, see original text in the rant box. 12 | 13 | 14 | # Concepts 15 | - Chrome DOM Event Listeners 16 | - Chrome Application Storage 17 | - Chrome Source debugger and stepping 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | # Spoilers (Try Yourself First) 27 | 28 | ## Key Files 29 | 30 | - `/src/public/scripts/addRantView.js:onChange()` Empty text value will be falsy 31 | 32 | ``` 33 | onChange: function(evt) { 34 | var text = (evt.target || {}).value; 35 | if (typeof text === 'string') { // <-- Change this to check for string 36 | localStorage.setItem('next-rant', text); 37 | } 38 | }, 39 | ``` 40 | 41 | ## Solution Links 42 | 43 | - [Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) 44 | -------------------------------------------------------------------------------- /exercises/8_Network_Not_Defined.md: -------------------------------------------------------------------------------- 1 | Exercise 8 - Network is Not Defined 2 | ================ 3 | 4 | # Goal 5 | Notify monitoring and user in case of major dependency failure. 6 | 7 | 8 | # Evidence 9 | - analytics and $ (jQuery) will sometimes not be defined based on monitoring 10 | - Comment out the jQuery script, how does the UI appear 11 | - Prove by simulating flaky networks with Charles or Fiddler 12 | 13 | 14 | # Concepts 15 | - [Progressive Enhancement](http://alistapart.com/article/understandingprogressiveenhancement) 16 | - Failure fallbacks 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | # Spoilers (Try Yourself First) 27 | 28 | ## Key Files 29 | 30 | - `/src/public/scripts/index.js` Main listener. Check for existence and show warning. 31 | 32 | ``` 33 | if (!$) { 34 | console.error('Failed to load jQuery dependency.'); 35 | document.body.innerHTML = '

Sorry! An error occurred loading the application. Please try again.

'; 36 | } 37 | ``` 38 | 39 | ## Solution Links 40 | -------------------------------------------------------------------------------- /exercises/4_Substr_of_Null.md: -------------------------------------------------------------------------------- 1 | Exercise 4 - Substr of Null 2 | ================ 3 | 4 | # Goal 5 | Why does a single user get `cannot read substr of null` errors? 6 | 7 | 8 | # Evidence 9 | - Using "Alt Todd" Account (`node import 3`) 10 | - UI is not fully shown 11 | - Error visible in console 12 | 13 | 14 | # Concepts 15 | - Chrome Network Inspector and Preview 16 | - Chrome Source debugger and stepping 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | # Spoilers (Try Yourself First) 26 | 27 | ## Key Files 28 | 29 | - `/src/public/scripts/rantListView.js:render()` Should check for valid rant before rendering 30 | 31 | ``` 32 | render: function() { 33 | var rant = this.model.toJSON(); 34 | if (typeof rant.text !== 'string') { // <-- add this validation check 35 | console.error('invalid rant data', rant); 36 | rant.text = 'Sorry! An error occurred with this rant. Our team is on it!'; 37 | } 38 | 39 | this.$el.html(this.template(rant)); 40 | return this; 41 | }, 42 | ``` 43 | 44 | ## Solution Links 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getRANTr", 3 | "version": "2.1.0", 4 | "description": "JavaScript Debugging Playground", 5 | "main": "public/index.html", 6 | "scripts": { 7 | "nuke": "rimraf node_modules & npm install", 8 | "postinstall": "node import todd", 9 | "start": "gulp start" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/toddhgardner/soliloquy2.git" 14 | }, 15 | "author": "Todd H Gardner ", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/toddhgardner/getRANTr/issues" 19 | }, 20 | "homepage": "https://github.com/toddhgardner/getRANTr#readme", 21 | "engines": { 22 | "node": "18.18.2" 23 | }, 24 | "dependencies": { 25 | "body-parser": "1.20.2", 26 | "express": "4.18.2", 27 | "nedb": "1.8.0", 28 | "node": "18.18.2" 29 | }, 30 | "devDependencies": { 31 | "gulp": "4.0.2", 32 | "gulp-nodemon": "2.5.0", 33 | "gulp-sass": "5.1.0", 34 | "gulp-util": "3.0.8", 35 | "rimraf": "5.0.5", 36 | "sass": "^1.69.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /exercises/7_Third_Party.md: -------------------------------------------------------------------------------- 1 | Exercise 7 - Third Party 2 | ================ 3 | 4 | # Goal 5 | Why did we get "form.submit is not a function" suddenly? 6 | 7 | 8 | # Evidence 9 | - (Remember to remove the previous network throttle) 10 | - Signup form on `http://www.getrantr.com:9000/signup.html` is broken. 11 | - Code from remove vendor script breaking form submit. 12 | - Fix issue *WITHOUT* changing anything in vendor/analytics.js 13 | 14 | 15 | # Concepts 16 | - Third party scripts. 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | # Spoilers (Try Yourself First) 26 | 27 | ## Key Files 28 | 29 | - `/src/public/scripts/analytics.js:submit listener()` Expects form.submit to always be a function, which is not the case if there is an input with name=submit. Change the form on `signup.html` to not have such an input. 30 | 31 | ``` 32 |
33 | 34 |
35 | ``` 36 | 37 | ## Solution Links 38 | 39 | - [When Form.submit is not a Function](https://trackjs.com/blog/when-form-submit-is-not-a-function/) 40 | -------------------------------------------------------------------------------- /exercises/6_Load_Performance.md: -------------------------------------------------------------------------------- 1 | Exercise 6 - Load Performance 2 | ================ 3 | 4 | # Goal 5 | Locate 3 sources with impact on load performance 6 | 7 | 8 | # Evidence 9 | - Users report the site is slow to load. 10 | - Simulate with Network Throttle: Chrome > Network > Throttle > Good 3G 11 | - Run Timeline: Network, Screenshots, Memory 12 | 13 | 14 | # Concepts 15 | - [Chrome Network Throttle](https://developers.google.com/web/tools/chrome-devtools/network-performance/network-conditions) 16 | - [Chrome Timeline](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool) 17 | - Timeline with Network, Screenshots, and Memory checked 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | # Spoilers (Try Yourself First) 27 | 28 | ## Key Indicators 29 | 30 | - `/src/public/index.html` Blocks for fonts from Google API 31 | - `/src/public/index.html` Loads lots of scripts independently 32 | - `/src/public/scripts/*` Are not compressed or cached 33 | - `/src/public/images/*` Are way to big for what they need to be 34 | 35 | ## Solution Links 36 | 37 | - [Tracking Real World Performance](https://vimeo.com/113715672) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2018 Todd H. Gardner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /exercises/charles/CharlesThrottle-3GSubway.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16384.0 5 | 8192.0 6 | 100 7 | 100 8 | 500 9 | 99 10 | 576 11 | 100 12 | 100 13 | 99 14 | 15 | 16 | 16384.0 17 | 8192.0 18 | 100 19 | 100 20 | 500 21 | 576 22 | 99 23 | 99 24 | 100 25 | 100 26 | 3G on the Subway 27 | 28 | 29 | 30 | 31 | 32 | false 33 | -------------------------------------------------------------------------------- /src/public/styles/timeline.scss: -------------------------------------------------------------------------------- 1 | #add-rant { 2 | 3 | padding: 20px 0; 4 | 5 | label, textarea { 6 | display: block; 7 | margin: 5px 0; 8 | } 9 | 10 | textarea { 11 | width: calc(100% - 14px); 12 | font-size: 16px; 13 | line-height: 20px; 14 | padding: 5px; 15 | height: 70px; 16 | border: 2px solid $color-secondary-1-0; 17 | resize: none; 18 | } 19 | 20 | button { 21 | font-size: 16px; 22 | padding: 4px 8px; 23 | color: white; 24 | background-color: $color-secondary-1-3; 25 | border: 1px solid $color-secondary-1-4; 26 | cursor: pointer; 27 | } 28 | 29 | } 30 | 31 | #timeline { 32 | 33 | margin-top: 40px; 34 | 35 | .rant { 36 | margin: 10px 0 20px; 37 | padding: 10px; 38 | border-bottom: 1px solid $color-complement-2; 39 | 40 | img { 41 | width: 60px; 42 | border-radius: 5px; 43 | position: relative; 44 | left: 0; 45 | } 46 | 47 | .rant-content { 48 | position: relative; 49 | top: -60px; 50 | left: 70px; 51 | width: calc(100% - 70px); 52 | margin-bottom: -60px; 53 | min-height: 60px; 54 | } 55 | form { display: inline; } 56 | .rant-meta { 57 | margin-left: 70px; 58 | } 59 | 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/controllers/adsController.js: -------------------------------------------------------------------------------- 1 | 2 | var ID_CHARS = '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; 3 | function getAdvertiserId() { 4 | var res = ''; 5 | for (var i = 0; i < 5000; i++) { 6 | res += ID_CHARS.charAt(Math.floor(Math.random() * ID_CHARS.length)); 7 | } 8 | return res; 9 | } 10 | 11 | function shuffle(array) { 12 | var currentIndex = array.length, temporaryValue, randomIndex; 13 | 14 | // While there remain elements to shuffle... 15 | while (0 !== currentIndex) { 16 | 17 | // Pick a remaining element... 18 | randomIndex = Math.floor(Math.random() * currentIndex); 19 | currentIndex -= 1; 20 | 21 | // And swap it with the current element. 22 | temporaryValue = array[currentIndex]; 23 | array[currentIndex] = array[randomIndex]; 24 | array[randomIndex] = temporaryValue; 25 | } 26 | 27 | return array; 28 | } 29 | 30 | module.exports = (app, db) => { 31 | 32 | app.get('/api/ads', (req, res, next) => { 33 | db.ads 34 | .find({}) 35 | .exec((err, docs) => { 36 | for (var i = 0; i < docs.length; i++) { 37 | docs[i].advertiserId = getAdvertiserId(); 38 | } 39 | var ads = shuffle(docs); 40 | res.json(ads); 41 | next(); 42 | }); 43 | }); 44 | 45 | }; 46 | -------------------------------------------------------------------------------- /src/controllers/rantsController.js: -------------------------------------------------------------------------------- 1 | var bodyParser = require('body-parser'); 2 | 3 | var jsonParser = bodyParser.json(); 4 | var baseURL = '/api/rants'; 5 | 6 | module.exports = (app, db) => { 7 | 8 | app.get(baseURL, function(req, res, next) { 9 | db.rants 10 | .find({}) 11 | .sort({ timestamp: 1 }) 12 | .exec((err, docs) => { 13 | res.json(docs); 14 | next(); 15 | }); 16 | }); 17 | 18 | app.post(baseURL, jsonParser, (req, res, next) => { 19 | var text = (req.body || {}).text; 20 | if (!text) { 21 | res.status(400).json({ message: 'text must not be empty' }); 22 | return next(); 23 | } 24 | db.users 25 | .find({}) 26 | .limit(1) 27 | .exec((err, user) => { 28 | var rant = { 29 | text: text, 30 | timestamp: new Date().toISOString(), 31 | name: user[0].name, 32 | imageURL: user[0].imageURL 33 | }; 34 | db.rants.insert(rant, (err, saved) => { 35 | res.json(saved); 36 | next(); 37 | }); 38 | }); 39 | }); 40 | 41 | app.delete(baseURL + '/:id', (req, res, next) => { 42 | db.rants.remove({ _id: req.params.id }, (err, removed) => { 43 | res.json({}); 44 | next(); 45 | }); 46 | }); 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /src/public/styles/index.scss: -------------------------------------------------------------------------------- 1 | 2 | $text-color: #111; 3 | $text-color-weak: #828282; 4 | $background-color: #fdfdfd; 5 | 6 | $color-primary-0: #FFDEA8; // Main Primary color */ 7 | $color-primary-1: #FFF2DD; 8 | $color-primary-2: #FFE9C4; 9 | $color-primary-3: #ECC480; 10 | $color-primary-4: #CFA156; 11 | 12 | $color-secondary-1-0: #86CB8F; // Main Secondary color (1) */ 13 | $color-secondary-1-1: #CAEACE; 14 | $color-secondary-1-2: #A9DCAF; 15 | $color-secondary-1-3: #63B76D; 16 | $color-secondary-1-4: #43A04E; 17 | 18 | $color-secondary-2-0: #FFACA8; // Main Secondary color (2) */ 19 | $color-secondary-2-1: #FFDEDD; 20 | $color-secondary-2-2: #FFC7C4; 21 | $color-secondary-2-3: #EC8580; 22 | $color-secondary-2-4: #CF5C56; 23 | 24 | $color-complement-0: #7991B0; // Main Complement color */ 25 | $color-complement-1: #C3CFDE; 26 | $color-complement-2: #9FB0C9; 27 | $color-complement-3: #5A769C; 28 | $color-complement-4: #3F5E88; 29 | 30 | $base-font-family: 'Open Sans', sans-serif; 31 | $base-font-size: 16px; 32 | $base-font-weight: 400; 33 | $small-font-size: $base-font-size * 0.875; 34 | $base-line-height: 1.2; 35 | $spacing-unit: 30px; 36 | $header-height: 60px; 37 | 38 | 39 | @import 'base'; 40 | @import 'layout'; 41 | @import 'typography'; 42 | @import 'header'; 43 | @import 'profile'; 44 | @import 'timeline'; 45 | @import 'ads'; 46 | @import 'signup'; 47 | -------------------------------------------------------------------------------- /src/public/styles/signup.scss: -------------------------------------------------------------------------------- 1 | .page-signup { 2 | 3 | width: 100%; 4 | margin: 20px 0; 5 | 6 | .signup-form { 7 | margin: 0 auto; 8 | width: 420px; 9 | background-color: white; 10 | box-shadow: 0px 0px 10px 4px rgba(0,0,0,0.3); 11 | 12 | a:hover, 13 | a:focus, 14 | a:active { 15 | color: black; 16 | } 17 | 18 | h2 { 19 | text-align: center; 20 | padding: 15px; 21 | margin: 0; 22 | background-color: $color-complement-4; 23 | color: white; 24 | } 25 | 26 | .signup-form-preamble { 27 | padding: 15px 0; 28 | text-align: center; 29 | border-bottom: 1px solid #f5f5f5; 30 | } 31 | 32 | .signup-form-wrapper { 33 | padding: 0px 15px; 34 | color: black; 35 | } 36 | 37 | .fields-wrap { 38 | margin: 20px 0; 39 | } 40 | .field-wrap { 41 | margin: 10px auto 20px; 42 | width: 320px; 43 | } 44 | .field-desc { 45 | color: #ddd; 46 | } 47 | 48 | .signup-action { 49 | padding: 20px 0; 50 | width: 100%; 51 | text-align: center; 52 | } 53 | 54 | } 55 | 56 | button { 57 | font-size: 24px; 58 | padding: 4px 8px; 59 | color: white; 60 | background-color: $color-secondary-1-3; 61 | border: 1px solid $color-secondary-1-4; 62 | cursor: pointer; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /exercises/5_Memory_Leak.md: -------------------------------------------------------------------------------- 1 | Exercise 5 - Memory Leak 2 | ================ 3 | 4 | # Goal 5 | Locate the source of the largest uncollected memory allocations in getRANTR 6 | 7 | 8 | # Evidence 9 | - Using "Keystone" Account (`node import 2`) 10 | - Users report slowing performance 11 | - Conduct a memory profile (for at least 30 seconds) 12 | 13 | 14 | # Concepts 15 | - [Chrome Allocation Snapshots](https://developers.google.com/web/tools/chrome-devtools/memory-problems/allocation-profiler) 16 | - [Chrome Timeline](https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/timeline-tool) 17 | - Timeline with JS Profile and Memory checked 18 | 19 | 20 | 21 | 22 | 23 | 24 | # Spoilers (Try Yourself First) 25 | 26 | ## Key Files 27 | 28 | - `/src/public/scripts/adListView.js:render()` Clears container node without destroying children. 29 | 30 | ``` 31 | render: function() { 32 | this.el.innerHTML = ''; 33 | this.children.forEach((view) => { view.remove(); }); // <-- optional, cleanup each child 34 | this.children.length = 0; // <-- release children for garbage cleanup 35 | this.collection.each((model) => { 36 | this.renderAd(model); 37 | }); 38 | return this; 39 | }, 40 | ``` 41 | 42 | ## Solution Links 43 | 44 | - [Chrome Memory Debugging](https://developers.google.com/web/tools/chrome-devtools/memory-problems/) 45 | -------------------------------------------------------------------------------- /src/public/scripts/adListView.js: -------------------------------------------------------------------------------- 1 | var AdView = Backbone.View.extend({ 2 | 3 | template: function(ad) { 4 | return `
5 | 6 | 7 | 8 |
`; 9 | }, 10 | 11 | render: function() { 12 | this.$el.html(this.template(this.model.toJSON())); 13 | return this; 14 | } 15 | 16 | }); 17 | 18 | var AdListView = Backbone.View.extend({ 19 | 20 | children: [], 21 | 22 | initialize: function() { 23 | this.collection.on('reset', this.render, this); 24 | this.collection.on('add', this.renderAd, this); 25 | this.$el.on('click', function(evt) { 26 | var clicked = evt.target.parentElement; 27 | if (clicked.matches('.ad-link')) { 28 | recordClickAnalytics(clicked.getAttribute('data-advertiserId')); 29 | } 30 | }); 31 | // start ad rotation 32 | this.collection.fetch(); 33 | setInterval(function() { 34 | this.collection.fetch({ reset: true }); 35 | }.bind(this), 10000); 36 | }, 37 | 38 | render: function() { 39 | this.el.innerHTML = ''; 40 | this.collection.each((model) => { 41 | this.renderAd(model); 42 | }); 43 | return this; 44 | }, 45 | 46 | renderAd: function(ad) { 47 | var view = new AdView({ 48 | model: ad 49 | }); 50 | this.$el.prepend(view.render().$el); 51 | this.children.push(view); 52 | } 53 | 54 | }); 55 | 56 | 57 | function recordClickAnalytics() { 58 | console.log('someone clicked on an ad!!'); 59 | } 60 | -------------------------------------------------------------------------------- /exercises/3_400_Response.md: -------------------------------------------------------------------------------- 1 | Exercise 3 - 400 Server Responses 2 | ================ 3 | 4 | # Goal 5 | Identify cause and resolve 400's responses from the server. 6 | 7 | 8 | # Evidence 9 | - Monitoring reporting large numbers of 400 responses from `POST /api/rants` 10 | - User enters 0 characters into textarea#rant_text 11 | - User clicks submit button 12 | 13 | 14 | # Concepts 15 | - Chrome Network Inspector and Preview 16 | - Chrome Source debugger and stepping 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | # Spoilers (Try Yourself First) 30 | 31 | ## Key Files 32 | 33 | - `/src/public/scripts/addRantView.js:onSubmit()` Should check for valid input 34 | 35 | ``` 36 | onSubmit: function(evt) { 37 | evt.preventDefault(); 38 | var form = evt.target; 39 | var rant = { 40 | text: form.rant.value 41 | }; 42 | 43 | if (rant.text && rant.text.length > 0) { // <-- add this validation check 44 | analytics.trackConversion(); 45 | this.collection.create(rant, { wait: true }); 46 | 47 | form.rant.value = ''; 48 | } 49 | } 50 | ``` 51 | 52 | - `/src/public/index.html` textarea should have required attribute 53 | 54 | ``` 55 |
56 | 57 | 58 | 59 |
60 | 61 |
62 |
63 | ``` 64 | 65 | ## Solution Links 66 | 67 | - [Input:Required](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input) 68 | -------------------------------------------------------------------------------- /src/public/scripts/rantListView.js: -------------------------------------------------------------------------------- 1 | var RantView = Backbone.View.extend({ 2 | 3 | template: function(rant) { 4 | return `
5 | ${rant.name} 6 |
7 |
${rant.name}
8 |
${rant.text.substr(0, 140)}
9 |
10 |
11 |
12 |
${moment(rant.timestamp).fromNow()}
13 |
14 |
`; 15 | }, 16 | 17 | initialize: function() { 18 | this.model.on('change', this.render, this); 19 | this.$el.on('click', function(evt) { 20 | if (evt.target.matches('.js-delete')) { 21 | this.onDelete(evt); 22 | } 23 | }.bind(this)); 24 | }, 25 | 26 | render: function() { 27 | this.$el.html(this.template(this.model.toJSON())); 28 | return this; 29 | }, 30 | 31 | onDelete: function() { 32 | setTimeout(function() { 33 | this.model.destroy(); 34 | this.remove(); 35 | }); 36 | } 37 | 38 | }); 39 | 40 | var RantListView = Backbone.View.extend({ 41 | 42 | initialize: function() { 43 | this.collection.on('reset', this.render, this); 44 | this.collection.on('add', this.renderStatement, this); 45 | }, 46 | 47 | render: function() { 48 | this.$el.html(''); 49 | this.collection.each(this.renderStatement, this); 50 | return this; 51 | }, 52 | 53 | renderStatement: function(model) { 54 | var view = new RantView({ model: model }); 55 | this.$el.prepend(view.render().$el); 56 | } 57 | 58 | }); 59 | -------------------------------------------------------------------------------- /exercises/1_delete_rant.md: -------------------------------------------------------------------------------- 1 | Exercise 1 - Delete Rant 2 | ================ 3 | 4 | # Goal 5 | User should be able to delete a rant from their timeline using the "delete" control. 6 | 7 | 8 | # Evidence 9 | - User cannot delete a rant from their timeline. 10 | 11 | 12 | # Concepts 13 | - Using Keystone Users (`node import 2`) 14 | - [Setting up Workspaces](https://developers.google.com/web/tools/setup/setup-workflow) 15 | - Chrome > Sources > Context Menu > Add Folder to Workspace 16 | - Select the getRANTR/src/public Folder 17 | - Allow Chrome filesystem access 18 | - Chrome > Sources > Context Menu > Map to File System Resource 19 | - Chrome "Persist Log" 20 | - Chrome DOM Event Listeners 21 | - [Chrome Async Stack traces](https://www.html5rocks.com/en/tutorials/developertools/async-call-stack/) 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | # Spoilers (Try Yourself First) 34 | 35 | ## Key Changes 36 | 37 | - `/src/public/scripts/rantListView.js:initialize()` Missing `preventDefault` on event 38 | 39 | ``` 40 | initialize: function() { 41 | this.model.on('change', this.render, this); 42 | this.$el.on('click', function(evt) { 43 | if (evt.target.matches('.js-delete')) { 44 | evt.preventDefault(); // <-- Add This! 45 | this.onDelete(evt); 46 | } 47 | }.bind(this)); 48 | }, 49 | ``` 50 | 51 | - `/src/public/scripts/rantListView.js:onDelete()` Loss of function context (this). 52 | 53 | ``` 54 | // Many possible solutions 55 | onDelete: function() { 56 | setTimeout(function() { 57 | this.model.destroy(); 58 | this.remove(); 59 | }.bind(this)); // <-- Add .bind(this) 60 | } 61 | ``` 62 | 63 | ## More Information 64 | 65 | - [Event PreventDefault](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault) 66 | - [Function Context](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) 67 | -------------------------------------------------------------------------------- /src/public/styles/base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Reset some basic elements 3 | */ 4 | body, h1, h2, h3, h4, h5, h6, 5 | p, blockquote, pre, hr, 6 | dl, dd, ol, ul, figure { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | 12 | 13 | /** 14 | * Basic styling 15 | */ 16 | body { 17 | font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; 18 | color: $text-color; 19 | background-color: $background-color; 20 | -webkit-text-size-adjust: 100%; 21 | -webkit-font-feature-settings: "kern" 1; 22 | -moz-font-feature-settings: "kern" 1; 23 | -o-font-feature-settings: "kern" 1; 24 | font-feature-settings: "kern" 1; 25 | font-kerning: normal; 26 | } 27 | 28 | 29 | 30 | /** 31 | * Set `margin-bottom` to maintain vertical rhythm 32 | */ 33 | h1, h2, h3, h4, h5, h6, 34 | p, blockquote, pre, 35 | ul, ol, dl, figure, 36 | %vertical-rhythm { 37 | margin-bottom: calc($spacing-unit / 2); 38 | } 39 | 40 | 41 | 42 | /** 43 | * Images 44 | */ 45 | img { 46 | max-width: 100%; 47 | vertical-align: middle; 48 | } 49 | 50 | 51 | 52 | /** 53 | * Figures 54 | */ 55 | figure > img { 56 | display: block; 57 | } 58 | 59 | figcaption { 60 | font-size: $small-font-size; 61 | } 62 | 63 | 64 | 65 | /** 66 | * Lists 67 | */ 68 | ul, ol { 69 | margin-left: $spacing-unit; 70 | } 71 | 72 | li { 73 | > ul, 74 | > ol { 75 | margin-bottom: 0; 76 | } 77 | } 78 | 79 | .pull-right { 80 | float:right; 81 | } 82 | .pull-left { 83 | float: left; 84 | } 85 | 86 | 87 | 88 | /** 89 | * Clearfix 90 | */ 91 | %clearfix { 92 | 93 | &:after { 94 | content: ""; 95 | display: table; 96 | clear: both; 97 | } 98 | } 99 | 100 | .clearfix { 101 | 102 | &:after { 103 | content: ""; 104 | display: table; 105 | clear: both; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /exercises/charles/CharlesRewrite-AnalyticsFail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | getRANTr - Analytics Fails 7 | 8 | 9 | 10 | 11 | http 12 | cdn.getrantr.com 13 | 9000 14 | /scripts/analytics.js 15 | 16 | true 17 | 18 | 19 | 20 | 21 | 22 | true 23 | 11 24 | 200 25 | false 26 | false 27 | false 28 | false 29 | 404 30 | false 31 | false 32 | false 33 | false 34 | 2 35 | 36 | 37 | true 38 | 7 39 | 40 | false 41 | false 42 | false 43 | true 44 | 45 | false 46 | false 47 | false 48 | false 49 | 2 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/public/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Signup for getRANTr 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # getRANTR 2 | 3 | As used in Todd Gardner's [Frontend Masters Workshop](https://frontendmasters.com/courses/debugging-javascript/). 4 | 5 | Social connections are overrated-they only lead to trouble, pain, and lawsuits. Modern social networks have given rise to trolls, fake news, and mansplaining. 6 | 7 | Introducing getRANTR, the revolutionary anti-social distributed network. getRANTR gives you all the obsessive status updating of a social network, with non of the misunderstandings and hurt feelings. With getRANTR, no one can see you RANT. 8 | 9 | ## Getting Started 10 | 11 | getRANTR is a distributed network. This means you download the code and run it yourself! No more annoying "public websites" or "cloud infrastructure". getRANTR includes all the fun of running your own technology stack. 12 | 13 | ### Software Dependencies 14 | 15 | getRANTR is built on NodeJS. If you don't have it already, you can download the latest runtime from the [NodeJS website](https://nodejs.org/en/). 16 | 17 | You may also need the following tools to enhance or debug getRANTR: 18 | 19 | - [VS Code](https://code.visualstudio.com/) or another JavaScript-aware text editor 20 | - [Chrome](https://www.google.com/chrome/) web browser 21 | 22 | **Optional** 23 | - [Charles](https://www.charlesproxy.com/) or [Fiddler](http://www.telerik.com/fiddler) web proxy 24 | 25 | ### Installation 26 | 27 | You'll need to install all the local dependencies, **in the same directory as getRANTR** for running getRANTR: 28 | 29 | ``` 30 | npm install 31 | ``` 32 | 33 | ### Running getRANTR 34 | 35 | After setting everything up and installing the dependencies, you can run getRANTR by: 36 | 37 | ``` 38 | npm start 39 | ``` 40 | 41 | This will run getRANTR at `http://www.getRANTR.com:9000/` and will setup to automatically reload if any of the source files are changed. 42 | 43 | ### Repository Structure 44 | 45 | ``` 46 | /build Automation and build tasks 47 | /data Data location for getRANTR users 48 | /exercises Guides and help for workshop exercises 49 | /node_modules There be dragons here 50 | /src Source code 51 | /controllers Server API actions 52 | /public Client-side application (this is probably what you are looking for) 53 | /test Source code tests 54 | ``` 55 | 56 | ### Impersonating Users 57 | 58 | There are three example users loaded into the system. You can impersonate them with the following commands, after closing the node session (`control` and `C` on Mac): 59 | 60 | ``` 61 | node import 1 # Will impersonate Todd's default account 62 | node import 2 # Will impersonate the Keystone user account 63 | node import 3 # Will impersonate Todd's alt account 64 | ``` 65 | 66 | Todd's default account is loaded automatically when you install. 67 | 68 | Happy Ranting! 69 | -------------------------------------------------------------------------------- /src/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | getRANTr 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |

getRANTr – Where it's all about you

15 |
16 |
17 | 18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | parserOptions: { 3 | ecmaVersion: 6, 4 | sourceType: 'module' 5 | }, 6 | env: { 7 | node: true, 8 | es6: true 9 | }, 10 | rules: { 11 | // Possible Errors 12 | 'comma-dangle': [2, 'never'], 13 | 'no-cond-assign': 2, 14 | 'no-constant-condition': 2, 15 | 'no-control-regex': 2, 16 | 'no-debugger': 2, 17 | 'no-dupe-args': 2, 18 | 'no-dupe-keys': 2, 19 | 'no-duplicate-case': 2, 20 | 'no-empty-character-class': 2, 21 | 22 | 'no-ex-assign': 2, 23 | 'no-extra-boolean-cast': 2, 24 | 25 | 'no-extra-semi': 2, 26 | 'no-func-assign': 2, 27 | 'no-inner-declarations': 2, 28 | 'no-invalid-regexp': 2, 29 | 'no-irregular-whitespace': 2, 30 | 'no-negated-in-lhs': 2, 31 | 'no-obj-calls': 2, 32 | 'no-regex-spaces': 2, 33 | 'no-sparse-arrays': 2, 34 | 'no-unreachable': 2, 35 | 'use-isnan': 2, 36 | 'valid-typeof': 2, 37 | 'no-unexpected-multiline': 2, 38 | 39 | // Best Practices 40 | 'accessor-pairs': 2, 41 | 'array-callback-return': 2, 42 | 'block-scoped-var': 2, 43 | 'curly': 2, 44 | 'default-case': 2, 45 | 'dot-notation': 2, 46 | 'dot-location': [2, 'property'], 47 | 'eqeqeq': 2, 48 | 'guard-for-in': 2, 49 | 'no-alert': 2, 50 | 'no-caller': 2, 51 | 'no-case-declarations': 2, 52 | 'no-div-regex': 2, 53 | 'no-else-return': 2, 54 | 'no-empty-pattern': 2, 55 | 'no-eq-null': 2, 56 | 'no-eval': 2, 57 | 'no-extend-native': 2, 58 | 'no-extra-bind': 2, 59 | 'no-extra-label': 2, 60 | 'no-fallthrough': 2, 61 | 'no-floating-decimal': 2, 62 | 'no-implicit-coercion': 2, 63 | 'no-implicit-globals': 2, 64 | 'no-implied-eval': 2, 65 | 'no-iterator': 2, 66 | 'no-labels': 2, 67 | 'no-lone-blocks': 2, 68 | 'no-loop-func': 2, 69 | 70 | 'no-multi-spaces': 0, 71 | 'no-multi-str': 0, 72 | 'no-native-reassign': 2, 73 | 'no-new-func': 2, 74 | 'no-new-wrappers': 2, 75 | 'no-new': 2, 76 | 'no-octal-escape': 2, 77 | 'no-octal': 2, 78 | 'no-proto': 2, 79 | 'no-redeclare': 2, 80 | 'no-return-assign': [2, 'always'], 81 | 'no-script-url': 2, 82 | 'no-self-assign': 2, 83 | 'no-self-compare': 2, 84 | 'no-sequences': 2, 85 | 'no-throw-literal': 2, 86 | 'no-unmodified-loop-condition': 2, 87 | 'no-unused-expressions': 2, 88 | 'no-unused-labels': 2, 89 | 'no-useless-call': 2, 90 | 'no-useless-concat': 2, 91 | 'no-void': 2, 92 | 'no-warning-comments': 1, 93 | 'no-with': 2, 94 | 'radix': 2, 95 | 'require-jsdoc': 0, 96 | 'valid-jsdoc': [2, { 97 | requireReturn: false, 98 | prefer: { 99 | returns: 'return' 100 | } 101 | }], 102 | 'wrap-iife': [2, 'inside'], 103 | 'yoda': 2, 104 | 105 | // Variables 106 | 'no-delete-var': 2, 107 | 'no-label-var': 2, 108 | 'no-shadow-restricted-names': 2, 109 | 'no-undef-init': 2, 110 | 'no-undef': [2, {typeof: true}], 111 | 'no-unused-vars': 0, 112 | 'no-use-before-define': [2, 'nofunc'], 113 | 114 | // Node.js 115 | 116 | 'handle-callback-err': 0, 117 | 'no-mixed-requires': [2, {grouping: true, allowCall: true}], 118 | 'no-new-require': 2, 119 | 'no-path-concat': 2, 120 | 'no-restricted-imports': [2, 'domain', 'freelist', 'smalloc', 'sys', 'colors'], 121 | 'no-restricted-modules': [2, 'domain', 'freelist', 'smalloc', 'sys', 'colors'], 122 | 123 | // Stylistic Issues 124 | 'array-bracket-spacing': [2, 'never'], 125 | 'brace-style': ["error", "stroustrup", { "allowSingleLine": true }], 126 | 'camelcase': [2, {properties: 'always'}], 127 | 'comma-spacing': [2, {before: false, after: true}], 128 | 'comma-style': [2, 'last'], 129 | 'computed-property-spacing': [2, 'never'], 130 | 'eol-last': 2, 131 | 'indent': [2, 2, { 132 | SwitchCase: 1 133 | }], 134 | 'jsx-quotes': 2, 135 | 'key-spacing': [2, {beforeColon: false, afterColon: true}], 136 | 'keyword-spacing': 2, 137 | 'linebreak-style': [2, 'unix'], 138 | 'max-len': 0, 139 | 'max-nested-callbacks': [1, 4], 140 | 'new-cap': [2, {newIsCap: true, capIsNew: true}], 141 | 'new-parens': 2, 142 | 'no-array-constructor': 2, 143 | 'no-lonely-if': 2, 144 | 'no-mixed-spaces-and-tabs': 2, 145 | 'no-multiple-empty-lines': 0, 146 | 'no-nested-ternary': 2, 147 | 'no-negated-condition': 2, 148 | 'no-new-object': 2, 149 | 'no-restricted-syntax': [2, 'WithStatement'], 150 | 'no-whitespace-before-property': 2, 151 | 'no-spaced-func': 2, 152 | 'no-trailing-spaces': 2, 153 | 'no-unneeded-ternary': 2, 154 | 'object-curly-spacing': [2, "always"], 155 | 'one-var': [2, 'never'], 156 | 'one-var-declaration-per-line': 2, 157 | 'operator-assignment': [2, 'always'], 158 | 'operator-linebreak': [2, 'after'], 159 | 'padded-blocks': 0, 160 | 'quote-props': [2, 'consistent-as-needed'], 161 | 'quotes': [2, 'single'], 162 | 'semi-spacing': [2, {before: false, after: true}], 163 | 'semi': [2, 'always'], 164 | 'space-before-blocks': [2, 'always'], 165 | 'space-before-function-paren': [2, 'never'], 166 | 'space-in-parens': [2, 'never'], 167 | 'space-infix-ops': 2, 168 | 'space-unary-ops': 2, 169 | 'spaced-comment': [2, 'always', {markers: ['!']}], 170 | 171 | // ES2015 172 | 'arrow-parens': [2, 'always'], 173 | 'arrow-spacing': [2, {before: true, after: true}], 174 | 'constructor-super': 2, 175 | 'generator-star-spacing': [2, 'both'], 176 | 'no-class-assign': 2, 177 | 'no-const-assign': 2, 178 | 'no-dupe-class-members': 2, 179 | 'no-new-symbol': 2, 180 | 'no-this-before-super': 2, 181 | 'no-useless-constructor': 2, 182 | 'template-curly-spacing': 2, 183 | 'yield-star-spacing': [2, 'both'] 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/public/vendor/lodash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE 4 | * Build: `lodash core -o ./dist/lodash.core.js` 5 | */ 6 | ;(function(){function n(n){return K(n)&&pn.call(n,"callee")&&!bn.call(n,"callee")}function t(n,t){return n.push.apply(n,t),n}function r(n){return function(t){return null==t?nn:t[n]}}function e(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function u(n,t){return j(t,function(t){return n[t]})}function o(n){return n instanceof i?n:new i(n)}function i(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t}function c(n,t,r){if(typeof n!="function")throw new TypeError("Expected a function"); 7 | return setTimeout(function(){n.apply(nn,r)},t)}function f(n,t){var r=true;return mn(n,function(n,e,u){return r=!!t(n,e,u)}),r}function a(n,t,r){for(var e=-1,u=n.length;++et}function y(n,t,r,e,u){return n===t||(null==n||null==t||!K(n)&&!K(t)?n!==n&&t!==t:b(n,t,r,e,y,u))}function b(n,t,r,e,u,o){var i=Nn(n),c=Nn(t),f=i?"[object Array]":hn.call(n),a=c?"[object Array]":hn.call(t),f="[object Arguments]"==f?"[object Object]":f,a="[object Arguments]"==a?"[object Object]":a,l="[object Object]"==f,c="[object Object]"==a,a=f==a;o||(o=[]);var p=An(o,function(t){return t[0]==n}),s=An(o,function(n){ 9 | return n[0]==t});if(p&&s)return p[1]==t;if(o.push([n,t]),o.push([t,n]),a&&!l){if(i)r=B(n,t,r,e,u,o);else n:{switch(f){case"[object Boolean]":case"[object Date]":case"[object Number]":r=M(+n,+t);break n;case"[object Error]":r=n.name==t.name&&n.message==t.message;break n;case"[object RegExp]":case"[object String]":r=n==t+"";break n}r=false}return o.pop(),r}return 1&r||(i=l&&pn.call(n,"__wrapped__"),f=c&&pn.call(t,"__wrapped__"),!i&&!f)?!!a&&(r=R(n,t,r,e,u,o),o.pop(),r):(i=i?n.value():n,f=f?t.value():t, 10 | r=u(i,f,r,e,o),o.pop(),r)}function g(n){return typeof n=="function"?n:null==n?Y:(typeof n=="object"?d:r)(n)}function _(n,t){return nt&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Array(u);++ei))return false;for(var c=-1,f=true,a=2&r?[]:nn;++cr?jn(e+r,0):r:0,r=(r||0)-1;for(var u=t===t;++rarguments.length,mn)}function J(n,t){var r;if(typeof t!="function")throw new TypeError("Expected a function");return n=Fn(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=nn),r}}function M(n,t){return n===t||n!==n&&t!==t}function U(n){var t;return(t=null!=n)&&(t=n.length,t=typeof t=="number"&&-1=t),t&&!V(n)}function V(n){return!!H(n)&&(n=hn.call(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n); 17 | }function H(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function K(n){return null!=n&&typeof n=="object"}function L(n){return typeof n=="number"||K(n)&&"[object Number]"==hn.call(n)}function Q(n){return typeof n=="string"||!Nn(n)&&K(n)&&"[object String]"==hn.call(n)}function W(n){return typeof n=="string"?n:null==n?"":n+""}function X(n){return null==n?[]:u(n,In(n))}function Y(n){return n}function Z(n,r,e){var u=In(r),o=h(r,u);null!=e||H(r)&&(o.length||!u.length)||(e=r,r=n,n=this,o=h(r,In(r))); 18 | var i=!(H(e)&&"chain"in e&&!e.chain),c=V(n);return mn(o,function(e){var u=r[e];n[e]=u,c&&(n.prototype[e]=function(){var r=this.__chain__;if(i||r){var e=n(this.__wrapped__);return(e.__actions__=A(this.__actions__)).push({func:u,args:arguments,thisArg:n}),e.__chain__=r,e}return u.apply(n,t([this.value()],arguments))})}),n}var nn,tn=1/0,rn=/[&<>"']/g,en=RegExp(rn.source),un=typeof self=="object"&&self&&self.Object===Object&&self,on=typeof global=="object"&&global&&global.Object===Object&&global||un||Function("return this")(),cn=(un=typeof exports=="object"&&exports&&!exports.nodeType&&exports)&&typeof module=="object"&&module&&!module.nodeType&&module,fn=function(n){ 19 | return function(t){return null==n?nn:n[t]}}({"&":"&","<":"<",">":">",'"':""","'":"'"}),an=Array.prototype,ln=Object.prototype,pn=ln.hasOwnProperty,sn=0,hn=ln.toString,vn=on._,yn=Object.create,bn=ln.propertyIsEnumerable,gn=on.isFinite,_n=function(n,t){return function(r){return n(t(r))}}(Object.keys,Object),jn=Math.max,dn=function(){function n(){}return function(t){return H(t)?yn?yn(t):(n.prototype=t,t=new n,n.prototype=nn,t):{}}}();i.prototype=dn(o.prototype),i.prototype.constructor=i; 20 | var mn=function(n,t){return function(r,e){if(null==r)return r;if(!U(r))return n(r,e);for(var u=r.length,o=t?u:-1,i=Object(r);(t?o--:++or&&(r=jn(e+r,0));n:{for(t=g(t),e=n.length,r+=-1;++re||o&&c&&a||!u&&a||!i){r=1;break n}if(!o&&r { 12 | console.log('Imported User 1 (Todd)'); 13 | }); 14 | break; 15 | 16 | case '2': 17 | case 'donald': 18 | importDonald(db, () => { 19 | console.log('Imported User 2 (Donald)'); 20 | }); 21 | break; 22 | 23 | case '3': 24 | case 'alttodd': 25 | importAltTodd(db, () => { 26 | console.log('Imported User 3 (Alt Todd)'); 27 | }); 28 | break; 29 | 30 | default: 31 | console.warn(`Invalid import ${process.argv[2]}`); 32 | 33 | } 34 | 35 | function importTodd(db, callback) { 36 | var user = { 37 | name: 'Todd H. Gardner', 38 | imageURL: '/images/profile1.jpg', 39 | joinedOn: '2015-06-01T00:00:00.000Z' 40 | }; 41 | 42 | var rants = []; 43 | rants.push({ 44 | name: user.name, 45 | imageURL: user.imageURL, 46 | text: 'There are only two hard problems in programming: cache invalidation, and being willing to relocate to San Francisco', 47 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 2).toISOString() 48 | }); 49 | rants.push({ 50 | name: user.name, 51 | imageURL: user.imageURL, 52 | text: 'Talk idea: "What If We\'ve Made a Horrible Mistake? JavaScript Without NPM, Webpack, or React."', 53 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 4).toISOString() 54 | }); 55 | rants.push({ 56 | name: user.name, 57 | imageURL: user.imageURL, 58 | text: 'A hard lesson that hits me over and over again: if you want to build software that lasts, aggressively remove dependencies.', 59 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 6).toISOString() 60 | }); 61 | rants.push({ 62 | name: user.name, 63 | imageURL: user.imageURL, 64 | text: 'If body { background: red; } isn’t the first thing you do to test a CSS file is included, then I don’t know what kind of developer you are.', 65 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 10).toISOString() 66 | }); 67 | rants.push({ 68 | name: user.name, 69 | imageURL: user.imageURL, 70 | text: 'How did health insurance pull the scam of counting eyeballs and mouths as not part of the body and not eligible for normal health care?', 71 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 16).toISOString() 72 | }); 73 | rants.push({ 74 | name: user.name, 75 | imageURL: user.imageURL, 76 | text: 'JavaScript sneaks into all sorts of places it shouldn\'t be because its the easiest way to do it on a tight timeline.', 77 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 20).toISOString() 78 | }); 79 | rants.push({ 80 | name: user.name, 81 | imageURL: user.imageURL, 82 | text: '*STEFON VOICE* This release has everything:\n- features\n- bug fixes\n- a root kit from a shadowy government agency\n- and human load balancers', 83 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 26).toISOString() 84 | }); 85 | rants.push({ 86 | name: user.name, 87 | imageURL: user.imageURL, 88 | text: 'People are the lifeblood of this organisation and we respect each employee as an individual.\n\nDoes that answer your question, number 795?', 89 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 28).toISOString() 90 | }); 91 | rants.push({ 92 | name: user.name, 93 | imageURL: user.imageURL, 94 | text: 'Today I learned "Standards are like toothbrushes: Everyone wants one but no one wants to use someone else\'s"', 95 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 32).toISOString() 96 | }); 97 | rants.push({ 98 | name: user.name, 99 | imageURL: user.imageURL, 100 | text: 'While we\'re at it, let\'s cut their budget to a third and redirect the funds to academic and ethical studies.', 101 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 38).toISOString() 102 | }); 103 | rants.push({ 104 | name: user.name, 105 | imageURL: user.imageURL, 106 | text: 'Business have to lose so much money to software resellers--I don\'t understand why this idea exists anymore.', 107 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 48).toISOString() 108 | }); 109 | rants.push({ 110 | name: user.name, 111 | imageURL: user.imageURL, 112 | text: 'McDonalds Breakfast is literally the only reason to go to McDonalds.', 113 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 56).toISOString() 114 | }); 115 | rants.push({ 116 | name: user.name, 117 | imageURL: user.imageURL, 118 | text: 'Startup (noun): Company running on other people\'s money, with a product running on other people\'s computers, selling other people\'s data.', 119 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 60).toISOString() 120 | }); 121 | rants.push({ 122 | name: user.name, 123 | imageURL: user.imageURL, 124 | text: 'just when you think you finally have a hold on IE, Safari comes and slams you with a chair across the back.', 125 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 60).toISOString() 126 | }); 127 | rants.push({ 128 | name: user.name, 129 | imageURL: user.imageURL, 130 | text: 'Hey, @MSEdgeDev I have a reproduceable IE11 Freezing bug, but all I can find is the Edge tracker. Should I report it there?', 131 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 72).toISOString() 132 | }); 133 | 134 | var ads = []; 135 | ads.push({ 136 | advertiserId: 'REPLACE_ME', 137 | imageURL: '/images/ad-bacon.jpg', 138 | targetURL: 'http://www.baconfreak.com/' 139 | }); 140 | ads.push({ 141 | advertiserId: 'REPLACE_ME', 142 | imageURL: '/images/ad-dundee.png', 143 | targetURL: 'https://www.youtube.com/watch?v=POJtaO2xB_o' 144 | }); 145 | ads.push({ 146 | advertiserId: 'REPLACE_ME', 147 | imageURL: '/images/ad-bear.jpeg', 148 | targetURL: 'http://www.bear.org/website/' 149 | }); 150 | ads.push({ 151 | advertiserId: 'REPLACE_ME', 152 | imageURL: '/images/ad-hasslehoff.png', 153 | targetURL: 'http://davidhasselhoffonline.com/bear' 154 | }); 155 | 156 | db.rants.remove({}, { multi: true }, () => { 157 | db.rants.insert(rants, () => { 158 | db.ads.remove({}, { multi: true }, () => { 159 | db.ads.insert(ads, () => { 160 | db.users.remove({}, { multi: true }, () => { 161 | db.users.insert(user, callback); 162 | }); 163 | }); 164 | }); 165 | }); 166 | }); 167 | } 168 | 169 | function importDonald(db, callback) { 170 | var user = { 171 | name: 'Donald J. Trump', 172 | imageURL: '/images/profile2.jpg', 173 | joinedOn: '2009-03-01T00:00:00.000Z' 174 | }; 175 | 176 | var rants = []; 177 | rants.push({ 178 | name: user.name, 179 | imageURL: user.imageURL, 180 | text: 'What is our country coming to when a judge can halt a Homeland Security travel ban and anyone, even with bad intentions, can come into U.S.?', 181 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 2).toISOString() 182 | }); 183 | rants.push({ 184 | name: user.name, 185 | imageURL: user.imageURL, 186 | text: 'MAKE AMERICA GREAT AGAIN!', 187 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 4).toISOString() 188 | }); 189 | rants.push({ 190 | name: user.name, 191 | imageURL: user.imageURL, 192 | text: 'The opinion of this so-called judge, which essentially takes law-enforcement away from our country, is ridiculous and will be overturned!', 193 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 6).toISOString() 194 | }); 195 | rants.push({ 196 | name: user.name, 197 | imageURL: user.imageURL, 198 | text: '.@CNN is in a total meltdown with their FAKE NEWS because their ratings are tanking since election and their credibility will soon be gone!', 199 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 10).toISOString() 200 | }); 201 | rants.push({ 202 | name: user.name, 203 | imageURL: user.imageURL, 204 | text: 'We had a great News Conference at Trump Tower today. A couple of FAKE NEWS organizations were there but the people truly get what\'s going on', 205 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 16).toISOString() 206 | }); 207 | rants.push({ 208 | name: user.name, 209 | imageURL: user.imageURL, 210 | text: 'Intelligence agencies should never have allowed this fake news to "leak" into the public. One last shot at me.Are we living in Nazi Germany?', 211 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 20).toISOString() 212 | }); 213 | rants.push({ 214 | name: user.name, 215 | imageURL: user.imageURL, 216 | text: 'I win an election easily, a great "movement" is verified, and crooked opponents try to belittle our victory with FAKE NEWS. A sorry state!', 217 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 26).toISOString() 218 | }); 219 | rants.push({ 220 | name: user.name, 221 | imageURL: user.imageURL, 222 | text: 'FAKE NEWS - A TOTAL POLITICAL WITCH HUNT!', 223 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 28).toISOString() 224 | }); 225 | rants.push({ 226 | name: user.name, 227 | imageURL: user.imageURL, 228 | text: '.@ariannahuff is unattractive both inside and out. I fully understand why her former husband left her for a man- he made a good decision.', 229 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 32).toISOString() 230 | }); 231 | rants.push({ 232 | name: user.name, 233 | imageURL: user.imageURL, 234 | text: 'Sorry losers and haters, but my I.Q. is one of the highest -and you all know it! Please don\'t feel so stupid or insecure,it\'s not your fault', 235 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 38).toISOString() 236 | }); 237 | rants.push({ 238 | name: user.name, 239 | imageURL: user.imageURL, 240 | text: 'Barney Frank looked disgusting--nipples protruding--in his blue shirt before Congress. Very very disrespectful.', 241 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 48).toISOString() 242 | }); 243 | rants.push({ 244 | name: user.name, 245 | imageURL: user.imageURL, 246 | text: 'Dopey Prince @Alwaleed_Talal wants to control our U.S. politicians with daddy’s money. Can’t do it when I get elected. #Trump2016', 247 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 56).toISOString() 248 | }); 249 | rants.push({ 250 | name: user.name, 251 | imageURL: user.imageURL, 252 | text: 'Rosie is crude, rude, obnoxious and dumb - other than that I like her very much!', 253 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 60).toISOString() 254 | }); 255 | rants.push({ 256 | name: user.name, 257 | imageURL: user.imageURL, 258 | text: 'Hillary Clinton has announced that she is letting her husband out to campaign but HE\'S DEMONSTRATED A PENCHANT FOR SEXISM, so inappropriate!', 259 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 60).toISOString() 260 | }); 261 | rants.push({ 262 | name: user.name, 263 | imageURL: user.imageURL, 264 | text: 'The concept of global warming was created by and for the Chinese in order to make U.S. manufacturing non-competitive.', 265 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 72).toISOString() 266 | }); 267 | 268 | var ads = []; 269 | ads.push({ 270 | advertiserId: 'REPLACE_ME', 271 | imageURL: '/images/ad-russia.jpg', 272 | targetURL: 'https://www.travelallrussia.com/' 273 | }); 274 | ads.push({ 275 | advertiserId: 'REPLACE_ME', 276 | imageURL: '/images/ad-goldbathroom.jpg', 277 | targetURL: 'http://www.houzz.com/gold-bathroom' 278 | }); 279 | ads.push({ 280 | advertiserId: 'REPLACE_ME', 281 | imageURL: '/images/ad-hair.jpg', 282 | targetURL: 'http://www.hairclub.com/' 283 | }); 284 | 285 | db.rants.remove({}, { multi: true }, () => { 286 | db.rants.insert(rants, () => { 287 | db.ads.remove({}, { multi: true }, () => { 288 | db.ads.insert(ads, () => { 289 | db.users.remove({}, { multi: true }, () => { 290 | db.users.insert(user, callback); 291 | }); 292 | }); 293 | }); 294 | }); 295 | }); 296 | 297 | } 298 | 299 | function importAltTodd(db, callback) { 300 | var user = { 301 | name: 'AltTodd', 302 | imageURL: '/images/profile1a.jpg', 303 | joinedOn: null 304 | }; 305 | 306 | var rants = []; 307 | rants.push({ 308 | name: user.name, 309 | imageURL: user.imageURL, 310 | text: null, 311 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 2).toISOString() 312 | }); 313 | rants.push({ 314 | name: user.name, 315 | imageURL: user.imageURL, 316 | text: null, 317 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 4).toISOString() 318 | }); 319 | rants.push({ 320 | name: user.name, 321 | imageURL: user.imageURL, 322 | text: null, 323 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 6).toISOString() 324 | }); 325 | rants.push({ 326 | name: user.name, 327 | imageURL: user.imageURL, 328 | text: null, 329 | timestamp: new Date((new Date()) * 1 - 1000 * 3600 * 10).toISOString() 330 | }); 331 | 332 | var ads = []; 333 | ads.push({ 334 | advertiserId: 'REPLACE_ME', 335 | imageURL: '/images/ad-bacon.jpg', 336 | targetURL: 'http://www.baconfreak.com/' 337 | }); 338 | ads.push({ 339 | advertiserId: 'REPLACE_ME', 340 | imageURL: '/images/ad-dundee.png', 341 | targetURL: 'https://www.youtube.com/watch?v=POJtaO2xB_o' 342 | }); 343 | ads.push({ 344 | advertiserId: 'REPLACE_ME', 345 | imageURL: '/images/ad-bear.jpeg', 346 | targetURL: 'http://www.bear.org/website/' 347 | }); 348 | ads.push({ 349 | advertiserId: 'REPLACE_ME', 350 | imageURL: '/images/ad-hasslehoff.png', 351 | targetURL: 'http://davidhasselhoffonline.com/bear' 352 | }); 353 | 354 | db.rants.remove({}, { multi: true }, () => { 355 | db.rants.insert(rants, () => { 356 | db.ads.remove({}, { multi: true }, () => { 357 | db.ads.insert(ads, () => { 358 | db.users.remove({}, { multi: true }, () => { 359 | db.users.insert(user, callback); 360 | }); 361 | }); 362 | }); 363 | }); 364 | }); 365 | 366 | } 367 | -------------------------------------------------------------------------------- /src/public/vendor/backbone.js: -------------------------------------------------------------------------------- 1 | (function(t){var e=typeof self=="object"&&self.self===self&&self||typeof global=="object"&&global.global===global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,n){e.Backbone=t(e,n,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore"),r;try{r=require("jquery")}catch(n){}t(e,exports,i,r)}else{e.Backbone=t(e,{},e._,e.jQuery||e.Zepto||e.ender||e.$)}})(function(t,e,i,r){var n=t.Backbone;var s=Array.prototype.slice;e.VERSION="1.3.3";e.$=r;e.noConflict=function(){t.Backbone=n;return this};e.emulateHTTP=false;e.emulateJSON=false;var a=function(t,e,r){switch(t){case 1:return function(){return i[e](this[r])};case 2:return function(t){return i[e](this[r],t)};case 3:return function(t,n){return i[e](this[r],o(t,this),n)};case 4:return function(t,n,s){return i[e](this[r],o(t,this),n,s)};default:return function(){var t=s.call(arguments);t.unshift(this[r]);return i[e].apply(i,t)}}};var h=function(t,e,r){i.each(e,function(e,n){if(i[n])t.prototype[n]=a(e,n,r)})};var o=function(t,e){if(i.isFunction(t))return t;if(i.isObject(t)&&!e._isModel(t))return l(t);if(i.isString(t))return function(e){return e.get(t)};return t};var l=function(t){var e=i.matches(t);return function(t){return e(t.attributes)}};var u=e.Events={};var c=/\s+/;var f=function(t,e,r,n,s){var a=0,h;if(r&&typeof r==="object"){if(n!==void 0&&"context"in s&&s.context===void 0)s.context=n;for(h=i.keys(r);athis.length)n=this.length;if(n<0)n+=this.length+1;var s=[];var a=[];var h=[];var o=[];var l={};var u=e.add;var c=e.merge;var f=e.remove;var d=false;var v=this.comparator&&n==null&&e.sort!==false;var g=i.isString(this.comparator)?this.comparator:null;var p,m;for(m=0;m7);this._useHashChange=this._wantsHashChange&&this._hasHashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.history&&this.history.pushState);this._usePushState=this._wantsPushState&&this._hasPushState;this.fragment=this.getFragment();this.root=("/"+this.root+"/").replace(O,"/");if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";this.location.replace(e+"#"+this.getPath());return true}else if(this._hasPushState&&this.atRoot()){this.navigate(this.getHash(),{replace:true})}}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe");this.iframe.src="javascript:0";this.iframe.style.display="none";this.iframe.tabIndex=-1;var r=document.body;var n=r.insertBefore(this.iframe,r.firstChild).contentWindow;n.document.open();n.document.close();n.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState){s("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){s("hashchange",this.checkUrl,false)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}if(!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};if(this._usePushState){t("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){t("hashchange",this.checkUrl,false)}if(this.iframe){document.body.removeChild(this.iframe);this.iframe=null}if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);N.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getHash(this.iframe.contentWindow)}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){if(!this.matchRoot())return false;t=this.fragment=this.getFragment(t);return i.some(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!N.started)return false;if(!e||e===true)e={trigger:!!e};t=this.getFragment(t||"");var i=this.root;if(t===""||t.charAt(0)==="?"){i=i.slice(0,-1)||"/"}var r=i+t;t=this.decodeFragment(t.replace(U,""));if(this.fragment===t)return;this.fragment=t;if(this._usePushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,r)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var n=this.iframe.contentWindow;if(!e.replace){n.document.open();n.document.close()}this._updateHash(n.location,t,e.replace)}}else{return this.location.assign(r)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});e.history=new N;var q=function(t,e){var r=this;var n;if(t&&i.has(t,"constructor")){n=t.constructor}else{n=function(){return r.apply(this,arguments)}}i.extend(n,r,e);n.prototype=i.create(r.prototype,t);n.prototype.constructor=n;n.__super__=r.prototype;return n};y.extend=x.extend=$.extend=k.extend=N.extend=q;var F=function(){throw new Error('A "url" property or function must be specified')};var B=function(t,e){var i=e.error;e.error=function(r){if(i)i.call(e.context,t,r,e);t.trigger("error",t,r,e)}};return e}); 2 | //# sourceMappingURL=backbone-min.map 3 | -------------------------------------------------------------------------------- /src/public/vendor/tracker.js: -------------------------------------------------------------------------------- 1 | //! TrackJS JavaScript error monitoring agent. 2 | //! COPYRIGHT (c) 2017 ALL RIGHTS RESERVED 3 | //! See License at https://trackjs.com/terms/ 4 | (function(h,x,m){"use awesome";if(h.trackJs)h.console&&h.console.warn&&h.console.warn("TrackJS global conflict");else{var n=function(a,c,b,d,e){this.util=a;this.onError=c;this.onFault=b;this.options=e;e.enabled&&this.initialize(d)};n.prototype={initialize:function(a){f.forEach(["EventTarget","Node","XMLHttpRequest"],function(c){f.has(a,c+".prototype.addEventListener")&&(c=a[c].prototype,c.hasOwnProperty("addEventListener")&&(this.wrapAndCatch(c,"addEventListener",1),this.wrapRemoveEventListener(c)))}, 5 | this);this.wrapAndCatch(a,"setTimeout",0);this.wrapAndCatch(a,"setInterval",0)},wrapAndCatch:function(a,c,b){var d=this,e=a[c];f.isWrappableFunction(e)&&(a[c]=function(){try{var g=Array.prototype.slice.call(arguments),k=g[b],l,h;if(d.options.bindStack)try{throw Error();}catch(m){h=m.stack,l=d.util.isoNow()}var E=function(){try{if(f.isObject(k))return k.handleEvent.apply(k,arguments);if(f.isFunction(k))return k.apply(this,arguments)}catch(c){throw d.onError("catch",c,{bindTime:l,bindStack:h}),f.wrapError(c); 6 | }};if("addEventListener"===c&&(this._trackJsEvt||(this._trackJsEvt=new p),this._trackJsEvt.get(g[0],k,g[2])))return;try{k&&(f.isWrappableFunction(k)||f.isWrappableFunction(k.handleEvent))&&(g[b]=E,"addEventListener"===c&&this._trackJsEvt.add(g[0],k,g[2],g[b]))}catch(m){return e.apply(this,arguments)}return e.apply(this,g)}catch(m){a[c]=e,d.onFault(m)}})},wrapRemoveEventListener:function(a){if(a&&a.removeEventListener&&this.util.hasFunction(a.removeEventListener,"call")){var c=a.removeEventListener; 7 | a.removeEventListener=function(a,d,e){if(this._trackJsEvt){var g=this._trackJsEvt.get(a,d,e);if(g)return this._trackJsEvt.remove(a,d,e),c.call(this,a,g,e)}return c.call(this,a,d,e)}}}};var p=function(){this.events=[]};p.prototype={add:function(a,c,b,d){-1>=this.indexOf(a,c,b)&&(b=this.getEventOptions(b),this.events.push([a,c,b.capture,b.once,b.passive,d]))},get:function(a,c,b){a=this.indexOf(a,c,b);return 0<=a?this.events[a][5]:m},getEventOptions:function(a){var c={capture:!1,once:!1,passive:!1}; 8 | return f.isBoolean(a)?f.extend(c,{capture:a}):f.extend(c,a)},indexOf:function(a,c,b){b=this.getEventOptions(b);for(var d=0;d"}if(""===a)return"Empty String"; 10 | if(a===m)return"undefined";if(f.isString(a)||f.isNumber(a)||f.isBoolean(a)||f.isFunction(a))return""+a;if(h.HTMLElement&&a instanceof HTMLElement&&a.attributes)return c(a);var b;try{b=JSON.stringify(a,function(a,b){return b===m?"undefined":f.isNumber(b)&&isNaN(b)?"NaN":f.isError(b)?{name:b.name,message:b.message,stack:b.stack}:h.HTMLElement&&b instanceof HTMLElement&&b.attributes?c(b):b})}catch(e){b="";for(var d in a)a.hasOwnProperty(d)&&(b+=',"'+d+'":"'+a[d]+'"');b=b?"{"+b.replace(",","")+"}":"Unserializable Object"}return b.replace(/"undefined"/g, 11 | "undefined").replace(/"NaN"/g,"NaN")},sessionId:"",token:"",userId:"",version:"",callback:{enabled:!0,bindStack:!1},console:{enabled:!0,display:!0,error:!0,warn:!1,watch:["log","debug","info","warn","error"]},navigation:{enabled:!0},network:{enabled:!0,error:!0},visitor:{enabled:!0},usageURL:"https://usage.trackjs.com/usage.gif",window:{enabled:!0,promise:!0}},initCurrent:function(a){if(this.validate(a,this.defaults,"config",{}))return this.current=this.util.extend(this.current,this.defaults,a),!0; 12 | this.current=this.util.extend(this.current,this.defaults);return!1},setCurrent:function(a){return this.validate(a,this.defaults,"config",this.initOnly)?(this.current=this.util.extend(this.current,a),!0):!1},validate:function(a,c,b,d){var e=!0;b=b||"";d=d||{};for(var g in a)if(a.hasOwnProperty(g))if(c.hasOwnProperty(g)){var k=typeof c[g];k!==typeof a[g]?(console.warn(b+"."+g+": property must be type "+k+"."),e=!1):"[object Array]"!==Object.prototype.toString.call(a[g])||this.validateArray(a[g],c[g], 13 | b+"."+g)?"[object Object]"===Object.prototype.toString.call(a[g])?e=this.validate(a[g],c[g],b+"."+g,d[g]):d.hasOwnProperty(g)&&(console.warn(b+"."+g+": property cannot be set after load."),e=!1):e=!1}else console.warn(b+"."+g+": property not supported."),e=!1;return e},validateArray:function(a,c,b){var d=!0;b=b||"";for(var e=0;ethis.maxLength&&(this.appender=this.appender.slice(Math.max(this.appender.length- 20 | this.maxLength,0)))},add:function(a,c){var b=this.util.uuid();this.appender.push({key:b,category:a,value:c});this.truncate();return b},get:function(a,c){var b,d;for(d=0;dd.indexOf("localhost:0")&&(this._trackJs={method:a,url:d});return b.apply(this,arguments)};a.prototype.send=function(){try{if(!this._trackJs)return d.apply(this,arguments);this._trackJs.logId=c.log.add("n",{startedOn:c.util.isoNow(),method:this._trackJs.method,url:this._trackJs.url});c.listenForNetworkComplete(this)}catch(a){c.onFault(a)}return d.apply(this,arguments)};return a},listenForNetworkComplete:function(a){var c= 24 | this;c.window.ProgressEvent&&a.addEventListener&&a.addEventListener("readystatechange",function(){4===a.readyState&&c.finalizeNetworkEvent(a)},!0);a.addEventListener?a.addEventListener("load",function(){c.finalizeNetworkEvent(a);c.checkNetworkFault(a)},!0):setTimeout(function(){try{var b=a.onload;a.onload=function(){c.finalizeNetworkEvent(a);c.checkNetworkFault(a);"function"===typeof b&&c.util.hasFunction(b,"apply")&&b.apply(a,arguments)};var d=a.onerror;a.onerror=function(){c.finalizeNetworkEvent(a); 25 | c.checkNetworkFault(a);"function"===typeof oldOnError&&d.apply(a,arguments)}}catch(e){c.onFault(e)}},0)},finalizeNetworkEvent:function(a){if(a._trackJs){var c=this.log.get("n",a._trackJs.logId);c&&(c.completedOn=this.util.isoNow(),c.statusCode=1223==a.status?204:a.status,c.statusText=1223==a.status?"No Content":a.statusText)}},checkNetworkFault:function(a){if(this.options.error&&400<=a.status&&1223!=a.status){var c=a._trackJs||{};this.onError("ajax",a.status+" "+a.statusText+": "+c.method+" "+c.url)}}, 26 | report:function(){return this.log.all("n")}};var q=function(a,c){this.util=a;this.config=c;this.disabled=!1;this.throttleStats={attemptCount:0,throttledCount:0,lastAttempt:(new Date).getTime()};h.JSON&&h.JSON.stringify||(this.disabled=!0)};q.prototype={errorEndpoint:function(a){var c=this.config.current.errorURL;this.util.testCrossdomainXhr()||-1!==h.location.protocol.indexOf("https")||(c=this.config.current.errorNoSSLURL);return c+"?token="+a},usageEndpoint:function(a){return this.appendObjectAsQuery(a, 27 | this.config.current.usageURL)},trackerFaultEndpoint:function(a){return this.appendObjectAsQuery(a,this.config.current.faultURL)},appendObjectAsQuery:function(a,c){c+="?";for(var b in a)a.hasOwnProperty(b)&&(c+=encodeURIComponent(b)+"="+encodeURIComponent(a[b])+"&");return c},getCORSRequest:function(a,c){var b;this.util.testCrossdomainXhr()?(b=new h.XMLHttpRequest,b.open(a,c),b.setRequestHeader("Content-Type","text/plain")):"undefined"!==typeof h.XDomainRequest?(b=new h.XDomainRequest,b.open(a,c)): 28 | b=null;return b},sendTrackerFault:function(a){this.throttle(a)||((new Image).src=this.trackerFaultEndpoint(a))},sendUsage:function(a){(new Image).src=this.usageEndpoint(a)},sendError:function(a,c){var b=this;if(!this.disabled&&!this.throttle(a))try{var d=this.getCORSRequest("POST",this.errorEndpoint(c));d.onreadystatechange=function(){4===d.readyState&&200!==d.status&&(b.disabled=!0)};d._trackJs=m;d.send(h.JSON.stringify(a))}catch(e){throw this.disabled=!0,e;}},throttle:function(a){var c=(new Date).getTime(); 29 | this.throttleStats.attemptCount++;if(this.throttleStats.lastAttempt+1E3>=c){if(this.throttleStats.lastAttempt=c,100)for(c in rd)d=rd[c],e=b[d],p(e)||(a[d]=e);return a} 17 | // Moment prototype object 18 | function r(b){q(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)), 19 | // Prevent infinite loop in case updateOffset creates new moment 20 | // objects. 21 | sd===!1&&(sd=!0,a.updateOffset(this),sd=!1)}function s(a){return a instanceof r||null!=a&&null!=a._isAMomentObject}function t(a){return a<0?Math.ceil(a)||0:Math.floor(a)}function u(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=t(b)),c} 22 | // compare two arrays, return the number of differences 23 | function v(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;d0?"future":"past"];return z(c)?c(b):c.replace(/%s/i,b)}function J(a,b){var c=a.toLowerCase();Dd[c]=Dd[c+"s"]=Dd[b]=a}function K(a){return"string"==typeof a?Dd[a]||Dd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)i(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(a,b){Ed[a]=b}function N(a){var b=[];for(var c in a)b.push({unit:c,priority:Ed[c]});return b.sort(function(a,b){return a.priority-b.priority}),b}function O(b,c){return function(d){return null!=d?(Q(this,b,d),a.updateOffset(this,c),this):P(this,b)}}function P(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function Q(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)} 29 | // MOMENTS 30 | function R(a){return a=K(a),z(this[a])?this[a]():this}function S(a,b){if("object"==typeof a){a=L(a);for(var c=N(a),d=0;d=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d} 31 | // token: 'M' 32 | // padded: ['MM', 2] 33 | // ordinal: 'Mo' 34 | // callback: function () { this.month() + 1 } 35 | function U(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(Id[a]=e),b&&(Id[b[0]]=function(){return T(e.apply(this,arguments),b[1],b[2])}),c&&(Id[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function V(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function W(a){var b,c,d=a.match(Fd);for(b=0,c=d.length;b=0&&Gd.test(a);)a=a.replace(Gd,c),Gd.lastIndex=0,d-=1;return a}function Z(a,b,c){$d[a]=z(b)?b:function(a,d){return a&&c?c:b}}function $(a,b){return i($d,a)?$d[a](b._strict,b._locale):new RegExp(_(a))} 38 | // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript 39 | function _(a){return aa(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function aa(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function ba(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),f(b)&&(d=function(a,c){c[b]=u(a)}),c=0;c=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function ta(a){var b=new Date(Date.UTC.apply(null,arguments)); 68 | //the Date.UTC function remaps years 0-99 to 1900-1999 69 | return a<100&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b} 70 | // start-of-first-week - start-of-year 71 | function ua(a,b,c){var// first-week day -- which january is always in the first week (4 for iso, 1 for other) 72 | d=7+b-c, 73 | // first-week day local weekday -- which local weekday is fwd 74 | e=(7+ta(a,0,d).getUTCDay()-b)%7;return-e+d-1} 75 | //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday 76 | function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return j<=0?(f=a-1,g=pa(f)+j):j>pa(a)?(f=a+1,g=j-pa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return g<1?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(pa(a)-d+e)/7} 77 | // HELPERS 78 | // LOCALES 79 | function ya(a){return wa(a,this._week.dow,this._week.doy).week}function za(){return this._week.dow}function Aa(){return this._week.doy} 80 | // MOMENTS 81 | function Ba(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function Ca(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")} 82 | // HELPERS 83 | function Da(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function Ea(a,b){return"string"==typeof a?b.weekdaysParse(a)%7||7:isNaN(a)?null:a}function Fa(a,b){return a?c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]:this._weekdays}function Ga(a){return a?this._weekdaysShort[a.day()]:this._weekdaysShort}function Ha(a){return a?this._weekdaysMin[a.day()]:this._weekdaysMin}function Ia(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;d<7;++d)f=k([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=je.call(this._weekdaysParse,g),e!==-1?e:null):"ddd"===b?(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:null):(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null):"dddd"===b?(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null))):"ddd"===b?(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._minWeekdaysParse,g),e!==-1?e:null))):(e=je.call(this._minWeekdaysParse,g),e!==-1?e:(e=je.call(this._weekdaysParse,g),e!==-1?e:(e=je.call(this._shortWeekdaysParse,g),e!==-1?e:null)))}function Ja(a,b,c){var d,e,f;if(this._weekdaysParseExact)return Ia.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;d<7;d++){ 84 | // test the regex 85 | if( 86 | // make the regex if we don't have it already 87 | e=k([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}} 88 | // MOMENTS 89 | function Ka(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=Da(a,this.localeData()),this.add(a-b,"d")):b}function La(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function Ma(a){if(!this.isValid())return null!=a?this:NaN; 90 | // behaves the same as moment#day except 91 | // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) 92 | // as a setter, sunday should belong to the previous week. 93 | if(null!=a){var b=Ea(a,this.localeData());return this.day(this.day()%7?b:b-7)}return this.day()||7}function Na(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):(i(this,"_weekdaysRegex")||(this._weekdaysRegex=ue),this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex)}function Oa(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(i(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ve),this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)}function Pa(a){return this._weekdaysParseExact?(i(this,"_weekdaysRegex")||Qa.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(i(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=we),this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)}function Qa(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],h=[],i=[],j=[];for(b=0;b<7;b++) 94 | // make the regex if we don't have it already 95 | c=k([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),h.push(e),i.push(f),j.push(d),j.push(e),j.push(f);for( 96 | // Sorting makes sure if one weekday (or abbr) is a prefix of another it 97 | // will match the longer piece. 98 | g.sort(a),h.sort(a),i.sort(a),j.sort(a),b=0;b<7;b++)h[b]=aa(h[b]),i[b]=aa(i[b]),j[b]=aa(j[b]);this._weekdaysRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+h.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")} 99 | // FORMATTING 100 | function Ra(){return this.hours()%12||12}function Sa(){return this.hours()||24}function Ta(a,b){U(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})} 101 | // PARSING 102 | function Ua(a,b){return b._meridiemParse} 103 | // LOCALES 104 | function Va(a){ 105 | // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays 106 | // Using charAt should be more compatible. 107 | return"p"===(a+"").toLowerCase().charAt(0)}function Wa(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function Xa(a){return a?a.toLowerCase().replace("_","-"):a} 108 | // pick the locale from the array 109 | // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each 110 | // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root 111 | function Ya(a){for(var b,c,d,e,f=0;f0;){if(d=Za(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&v(e,c,!0)>=b-1) 112 | //the next array item is better than a shallower substring of this one 113 | break;b--}f++}return null}function Za(a){var b=null; 114 | // TODO: Find a better way to register and load all the locales in Node 115 | if(!Be[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=xe._abbr,require("./locale/"+a), 116 | // because defineLocale currently also sets the global locale, we 117 | // want to undo that for lazy loaded locales 118 | $a(b)}catch(a){}return Be[a]} 119 | // This function will load locale and then set the global locale. If 120 | // no arguments are passed in, it will simply return the current global 121 | // locale key. 122 | function $a(a,b){var c; 123 | // moment.duration._locale = moment._locale = data; 124 | return a&&(c=p(b)?bb(a):_a(a,b),c&&(xe=c)),xe._abbr}function _a(a,b){if(null!==b){var c=Ae;if(b.abbr=a,null!=Be[a])y("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),c=Be[a]._config;else if(null!=b.parentLocale){if(null==Be[b.parentLocale])return Ce[b.parentLocale]||(Ce[b.parentLocale]=[]),Ce[b.parentLocale].push({name:a,config:b}),null;c=Be[b.parentLocale]._config} 125 | // backwards compat for now: also set the locale 126 | // make sure we set the locale AFTER all child locales have been 127 | // created, so we won't end up with the child locale set. 128 | return Be[a]=new C(B(c,b)),Ce[a]&&Ce[a].forEach(function(a){_a(a.name,a.config)}),$a(a),Be[a]} 129 | // useful for testing 130 | return delete Be[a],null}function ab(a,b){if(null!=b){var c,d=Ae; 131 | // MERGE 132 | null!=Be[a]&&(d=Be[a]._config),b=B(d,b),c=new C(b),c.parentLocale=Be[a],Be[a]=c, 133 | // backwards compat for now: also set the locale 134 | $a(a)}else 135 | // pass null for config to unupdate, useful for tests 136 | null!=Be[a]&&(null!=Be[a].parentLocale?Be[a]=Be[a].parentLocale:null!=Be[a]&&delete Be[a]);return Be[a]} 137 | // returns locale data 138 | function bb(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return xe;if(!c(a)){if( 139 | //short-circuit everything else 140 | b=Za(a))return b;a=[a]}return Ya(a)}function cb(){return wd(Be)}function db(a){var b,c=a._a;return c&&m(a).overflow===-2&&(b=c[be]<0||c[be]>11?be:c[ce]<1||c[ce]>ea(c[ae],c[be])?ce:c[de]<0||c[de]>24||24===c[de]&&(0!==c[ee]||0!==c[fe]||0!==c[ge])?de:c[ee]<0||c[ee]>59?ee:c[fe]<0||c[fe]>59?fe:c[ge]<0||c[ge]>999?ge:-1,m(a)._overflowDayOfYear&&(bce)&&(b=ce),m(a)._overflowWeeks&&b===-1&&(b=he),m(a)._overflowWeekday&&b===-1&&(b=ie),m(a).overflow=b),a} 141 | // date from iso format 142 | function eb(a){var b,c,d,e,f,g,h=a._i,i=De.exec(h)||Ee.exec(h);if(i){for(m(a).iso=!0,b=0,c=Ge.length;bpa(e)&&(m(a)._overflowDayOfYear=!0),c=ta(e,0,a._dayOfYear),a._a[be]=c.getUTCMonth(),a._a[ce]=c.getUTCDate()),b=0;b<3&&null==a._a[b];++b)a._a[b]=f[b]=d[b]; 166 | // Zero out whatever was not defaulted, including time 167 | for(;b<7;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b]; 168 | // Check for 24:00:00.000 169 | 24===a._a[de]&&0===a._a[ee]&&0===a._a[fe]&&0===a._a[ge]&&(a._nextDay=!0,a._a[de]=0),a._d=(a._useUTC?ta:sa).apply(null,f), 170 | // Apply timezone offset from input. The actual utcOffset can be changed 171 | // with parseZone. 172 | null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[de]=24)}}function jb(a){var b,c,d,e,f,g,h,i;if(b=a._w,null!=b.GG||null!=b.W||null!=b.E)f=1,g=4, 173 | // TODO: We need to take the current isoWeekYear, but that depends on 174 | // how we interpret now (local, utc, fixed offset). So create 175 | // a now version of current config (take local/utc/offset flags, and 176 | // create now). 177 | c=gb(b.GG,a._a[ae],wa(sb(),1,4).year),d=gb(b.W,1),e=gb(b.E,1),(e<1||e>7)&&(i=!0);else{f=a._locale._week.dow,g=a._locale._week.doy;var j=wa(sb(),f,g);c=gb(b.gg,a._a[ae],j.year), 178 | // Default to current week. 179 | d=gb(b.w,j.week),null!=b.d?( 180 | // weekday -- low day numbers are considered next week 181 | e=b.d,(e<0||e>6)&&(i=!0)):null!=b.e?( 182 | // local weekday -- counting starts from begining of week 183 | e=b.e+f,(b.e<0||b.e>6)&&(i=!0)): 184 | // default to begining of week 185 | e=f}d<1||d>xa(c,f,g)?m(a)._overflowWeeks=!0:null!=i?m(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[ae]=h.year,a._dayOfYear=h.dayOfYear)} 186 | // date from string and format string 187 | function kb(b){ 188 | // TODO: Move this to another part of the creation flow to prevent circular deps 189 | if(b._f===a.ISO_8601)return void eb(b);b._a=[],m(b).empty=!0; 190 | // This array is used to make a Date, either with `new Date` or `Date.UTC` 191 | var c,d,e,f,g,h=""+b._i,i=h.length,j=0;for(e=Y(b._f,b._locale).match(Fd)||[],c=0;c0&&m(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),j+=d.length), 195 | // don't parse if it's not a known token 196 | Id[f]?(d?m(b).empty=!1:m(b).unusedTokens.push(f),da(f,d,b)):b._strict&&!d&&m(b).unusedTokens.push(f); 197 | // add remaining unparsed input length to the string 198 | m(b).charsLeftOver=i-j,h.length>0&&m(b).unusedInput.push(h), 199 | // clear _12h flag if hour is <= 12 200 | b._a[de]<=12&&m(b).bigHour===!0&&b._a[de]>0&&(m(b).bigHour=void 0),m(b).parsedDateParts=b._a.slice(0),m(b).meridiem=b._meridiem, 201 | // handle meridiem 202 | b._a[de]=lb(b._locale,b._a[de],b._meridiem),ib(b),db(b)}function lb(a,b,c){var d; 203 | // Fallback 204 | return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&b<12&&(b+=12),d||12!==b||(b=0),b):b} 205 | // date from string and array of format strings 206 | function mb(a){var b,c,d,e,f;if(0===a._f.length)return m(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e 249 | // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset 250 | // +0200, so we adjust the time as needed, to be valid. 251 | // 252 | // Keeping the time actually adds/subtracts (one hour) 253 | // from the actual represented time. That is why we call updateOffset 254 | // a second time. In case it wants us to change the offset again 255 | // _changeInProgress == true case, then we have to adjust, because 256 | // there is no such time in the given timezone. 257 | function Db(b,c){var d,e=this._offset||0;if(!this.isValid())return null!=b?this:NaN;if(null!=b){if("string"==typeof b){if(b=Ab(Xd,b),null===b)return this}else Math.abs(b)<16&&(b=60*b);return!this._isUTC&&c&&(d=Cb(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?Tb(this,Ob(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?e:Cb(this)}function Eb(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Fb(a){return this.utcOffset(0,a)}function Gb(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Cb(this),"m")),this}function Hb(){if(null!=this._tzm)this.utcOffset(this._tzm);else if("string"==typeof this._i){var a=Ab(Wd,this._i);null!=a?this.utcOffset(a):this.utcOffset(0,!0)}return this}function Ib(a){return!!this.isValid()&&(a=a?sb(a).utcOffset():0,(this.utcOffset()-a)%60===0)}function Jb(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Kb(){if(!p(this._isDSTShifted))return this._isDSTShifted;var a={};if(q(a,this),a=pb(a),a._a){var b=a._isUTC?k(a._a):sb(a._a);this._isDSTShifted=this.isValid()&&v(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function Lb(){return!!this.isValid()&&!this._isUTC}function Mb(){return!!this.isValid()&&this._isUTC}function Nb(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function Ob(a,b){var c,d,e,g=a, 258 | // matching against regexp is expensive, do it on demand 259 | h=null;// checks for null or undefined 260 | return xb(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:f(a)?(g={},b?g[b]=a:g.milliseconds=a):(h=Ne.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:u(h[ce])*c,h:u(h[de])*c,m:u(h[ee])*c,s:u(h[fe])*c,ms:u(yb(1e3*h[ge]))*c}):(h=Oe.exec(a))?(c="-"===h[1]?-1:1,g={y:Pb(h[2],c),M:Pb(h[3],c),w:Pb(h[4],c),d:Pb(h[5],c),h:Pb(h[6],c),m:Pb(h[7],c),s:Pb(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=Rb(sb(g.from),sb(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new wb(g),xb(a)&&i(a,"_locale")&&(d._locale=a._locale),d}function Pb(a,b){ 261 | // We'd normally use ~~inp for this, but unfortunately it also 262 | // converts floats to ints. 263 | // inp may be undefined, so careful calling replace on it. 264 | var c=a&&parseFloat(a.replace(",",".")); 265 | // apply sign while we're at it 266 | return(isNaN(c)?0:c)*b}function Qb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function Rb(a,b){var c;return a.isValid()&&b.isValid()?(b=Bb(b,a),a.isBefore(b)?c=Qb(a,b):(c=Qb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}} 267 | // TODO: remove 'name' arg after deprecation is removed 268 | function Sb(a,b){return function(c,d){var e,f; 269 | //invert the arguments, but complain about it 270 | return null===d||isNaN(+d)||(y(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=Ob(c,d),Tb(this,e,a),this}}function Tb(b,c,d,e){var f=c._milliseconds,g=yb(c._days),h=yb(c._months);b.isValid()&&(e=null==e||e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&Q(b,"Date",P(b,"Date")+g*d),h&&ja(b,P(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function Ub(a,b){var c=a.diff(b,"days",!0);return c<-6?"sameElse":c<-1?"lastWeek":c<0?"lastDay":c<1?"sameDay":c<2?"nextDay":c<7?"nextWeek":"sameElse"}function Vb(b,c){ 271 | // We want to compare the start of today, vs this. 272 | // Getting start-of-today depends on whether we're local/utc/offset or not. 273 | var d=b||sb(),e=Bb(d,this).startOf("day"),f=a.calendarFormat(this,e)||"sameElse",g=c&&(z(c[f])?c[f].call(this,d):c[f]);return this.format(g||this.localeData().calendar(f,this,sb(d)))}function Wb(){return new r(this)}function Xb(a,b){var c=s(a)?a:sb(a);return!(!this.isValid()||!c.isValid())&&(b=K(p(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()f&&(b=f),Fc.call(this,a,b,c,d,e))}function Fc(a,b,c,d,e){var f=va(a,b,c,d,e),g=ta(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this} 314 | // MOMENTS 315 | function Gc(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)} 316 | // HELPERS 317 | // MOMENTS 318 | function Hc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function Ic(a,b){b[ge]=u(1e3*("0."+a))} 319 | // MOMENTS 320 | function Jc(){return this._isUTC?"UTC":""}function Kc(){return this._isUTC?"Coordinated Universal Time":""}function Lc(a){return sb(1e3*a)}function Mc(){return sb.apply(null,arguments).parseZone()}function Nc(a){return a}function Oc(a,b,c,d){var e=bb(),f=k().set(d,b);return e[c](f,a)}function Pc(a,b,c){if(f(a)&&(b=a,a=void 0),a=a||"",null!=b)return Oc(a,b,c,"month");var d,e=[];for(d=0;d<12;d++)e[d]=Oc(a,d,c,"month");return e} 321 | // () 322 | // (5) 323 | // (fmt, 5) 324 | // (fmt) 325 | // (true) 326 | // (true, 5) 327 | // (true, fmt, 5) 328 | // (true, fmt) 329 | function Qc(a,b,c,d){"boolean"==typeof a?(f(b)&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,f(b)&&(c=b,b=void 0),b=b||"");var e=bb(),g=a?e._week.dow:0;if(null!=c)return Oc(b,(c+g)%7,d,"day");var h,i=[];for(h=0;h<7;h++)i[h]=Oc(b,(h+g)%7,d,"day");return i}function Rc(a,b){return Pc(a,b,"months")}function Sc(a,b){return Pc(a,b,"monthsShort")}function Tc(a,b,c){return Qc(a,b,c,"weekdays")}function Uc(a,b,c){return Qc(a,b,c,"weekdaysShort")}function Vc(a,b,c){return Qc(a,b,c,"weekdaysMin")}function Wc(){var a=this._data;return this._milliseconds=Ze(this._milliseconds),this._days=Ze(this._days),this._months=Ze(this._months),a.milliseconds=Ze(a.milliseconds),a.seconds=Ze(a.seconds),a.minutes=Ze(a.minutes),a.hours=Ze(a.hours),a.months=Ze(a.months),a.years=Ze(a.years),this}function Xc(a,b,c,d){var e=Ob(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()} 330 | // supports only 2.0-style add(1, 's') or add(duration) 331 | function Yc(a,b){return Xc(this,a,b,1)} 332 | // supports only 2.0-style subtract(1, 's') or subtract(duration) 333 | function Zc(a,b){return Xc(this,a,b,-1)}function $c(a){return a<0?Math.floor(a):Math.ceil(a)}function _c(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data; 334 | // if we have a mix of positive and negative values, bubble down first 335 | // check: https://github.com/moment/moment/issues/2166 336 | // The following code bubbles up values, see the tests for 337 | // examples of what that means. 338 | // convert days to months 339 | // 12 months -> 1 year 340 | return f>=0&&g>=0&&h>=0||f<=0&&g<=0&&h<=0||(f+=864e5*$c(bd(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=t(f/1e3),i.seconds=a%60,b=t(a/60),i.minutes=b%60,c=t(b/60),i.hours=c%24,g+=t(c/24),e=t(ad(g)),h+=e,g-=$c(bd(e)),d=t(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function ad(a){ 341 | // 400 years have 146097 days (taking into account leap year rules) 342 | // 400 years have 12 months === 4800 343 | return 4800*a/146097}function bd(a){ 344 | // the reverse of daysToMonths 345 | return 146097*a/4800}function cd(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+ad(b),"month"===a?c:c/12;switch( 346 | // handle milliseconds separately because of floating point math errors (issue #1867) 347 | b=this._days+Math.round(bd(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3; 348 | // Math.floor prevents floating point math errors here 349 | case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}} 350 | // TODO: Use this.as('ms')? 351 | function dd(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*u(this._months/12)}function ed(a){return function(){return this.as(a)}}function fd(a){return a=K(a),this[a+"s"]()}function gd(a){return function(){return this._data[a]}}function hd(){return t(this.days()/7)} 352 | // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize 353 | function id(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function jd(a,b,c){var d=Ob(a).abs(),e=of(d.as("s")),f=of(d.as("m")),g=of(d.as("h")),h=of(d.as("d")),i=of(d.as("M")),j=of(d.as("y")),k=e0,k[4]=c,id.apply(null,k)} 354 | // This function allows you to set the rounding function for relative time strings 355 | function kd(a){return void 0===a?of:"function"==typeof a&&(of=a,!0)} 356 | // This function allows you to set a threshold for relative time strings 357 | function ld(a,b){return void 0!==pf[a]&&(void 0===b?pf[a]:(pf[a]=b,!0))}function md(a){var b=this.localeData(),c=jd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function nd(){ 358 | // for ISO strings we do not use the normal bubbling rules: 359 | // * milliseconds bubble up until they become hours 360 | // * days do not bubble at all 361 | // * months bubble up until they become years 362 | // This is because there is no context-free conversion between hours and days 363 | // (think of clock changes) 364 | // and also not between days and months (28-31 days per month) 365 | var a,b,c,d=qf(this._milliseconds)/1e3,e=qf(this._days),f=qf(this._months); 366 | // 3600 seconds -> 60 minutes -> 1 hour 367 | a=t(d/60),b=t(a/60),d%=60,a%=60, 368 | // 12 months -> 1 year 369 | c=t(f/12),f%=12; 370 | // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js 371 | var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(m<0?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var od,pd;pd=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;d68?1900:2e3)}; 396 | // MOMENTS 397 | var pe=O("FullYear",!0); 398 | // FORMATTING 399 | U("w",["ww",2],"wo","week"),U("W",["WW",2],"Wo","isoWeek"), 400 | // ALIASES 401 | J("week","w"),J("isoWeek","W"), 402 | // PRIORITIES 403 | M("week",5),M("isoWeek",5), 404 | // PARSING 405 | Z("w",Od),Z("ww",Od,Kd),Z("W",Od),Z("WW",Od,Kd),ca(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=u(a)});var qe={dow:0,// Sunday is the first day of the week. 406 | doy:6}; 407 | // FORMATTING 408 | U("d",0,"do","day"),U("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),U("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),U("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),U("e",0,0,"weekday"),U("E",0,0,"isoWeekday"), 409 | // ALIASES 410 | J("day","d"),J("weekday","e"),J("isoWeekday","E"), 411 | // PRIORITY 412 | M("day",11),M("weekday",11),M("isoWeekday",11), 413 | // PARSING 414 | Z("d",Od),Z("e",Od),Z("E",Od),Z("dd",function(a,b){return b.weekdaysMinRegex(a)}),Z("ddd",function(a,b){return b.weekdaysShortRegex(a)}),Z("dddd",function(a,b){return b.weekdaysRegex(a)}),ca(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict); 415 | // if we didn't get a weekday name, mark the date as invalid 416 | null!=e?b.d=e:m(c).invalidWeekday=a}),ca(["d","e","E"],function(a,b,c,d){b[d]=u(a)}); 417 | // LOCALES 418 | var re="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),se="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),te="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),ue=Zd,ve=Zd,we=Zd;U("H",["HH",2],0,"hour"),U("h",["hh",2],0,Ra),U("k",["kk",2],0,Sa),U("hmm",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)}),U("hmmss",0,0,function(){return""+Ra.apply(this)+T(this.minutes(),2)+T(this.seconds(),2)}),U("Hmm",0,0,function(){return""+this.hours()+T(this.minutes(),2)}),U("Hmmss",0,0,function(){return""+this.hours()+T(this.minutes(),2)+T(this.seconds(),2)}),Ta("a",!0),Ta("A",!1), 419 | // ALIASES 420 | J("hour","h"), 421 | // PRIORITY 422 | M("hour",13),Z("a",Ua),Z("A",Ua),Z("H",Od),Z("h",Od),Z("HH",Od,Kd),Z("hh",Od,Kd),Z("hmm",Pd),Z("hmmss",Qd),Z("Hmm",Pd),Z("Hmmss",Qd),ba(["H","HH"],de),ba(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),ba(["h","hh"],function(a,b,c){b[de]=u(a),m(c).bigHour=!0}),ba("hmm",function(a,b,c){var d=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d)),m(c).bigHour=!0}),ba("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d,2)),b[fe]=u(a.substr(e)),m(c).bigHour=!0}),ba("Hmm",function(a,b,c){var d=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d))}),ba("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[de]=u(a.substr(0,d)),b[ee]=u(a.substr(d,2)),b[fe]=u(a.substr(e))});var xe,ye=/[ap]\.?m?\.?/i,ze=O("Hours",!0),Ae={calendar:xd,longDateFormat:yd,invalidDate:zd,ordinal:Ad,ordinalParse:Bd,relativeTime:Cd,months:le,monthsShort:me,week:qe,weekdays:re,weekdaysMin:te,weekdaysShort:se,meridiemParse:ye},Be={},Ce={},De=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Ee=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Fe=/Z|[+-]\d\d(?::?\d\d)?/,Ge=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/], 423 | // YYYYMM is NOT allowed by the standard 424 | ["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],He=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ie=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=x("value provided is not in a recognized ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}), 425 | // constant that refers to the ISO standard 426 | a.ISO_8601=function(){};var Je=x("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",function(){var a=sb.apply(null,arguments);return this.isValid()&&a.isValid()?athis?this:a:o()}),Le=function(){return Date.now?Date.now():+new Date};zb("Z",":"),zb("ZZ",""), 427 | // PARSING 428 | Z("Z",Xd),Z("ZZ",Xd),ba(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ab(Xd,a)}); 429 | // HELPERS 430 | // timezone chunker 431 | // '+10:00' > ['10', '00'] 432 | // '-1530' > ['-15', '30'] 433 | var Me=/([\+\-]|\d\d)/gi; 434 | // HOOKS 435 | // This function will be called whenever a moment is mutated. 436 | // It is intended to keep the offset in sync with the timezone. 437 | a.updateOffset=function(){}; 438 | // ASP.NET json date format regex 439 | var Ne=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Oe=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;Ob.fn=wb.prototype;var Pe=Sb(1,"add"),Qe=Sb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var Re=x("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)}); 440 | // FORMATTING 441 | U(0,["gg",2],0,function(){return this.weekYear()%100}),U(0,["GG",2],0,function(){return this.isoWeekYear()%100}),zc("gggg","weekYear"),zc("ggggg","weekYear"),zc("GGGG","isoWeekYear"),zc("GGGGG","isoWeekYear"), 442 | // ALIASES 443 | J("weekYear","gg"),J("isoWeekYear","GG"), 444 | // PRIORITY 445 | M("weekYear",1),M("isoWeekYear",1), 446 | // PARSING 447 | Z("G",Vd),Z("g",Vd),Z("GG",Od,Kd),Z("gg",Od,Kd),Z("GGGG",Sd,Md),Z("gggg",Sd,Md),Z("GGGGG",Td,Nd),Z("ggggg",Td,Nd),ca(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=u(a)}),ca(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}), 448 | // FORMATTING 449 | U("Q",0,"Qo","quarter"), 450 | // ALIASES 451 | J("quarter","Q"), 452 | // PRIORITY 453 | M("quarter",7), 454 | // PARSING 455 | Z("Q",Jd),ba("Q",function(a,b){b[be]=3*(u(a)-1)}), 456 | // FORMATTING 457 | U("D",["DD",2],"Do","date"), 458 | // ALIASES 459 | J("date","D"), 460 | // PRIOROITY 461 | M("date",9), 462 | // PARSING 463 | Z("D",Od),Z("DD",Od,Kd),Z("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),ba(["D","DD"],ce),ba("Do",function(a,b){b[ce]=u(a.match(Od)[0],10)}); 464 | // MOMENTS 465 | var Se=O("Date",!0); 466 | // FORMATTING 467 | U("DDD",["DDDD",3],"DDDo","dayOfYear"), 468 | // ALIASES 469 | J("dayOfYear","DDD"), 470 | // PRIORITY 471 | M("dayOfYear",4), 472 | // PARSING 473 | Z("DDD",Rd),Z("DDDD",Ld),ba(["DDD","DDDD"],function(a,b,c){c._dayOfYear=u(a)}), 474 | // FORMATTING 475 | U("m",["mm",2],0,"minute"), 476 | // ALIASES 477 | J("minute","m"), 478 | // PRIORITY 479 | M("minute",14), 480 | // PARSING 481 | Z("m",Od),Z("mm",Od,Kd),ba(["m","mm"],ee); 482 | // MOMENTS 483 | var Te=O("Minutes",!1); 484 | // FORMATTING 485 | U("s",["ss",2],0,"second"), 486 | // ALIASES 487 | J("second","s"), 488 | // PRIORITY 489 | M("second",15), 490 | // PARSING 491 | Z("s",Od),Z("ss",Od,Kd),ba(["s","ss"],fe); 492 | // MOMENTS 493 | var Ue=O("Seconds",!1); 494 | // FORMATTING 495 | U("S",0,0,function(){return~~(this.millisecond()/100)}),U(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),U(0,["SSS",3],0,"millisecond"),U(0,["SSSS",4],0,function(){return 10*this.millisecond()}),U(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),U(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),U(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),U(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),U(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}), 496 | // ALIASES 497 | J("millisecond","ms"), 498 | // PRIORITY 499 | M("millisecond",16), 500 | // PARSING 501 | Z("S",Rd,Jd),Z("SS",Rd,Kd),Z("SSS",Rd,Ld);var Ve;for(Ve="SSSS";Ve.length<=9;Ve+="S")Z(Ve,Ud);for(Ve="S";Ve.length<=9;Ve+="S")ba(Ve,Ic); 502 | // MOMENTS 503 | var We=O("Milliseconds",!1); 504 | // FORMATTING 505 | U("z",0,0,"zoneAbbr"),U("zz",0,0,"zoneName");var Xe=r.prototype;Xe.add=Pe,Xe.calendar=Vb,Xe.clone=Wb,Xe.diff=bc,Xe.endOf=oc,Xe.format=gc,Xe.from=hc,Xe.fromNow=ic,Xe.to=jc,Xe.toNow=kc,Xe.get=R,Xe.invalidAt=xc,Xe.isAfter=Xb,Xe.isBefore=Yb,Xe.isBetween=Zb,Xe.isSame=$b,Xe.isSameOrAfter=_b,Xe.isSameOrBefore=ac,Xe.isValid=vc,Xe.lang=Re,Xe.locale=lc,Xe.localeData=mc,Xe.max=Ke,Xe.min=Je,Xe.parsingFlags=wc,Xe.set=S,Xe.startOf=nc,Xe.subtract=Qe,Xe.toArray=sc,Xe.toObject=tc,Xe.toDate=rc,Xe.toISOString=ec,Xe.inspect=fc,Xe.toJSON=uc,Xe.toString=dc,Xe.unix=qc,Xe.valueOf=pc,Xe.creationData=yc, 506 | // Year 507 | Xe.year=pe,Xe.isLeapYear=ra, 508 | // Week Year 509 | Xe.weekYear=Ac,Xe.isoWeekYear=Bc, 510 | // Quarter 511 | Xe.quarter=Xe.quarters=Gc, 512 | // Month 513 | Xe.month=ka,Xe.daysInMonth=la, 514 | // Week 515 | Xe.week=Xe.weeks=Ba,Xe.isoWeek=Xe.isoWeeks=Ca,Xe.weeksInYear=Dc,Xe.isoWeeksInYear=Cc, 516 | // Day 517 | Xe.date=Se,Xe.day=Xe.days=Ka,Xe.weekday=La,Xe.isoWeekday=Ma,Xe.dayOfYear=Hc, 518 | // Hour 519 | Xe.hour=Xe.hours=ze, 520 | // Minute 521 | Xe.minute=Xe.minutes=Te, 522 | // Second 523 | Xe.second=Xe.seconds=Ue, 524 | // Millisecond 525 | Xe.millisecond=Xe.milliseconds=We, 526 | // Offset 527 | Xe.utcOffset=Db,Xe.utc=Fb,Xe.local=Gb,Xe.parseZone=Hb,Xe.hasAlignedHourOffset=Ib,Xe.isDST=Jb,Xe.isLocal=Lb,Xe.isUtcOffset=Mb,Xe.isUtc=Nb,Xe.isUTC=Nb, 528 | // Timezone 529 | Xe.zoneAbbr=Jc,Xe.zoneName=Kc, 530 | // Deprecations 531 | Xe.dates=x("dates accessor is deprecated. Use date instead.",Se),Xe.months=x("months accessor is deprecated. Use month instead",ka),Xe.years=x("years accessor is deprecated. Use year instead",pe),Xe.zone=x("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",Eb),Xe.isDSTShifted=x("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Kb);var Ye=C.prototype;Ye.calendar=D,Ye.longDateFormat=E,Ye.invalidDate=F,Ye.ordinal=G,Ye.preparse=Nc,Ye.postformat=Nc,Ye.relativeTime=H,Ye.pastFuture=I,Ye.set=A, 532 | // Month 533 | Ye.months=fa,Ye.monthsShort=ga,Ye.monthsParse=ia,Ye.monthsRegex=na,Ye.monthsShortRegex=ma, 534 | // Week 535 | Ye.week=ya,Ye.firstDayOfYear=Aa,Ye.firstDayOfWeek=za, 536 | // Day of Week 537 | Ye.weekdays=Fa,Ye.weekdaysMin=Ha,Ye.weekdaysShort=Ga,Ye.weekdaysParse=Ja,Ye.weekdaysRegex=Na,Ye.weekdaysShortRegex=Oa,Ye.weekdaysMinRegex=Pa, 538 | // Hours 539 | Ye.isPM=Va,Ye.meridiem=Wa,$a("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===u(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}), 540 | // Side effect imports 541 | a.lang=x("moment.lang is deprecated. Use moment.locale instead.",$a),a.langData=x("moment.langData is deprecated. Use moment.localeData instead.",bb);var Ze=Math.abs,$e=ed("ms"),_e=ed("s"),af=ed("m"),bf=ed("h"),cf=ed("d"),df=ed("w"),ef=ed("M"),ff=ed("y"),gf=gd("milliseconds"),hf=gd("seconds"),jf=gd("minutes"),kf=gd("hours"),lf=gd("days"),mf=gd("months"),nf=gd("years"),of=Math.round,pf={s:45,// seconds to minute 542 | m:45,// minutes to hour 543 | h:22,// hours to day 544 | d:26,// days to month 545 | M:11},qf=Math.abs,rf=wb.prototype; 546 | // Deprecations 547 | // Side effect imports 548 | // FORMATTING 549 | // PARSING 550 | // Side effect imports 551 | return rf.abs=Wc,rf.add=Yc,rf.subtract=Zc,rf.as=cd,rf.asMilliseconds=$e,rf.asSeconds=_e,rf.asMinutes=af,rf.asHours=bf,rf.asDays=cf,rf.asWeeks=df,rf.asMonths=ef,rf.asYears=ff,rf.valueOf=dd,rf._bubble=_c,rf.get=fd,rf.milliseconds=gf,rf.seconds=hf,rf.minutes=jf,rf.hours=kf,rf.days=lf,rf.weeks=hd,rf.months=mf,rf.years=nf,rf.humanize=md,rf.toISOString=nd,rf.toString=nd,rf.toJSON=nd,rf.locale=lc,rf.localeData=mc,rf.toIsoString=x("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",nd),rf.lang=Re,U("X",0,0,"unix"),U("x",0,0,"valueOf"),Z("x",Vd),Z("X",Yd),ba("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),ba("x",function(a,b,c){c._d=new Date(u(a))}),a.version="2.17.1",b(sb),a.fn=Xe,a.min=ub,a.max=vb,a.now=Le,a.utc=k,a.unix=Lc,a.months=Rc,a.isDate=g,a.locale=$a,a.invalid=o,a.duration=Ob,a.isMoment=s,a.weekdays=Tc,a.parseZone=Mc,a.localeData=bb,a.isDuration=xb,a.monthsShort=Sc,a.weekdaysMin=Vc,a.defineLocale=_a,a.updateLocale=ab,a.locales=cb,a.weekdaysShort=Uc,a.normalizeUnits=K,a.relativeTimeRounding=kd,a.relativeTimeThreshold=ld,a.calendarFormat=Ub,a.prototype=Xe,a}); 552 | --------------------------------------------------------------------------------