From 2146e69a116912f1d284bcc600004563988b1014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 13 Sep 2016 21:26:28 +0200 Subject: [PATCH] revert angularjs upgrade --- bower.json | 14 +- public/vendor/angular-bindonce/.bower.json | 7 +- public/vendor/angular-mocks/.bower.json | 12 +- public/vendor/angular-mocks/LICENSE.md | 21 - public/vendor/angular-mocks/README.md | 2 +- public/vendor/angular-mocks/angular-mocks.js | 663 +- public/vendor/angular-mocks/bower.json | 4 +- public/vendor/angular-mocks/package.json | 11 +- .../angular-native-dragdrop/.bower.json | 2 +- .../angular-native-dragdrop/draganddrop.js | 6 +- public/vendor/angular-route/.bower.json | 12 +- public/vendor/angular-route/LICENSE.md | 21 - public/vendor/angular-route/angular-route.js | 366 +- .../vendor/angular-route/angular-route.min.js | 22 +- .../angular-route/angular-route.min.js.map | 6 +- public/vendor/angular-route/bower.json | 4 +- public/vendor/angular-route/package.json | 11 +- public/vendor/angular-sanitize/.bower.json | 12 +- public/vendor/angular-sanitize/LICENSE.md | 21 - .../angular-sanitize/angular-sanitize.js | 696 +- .../angular-sanitize/angular-sanitize.min.js | 21 +- .../angular-sanitize.min.js.map | 6 +- public/vendor/angular-sanitize/bower.json | 4 +- public/vendor/angular-sanitize/package.json | 11 +- public/vendor/angular/.bower.json | 10 +- public/vendor/angular/LICENSE.md | 21 - public/vendor/angular/angular.js | 5597 +-- public/vendor/angular/angular.min.js | 630 +- public/vendor/angular/angular.min.js.gzip | Bin 57862 -> 54974 bytes public/vendor/angular/angular.min.js.map | 6 +- public/vendor/angular/bower.json | 2 +- public/vendor/angular/package.json | 2 +- public/vendor/jquery/.bower.json | 2 +- public/vendor/lodash/.bower.json | 14 + public/vendor/lodash/.editorconfig | 12 + public/vendor/lodash/.gitattributes | 1 + public/vendor/lodash/.github/CONTRIBUTING.md | 78 + public/vendor/lodash/.gitignore | 4 + public/vendor/lodash/.jscsrc | 97 + .../vendor/lodash/.markdown-doctest-setup.js | 11 + public/vendor/lodash/.travis.yml | 105 + public/vendor/lodash/LICENSE | 47 + public/vendor/lodash/README.md | 46 + public/vendor/lodash/dist/lodash.core.js | 3806 ++ public/vendor/lodash/dist/lodash.core.min.js | 28 + public/vendor/lodash/dist/lodash.fp.js | 1001 + public/vendor/lodash/dist/lodash.fp.min.js | 20 + public/vendor/lodash/dist/lodash.js | 16733 +++++++++ public/vendor/lodash/dist/lodash.min.js | 132 + public/vendor/lodash/dist/mapping.fp.js | 424 + public/vendor/lodash/doc/README.md | 11006 ++++++ public/vendor/lodash/fp/_baseConvert.js | 535 + public/vendor/lodash/fp/_convertBrowser.js | 18 + public/vendor/lodash/fp/_mapping.js | 362 + public/vendor/lodash/fp/placeholder.js | 6 + public/vendor/lodash/lib/common/file.js | 71 + public/vendor/lodash/lib/common/mapping.js | 9 + public/vendor/lodash/lib/common/minify.js | 39 + .../lodash/lib/common/uglify.options.js | 21 + public/vendor/lodash/lib/common/util.js | 40 + public/vendor/lodash/lib/fp/build-dist.js | 56 + public/vendor/lodash/lib/fp/build-doc.js | 81 + public/vendor/lodash/lib/fp/build-modules.js | 150 + .../lodash/lib/fp/template/doc/wiki.jst | 227 + .../lib/fp/template/modules/_falseOptions.jst | 7 + .../lodash/lib/fp/template/modules/_util.jst | 15 + .../lodash/lib/fp/template/modules/alias.jst | 1 + .../lib/fp/template/modules/category.jst | 2 + .../lib/fp/template/modules/convert.jst | 18 + .../lodash/lib/fp/template/modules/fp.jst | 2 + .../lodash/lib/fp/template/modules/module.jst | 5 + .../lodash/lib/fp/template/modules/thru.jst | 5 + public/vendor/lodash/lib/main/build-dist.js | 31 + public/vendor/lodash/lib/main/build-doc.js | 83 + .../vendor/lodash/lib/main/build-modules.js | 35 + public/vendor/lodash/lib/main/build-site.js | 73 + public/vendor/lodash/lodash.js | 16733 +++++++++ public/vendor/lodash/package.json | 57 + public/vendor/lodash/perf/asset/perf-ui.js | 112 + public/vendor/lodash/perf/index.html | 80 + public/vendor/lodash/perf/perf.js | 1978 + public/vendor/lodash/test/asset/test-ui.js | 155 + public/vendor/lodash/test/asset/worker.js | 17 + public/vendor/lodash/test/backbone.html | 170 + public/vendor/lodash/test/fp.html | 41 + public/vendor/lodash/test/index.html | 351 + public/vendor/lodash/test/remove.js | 27 + public/vendor/lodash/test/saucelabs.js | 914 + public/vendor/lodash/test/test-fp.js | 2301 ++ public/vendor/lodash/test/test.js | 26556 +++++++++++++ public/vendor/lodash/test/underscore.html | 460 + public/vendor/lodash/vendor/backbone/LICENSE | 22 + .../vendor/lodash/vendor/backbone/backbone.js | 1946 + .../lodash/vendor/backbone/test/collection.js | 2023 + .../lodash/vendor/backbone/test/events.js | 706 + .../lodash/vendor/backbone/test/model.js | 1448 + .../lodash/vendor/backbone/test/noconflict.js | 13 + .../lodash/vendor/backbone/test/router.js | 1081 + .../vendor/backbone/test/setup/dom-setup.js | 4 + .../vendor/backbone/test/setup/environment.js | 45 + .../lodash/vendor/backbone/test/sync.js | 239 + .../lodash/vendor/backbone/test/view.js | 516 + .../lodash/vendor/firebug-lite/license.txt | 30 + .../vendor/firebug-lite/skin/xp/blank.gif | Bin 0 -> 43 bytes .../vendor/firebug-lite/skin/xp/buttonBg.png | Bin 0 -> 167 bytes .../firebug-lite/skin/xp/buttonBgHover.png | Bin 0 -> 171 bytes .../vendor/firebug-lite/skin/xp/debugger.css | 331 + .../vendor/firebug-lite/skin/xp/detach.png | Bin 0 -> 655 bytes .../firebug-lite/skin/xp/detachHover.png | Bin 0 -> 586 bytes .../vendor/firebug-lite/skin/xp/disable.gif | Bin 0 -> 340 bytes .../vendor/firebug-lite/skin/xp/disable.png | Bin 0 -> 543 bytes .../firebug-lite/skin/xp/disableHover.gif | Bin 0 -> 344 bytes .../firebug-lite/skin/xp/disableHover.png | Bin 0 -> 512 bytes .../vendor/firebug-lite/skin/xp/down.png | Bin 0 -> 637 bytes .../firebug-lite/skin/xp/downActive.png | Bin 0 -> 543 bytes .../vendor/firebug-lite/skin/xp/downHover.png | Bin 0 -> 526 bytes .../firebug-lite/skin/xp/errorIcon-sm.png | Bin 0 -> 447 bytes .../vendor/firebug-lite/skin/xp/errorIcon.gif | Bin 0 -> 365 bytes .../vendor/firebug-lite/skin/xp/errorIcon.png | Bin 0 -> 457 bytes .../firebug-lite/skin/xp/firebug-1.3a2.css | 817 + .../firebug-lite/skin/xp/firebug.IE6.css | 20 + .../vendor/firebug-lite/skin/xp/firebug.css | 3147 ++ .../vendor/firebug-lite/skin/xp/firebug.html | 215 + .../vendor/firebug-lite/skin/xp/firebug.png | Bin 0 -> 1167 bytes .../vendor/firebug-lite/skin/xp/group.gif | Bin 0 -> 158 bytes .../vendor/firebug-lite/skin/xp/html.css | 272 + .../vendor/firebug-lite/skin/xp/infoIcon.gif | Bin 0 -> 359 bytes .../vendor/firebug-lite/skin/xp/infoIcon.png | Bin 0 -> 524 bytes .../firebug-lite/skin/xp/loading_16.gif | Bin 0 -> 1553 bytes .../vendor/firebug-lite/skin/xp/min.png | Bin 0 -> 552 bytes .../vendor/firebug-lite/skin/xp/minHover.png | Bin 0 -> 485 bytes .../vendor/firebug-lite/skin/xp/off.png | Bin 0 -> 742 bytes .../vendor/firebug-lite/skin/xp/offHover.png | Bin 0 -> 680 bytes .../skin/xp/pixel_transparent.gif | Bin 0 -> 43 bytes .../firebug-lite/skin/xp/roundCorner.svg | 6 + .../vendor/firebug-lite/skin/xp/search.gif | Bin 0 -> 550 bytes .../vendor/firebug-lite/skin/xp/search.png | Bin 0 -> 685 bytes .../vendor/firebug-lite/skin/xp/shadow.gif | Bin 0 -> 4364 bytes .../vendor/firebug-lite/skin/xp/shadow2.gif | Bin 0 -> 3093 bytes .../firebug-lite/skin/xp/shadowAlpha.png | Bin 0 -> 3403 bytes .../vendor/firebug-lite/skin/xp/sprite.png | Bin 0 -> 40027 bytes .../firebug-lite/skin/xp/tabHoverLeft.png | Bin 0 -> 438 bytes .../firebug-lite/skin/xp/tabHoverMid.png | Bin 0 -> 261 bytes .../firebug-lite/skin/xp/tabHoverRight.png | Bin 0 -> 436 bytes .../vendor/firebug-lite/skin/xp/tabLeft.png | Bin 0 -> 449 bytes .../firebug-lite/skin/xp/tabMenuCheckbox.png | Bin 0 -> 220 bytes .../firebug-lite/skin/xp/tabMenuPin.png | Bin 0 -> 207 bytes .../firebug-lite/skin/xp/tabMenuRadio.png | Bin 0 -> 192 bytes .../firebug-lite/skin/xp/tabMenuTarget.png | Bin 0 -> 142 bytes .../skin/xp/tabMenuTargetHover.png | Bin 0 -> 148 bytes .../vendor/firebug-lite/skin/xp/tabMid.png | Bin 0 -> 262 bytes .../vendor/firebug-lite/skin/xp/tabRight.png | Bin 0 -> 448 bytes .../skin/xp/textEditorBorders.gif | Bin 0 -> 117 bytes .../skin/xp/textEditorBorders.png | Bin 0 -> 3144 bytes .../skin/xp/textEditorCorners.gif | Bin 0 -> 1821 bytes .../skin/xp/textEditorCorners.png | Bin 0 -> 3960 bytes .../firebug-lite/skin/xp/titlebarMid.png | Bin 0 -> 273 bytes .../firebug-lite/skin/xp/toolbarMid.png | Bin 0 -> 242 bytes .../firebug-lite/skin/xp/tree_close.gif | Bin 0 -> 300 bytes .../vendor/firebug-lite/skin/xp/tree_open.gif | Bin 0 -> 202 bytes .../firebug-lite/skin/xp/twistyClosed.png | Bin 0 -> 334 bytes .../firebug-lite/skin/xp/twistyOpen.png | Bin 0 -> 309 bytes .../lodash/vendor/firebug-lite/skin/xp/up.png | Bin 0 -> 619 bytes .../vendor/firebug-lite/skin/xp/upActive.png | Bin 0 -> 551 bytes .../vendor/firebug-lite/skin/xp/upHover.png | Bin 0 -> 526 bytes .../firebug-lite/skin/xp/warningIcon.gif | Bin 0 -> 357 bytes .../firebug-lite/skin/xp/warningIcon.png | Bin 0 -> 516 bytes .../firebug-lite/src/firebug-lite-debug.js | 31176 ++++++++++++++++ public/vendor/lodash/vendor/json-js/json2.js | 519 + .../vendor/lodash/vendor/underscore/LICENSE | 23 + .../lodash/vendor/underscore/test/arrays.js | 563 + .../lodash/vendor/underscore/test/chaining.js | 99 + .../vendor/underscore/test/collections.js | 896 + .../vendor/underscore/test/cross-document.js | 141 + .../vendor/underscore/test/functions.js | 765 + .../lodash/vendor/underscore/test/objects.js | 1105 + .../lodash/vendor/underscore/test/utility.js | 421 + .../vendor/underscore/underscore-min.js | 6 + .../lodash/vendor/underscore/underscore.js | 1626 + 179 files changed, 138314 insertions(+), 5541 deletions(-) delete mode 100644 public/vendor/angular-mocks/LICENSE.md delete mode 100644 public/vendor/angular-route/LICENSE.md delete mode 100644 public/vendor/angular-sanitize/LICENSE.md delete mode 100644 public/vendor/angular/LICENSE.md create mode 100644 public/vendor/lodash/.bower.json create mode 100644 public/vendor/lodash/.editorconfig create mode 100644 public/vendor/lodash/.gitattributes create mode 100644 public/vendor/lodash/.github/CONTRIBUTING.md create mode 100644 public/vendor/lodash/.gitignore create mode 100644 public/vendor/lodash/.jscsrc create mode 100644 public/vendor/lodash/.markdown-doctest-setup.js create mode 100644 public/vendor/lodash/.travis.yml create mode 100644 public/vendor/lodash/LICENSE create mode 100644 public/vendor/lodash/README.md create mode 100644 public/vendor/lodash/dist/lodash.core.js create mode 100644 public/vendor/lodash/dist/lodash.core.min.js create mode 100644 public/vendor/lodash/dist/lodash.fp.js create mode 100644 public/vendor/lodash/dist/lodash.fp.min.js create mode 100644 public/vendor/lodash/dist/lodash.js create mode 100644 public/vendor/lodash/dist/lodash.min.js create mode 100644 public/vendor/lodash/dist/mapping.fp.js create mode 100644 public/vendor/lodash/doc/README.md create mode 100644 public/vendor/lodash/fp/_baseConvert.js create mode 100644 public/vendor/lodash/fp/_convertBrowser.js create mode 100644 public/vendor/lodash/fp/_mapping.js create mode 100644 public/vendor/lodash/fp/placeholder.js create mode 100644 public/vendor/lodash/lib/common/file.js create mode 100644 public/vendor/lodash/lib/common/mapping.js create mode 100644 public/vendor/lodash/lib/common/minify.js create mode 100644 public/vendor/lodash/lib/common/uglify.options.js create mode 100644 public/vendor/lodash/lib/common/util.js create mode 100644 public/vendor/lodash/lib/fp/build-dist.js create mode 100644 public/vendor/lodash/lib/fp/build-doc.js create mode 100644 public/vendor/lodash/lib/fp/build-modules.js create mode 100644 public/vendor/lodash/lib/fp/template/doc/wiki.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/_falseOptions.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/_util.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/alias.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/category.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/convert.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/fp.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/module.jst create mode 100644 public/vendor/lodash/lib/fp/template/modules/thru.jst create mode 100644 public/vendor/lodash/lib/main/build-dist.js create mode 100644 public/vendor/lodash/lib/main/build-doc.js create mode 100644 public/vendor/lodash/lib/main/build-modules.js create mode 100644 public/vendor/lodash/lib/main/build-site.js create mode 100644 public/vendor/lodash/lodash.js create mode 100644 public/vendor/lodash/package.json create mode 100644 public/vendor/lodash/perf/asset/perf-ui.js create mode 100644 public/vendor/lodash/perf/index.html create mode 100644 public/vendor/lodash/perf/perf.js create mode 100644 public/vendor/lodash/test/asset/test-ui.js create mode 100644 public/vendor/lodash/test/asset/worker.js create mode 100644 public/vendor/lodash/test/backbone.html create mode 100644 public/vendor/lodash/test/fp.html create mode 100644 public/vendor/lodash/test/index.html create mode 100644 public/vendor/lodash/test/remove.js create mode 100644 public/vendor/lodash/test/saucelabs.js create mode 100644 public/vendor/lodash/test/test-fp.js create mode 100644 public/vendor/lodash/test/test.js create mode 100644 public/vendor/lodash/test/underscore.html create mode 100644 public/vendor/lodash/vendor/backbone/LICENSE create mode 100644 public/vendor/lodash/vendor/backbone/backbone.js create mode 100644 public/vendor/lodash/vendor/backbone/test/collection.js create mode 100644 public/vendor/lodash/vendor/backbone/test/events.js create mode 100644 public/vendor/lodash/vendor/backbone/test/model.js create mode 100644 public/vendor/lodash/vendor/backbone/test/noconflict.js create mode 100644 public/vendor/lodash/vendor/backbone/test/router.js create mode 100644 public/vendor/lodash/vendor/backbone/test/setup/dom-setup.js create mode 100644 public/vendor/lodash/vendor/backbone/test/setup/environment.js create mode 100644 public/vendor/lodash/vendor/backbone/test/sync.js create mode 100644 public/vendor/lodash/vendor/backbone/test/view.js create mode 100644 public/vendor/lodash/vendor/firebug-lite/license.txt create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/blank.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBg.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/detach.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/detachHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/disable.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/disable.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/down.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/downActive.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/downHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/group.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/html.css create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/loading_16.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/min.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/minHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/off.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/offHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/search.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/search.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/shadow.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/shadow2.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/sprite.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabLeft.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMid.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tabRight.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tree_close.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/tree_open.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/up.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/upActive.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/upHover.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif create mode 100644 public/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.png create mode 100644 public/vendor/lodash/vendor/firebug-lite/src/firebug-lite-debug.js create mode 100644 public/vendor/lodash/vendor/json-js/json2.js create mode 100644 public/vendor/lodash/vendor/underscore/LICENSE create mode 100644 public/vendor/lodash/vendor/underscore/test/arrays.js create mode 100644 public/vendor/lodash/vendor/underscore/test/chaining.js create mode 100644 public/vendor/lodash/vendor/underscore/test/collections.js create mode 100644 public/vendor/lodash/vendor/underscore/test/cross-document.js create mode 100644 public/vendor/lodash/vendor/underscore/test/functions.js create mode 100644 public/vendor/lodash/vendor/underscore/test/objects.js create mode 100644 public/vendor/lodash/vendor/underscore/test/utility.js create mode 100644 public/vendor/lodash/vendor/underscore/underscore-min.js create mode 100644 public/vendor/lodash/vendor/underscore/underscore.js diff --git a/bower.json b/bower.json index 6f0eee8fbff..8d14ace8610 100644 --- a/bower.json +++ b/bower.json @@ -13,12 +13,12 @@ "tests" ], "dependencies": { - "jquery": "~3.1.0", - "angular": "~1.5.9", - "angular-route": "~1.5.9", - "angular-mocks": "~1.5.9", - "angular-sanitize": "~1.5.9", - "angular-native-dragdrop": "~1.2.2", - "angular-bindonce": "~0.3.3" + "jquery": "3.1.0", + "angular": "1.5.3", + "angular-route": "1.5.3", + "angular-mocks": "1.5.3", + "angular-sanitize": "1.5.3", + "angular-native-dragdrop": "1.2.2", + "angular-bindonce": "0.3.3" } } diff --git a/public/vendor/angular-bindonce/.bower.json b/public/vendor/angular-bindonce/.bower.json index 313c654cc14..9eef82db403 100644 --- a/public/vendor/angular-bindonce/.bower.json +++ b/public/vendor/angular-bindonce/.bower.json @@ -30,8 +30,7 @@ "tag": "0.3.3", "commit": "0fcf71e6effc88179893c9c06baf6c6bf9037632" }, - "_source": "git://github.com/Pasvaz/bindonce.git", - "_target": "~0.3.3", - "_originalSource": "angular-bindonce", - "_direct": true + "_source": "https://github.com/Pasvaz/bindonce.git", + "_target": "0.3.3", + "_originalSource": "angular-bindonce" } \ No newline at end of file diff --git a/public/vendor/angular-mocks/.bower.json b/public/vendor/angular-mocks/.bower.json index 9c007005ad9..563c2635a8a 100644 --- a/public/vendor/angular-mocks/.bower.json +++ b/public/vendor/angular-mocks/.bower.json @@ -1,20 +1,20 @@ { "name": "angular-mocks", - "version": "1.5.9-build.5020+sha.0784977", + "version": "1.5.3", "license": "MIT", "main": "./angular-mocks.js", "ignore": [], "dependencies": { - "angular": "1.5.9-build.5020+sha.0784977" + "angular": "1.5.3" }, "homepage": "https://github.com/angular/bower-angular-mocks", - "_release": "1.5.9-build.5020+sha.0784977", + "_release": "1.5.3", "_resolution": { "type": "version", - "tag": "v1.5.9-build.5020+sha.0784977", - "commit": "f390789bb409cabbcaec5e3d316e467c9df5863f" + "tag": "v1.5.3", + "commit": "319557fe710cecc11e12c772cc1abb8098d29ccb" }, "_source": "https://github.com/angular/bower-angular-mocks.git", - "_target": "~1.5.9", + "_target": "1.5.3", "_originalSource": "angular-mocks" } \ No newline at end of file diff --git a/public/vendor/angular-mocks/LICENSE.md b/public/vendor/angular-mocks/LICENSE.md deleted file mode 100644 index 2c395eef1ba..00000000000 --- a/public/vendor/angular-mocks/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Angular - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/public/vendor/angular-mocks/README.md b/public/vendor/angular-mocks/README.md index 61b9d8c0831..440cce9b78a 100644 --- a/public/vendor/angular-mocks/README.md +++ b/public/vendor/angular-mocks/README.md @@ -20,7 +20,7 @@ You can `require` ngMock modules: var angular = require('angular'); angular.module('myMod', [ require('angular-animate'), - require('angular-mocks/ngMock'), + require('angular-mocks/ngMock') require('angular-mocks/ngAnimateMock') ]); ``` diff --git a/public/vendor/angular-mocks/angular-mocks.js b/public/vendor/angular-mocks/angular-mocks.js index 1917193cff6..a1fc2fcf7ae 100644 --- a/public/vendor/angular-mocks/angular-mocks.js +++ b/public/vendor/angular-mocks/angular-mocks.js @@ -1,9 +1,9 @@ /** - * @license AngularJS v1.5.9-build.5020+sha.0784977 + * @license AngularJS v1.5.3 * (c) 2010-2016 Google, Inc. http://angularjs.org * License: MIT */ -(function(window, angular) { +(function(window, angular, undefined) { 'use strict'; @@ -13,7 +13,6 @@ * @description * * Namespace from 'angular-mocks.js' which contains testing related code. - * */ angular.mock = {}; @@ -25,7 +24,7 @@ angular.mock = {}; * @description * This service is a mock implementation of {@link ng.$browser}. It provides fake * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, - * cookies, etc. + * cookies, etc... * * The api of this service is the same as that of the real {@link ng.$browser $browser}, except * that there are several helper methods available which can be used in tests. @@ -40,7 +39,7 @@ angular.mock.$Browser = function() { var self = this; this.isMock = true; - self.$$url = 'http://server/'; + self.$$url = "http://server/"; self.$$lastUrl = self.$$url; // used by url polling fn self.pollFns = []; @@ -113,29 +112,19 @@ angular.mock.$Browser = function() { * @param {number=} number of milliseconds to flush. See {@link #defer.now} */ self.defer.flush = function(delay) { - var nextTime; - if (angular.isDefined(delay)) { - // A delay was passed so compute the next time - nextTime = self.defer.now + delay; + self.defer.now += delay; } else { if (self.deferredFns.length) { - // No delay was passed so set the next time so that it clears the deferred queue - nextTime = self.deferredFns[self.deferredFns.length - 1].time; + self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; } else { - // No delay passed, but there are no deferred tasks so flush - indicates an error! throw new Error('No deferred tasks to be flushed'); } } - while (self.deferredFns.length && self.deferredFns[0].time <= nextTime) { - // Increment the time and call the next deferred function - self.defer.now = self.deferredFns[0].time; + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { self.deferredFns.shift().fn(); } - - // Ensure that the current time is correct - self.defer.now = nextTime; }; self.$$baseHref = '/'; @@ -237,13 +226,13 @@ angular.mock.$ExceptionHandlerProvider = function() { * @param {string} mode Mode of operation, defaults to `rethrow`. * * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` - * mode stores an array of errors in `$exceptionHandler.errors`, to allow later assertion of - * them. See {@link ngMock.$log#assertEmpty assertEmpty()} and - * {@link ngMock.$log#reset reset()}. + * mode stores an array of errors in `$exceptionHandler.errors`, to allow later + * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there - * is a bug in the application or test, so this mock will make these tests fail. For any - * implementations that expect exceptions to be thrown, the `rethrow` mode will also maintain - * a log of thrown errors in `$exceptionHandler.errors`. + * is a bug in the application or test, so this mock will make these tests fail. + * For any implementations that expect exceptions to be thrown, the `rethrow` mode + * will also maintain a log of thrown errors. */ this.mode = function(mode) { @@ -252,19 +241,19 @@ angular.mock.$ExceptionHandlerProvider = function() { case 'rethrow': var errors = []; handler = function(e) { - if (arguments.length === 1) { + if (arguments.length == 1) { errors.push(e); } else { errors.push([].slice.call(arguments, 0)); } - if (mode === 'rethrow') { + if (mode === "rethrow") { throw e; } }; handler.errors = errors; break; default: - throw new Error('Unknown mode \'' + mode + '\', only \'log\'/\'rethrow\' modes are allowed!'); + throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); } }; @@ -414,8 +403,8 @@ angular.mock.$LogProvider = function() { }); }); if (errors.length) { - errors.unshift('Expected $log to be empty! Either a message was logged unexpectedly, or ' + - 'an expected log message was not checked and removed:'); + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + + "an expected log message was not checked and removed:"); errors.push(''); throw new Error(errors.join('\n---------\n')); } @@ -463,7 +452,7 @@ angular.mock.$IntervalProvider = function() { promise = deferred.promise; count = (angular.isDefined(count)) ? count : 0; - promise.then(null, function() {}, (!hasParams) ? fn : function() { + promise.then(null, null, (!hasParams) ? fn : function() { fn.apply(null, args); }); @@ -523,7 +512,6 @@ angular.mock.$IntervalProvider = function() { }); if (angular.isDefined(fnIndex)) { - repeatFns[fnIndex].deferred.promise.then(undefined, function() {}); repeatFns[fnIndex].deferred.reject('canceled'); repeatFns.splice(fnIndex, 1); return true; @@ -559,13 +547,16 @@ angular.mock.$IntervalProvider = function() { }; -function jsonStringToDate(string) { - // The R_ISO8061_STR regex is never going to fit into the 100 char limit! - // eslit-disable-next-line max-len - var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; +/* jshint -W101 */ +/* The R_ISO8061_STR regex is never going to fit into the 100 char limit! + * This directive should go inside the anonymous function but a bug in JSHint means that it would + * not be enacted early enough to prevent the warning. + */ +var R_ISO8061_STR = /^(-?\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; +function jsonStringToDate(string) { var match; - if ((match = string.match(R_ISO8061_STR))) { + if (match = string.match(R_ISO8061_STR)) { var date = new Date(0), tzHour = 0, tzMin = 0; @@ -648,10 +639,9 @@ angular.mock.TzDate = function(offset, timestamp) { timestamp = self.origDate.getTime(); if (isNaN(timestamp)) { - // eslint-disable-next-line no-throw-literal throw { - name: 'Illegal Argument', - message: 'Arg \'' + tsStr + '\' passed into TzDate constructor is not a valid date string' + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" }; } } else { @@ -757,7 +747,7 @@ angular.mock.TzDate = function(offset, timestamp) { angular.forEach(unimplementedMethods, function(methodName) { self[methodName] = function() { - throw new Error('Method \'' + methodName + '\' is not implemented in the TzDate mock'); + throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); }; }); @@ -766,6 +756,7 @@ angular.mock.TzDate = function(offset, timestamp) { //make "tzDateInstance instanceof Date" return true angular.mock.TzDate.prototype = Date.prototype; +/* jshint +W101 */ /** @@ -775,8 +766,6 @@ angular.mock.TzDate.prototype = Date.prototype; * @description * Mock implementation of the {@link ng.$animate `$animate`} service. Exposes two additional methods * for testing animations. - * - * You need to require the `ngAnimateMock` module in your test suite for instance `beforeEach(module('ngAnimateMock'))` */ angular.mock.animate = angular.module('ngAnimateMock', ['ng']) @@ -942,10 +931,13 @@ angular.mock.animate = angular.module('ngAnimateMock', ['ng']) * @name angular.mock.dump * @description * - * *NOTE*: This is not an injectable instance, just a globally available function. + * *NOTE*: this is not an injectable instance, just a globally available function. * - * Method for serializing common angular objects (scope, elements, etc..) into strings. - * It is useful for logging objects to the console when debugging. + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for + * debugging. + * + * This method is also available on window, where it can be used to display objects on debug + * console. * * @param {*} object - any object to turn into string. * @return {string} a serialized string of the argument @@ -1011,10 +1003,8 @@ angular.mock.dump = function(object) { * Fake HTTP backend implementation suitable for unit testing applications that use the * {@link ng.$http $http service}. * - *
- * **Note**: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. - *
* * During unit testing, we want our unit tests to run quickly and have no external dependencies so * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or @@ -1213,7 +1203,7 @@ angular.mock.dump = function(object) { $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { // check if the header was sent, if it wasn't the expectation won't // match the request and the test will fail - return headers['Authorization'] === 'xxx'; + return headers['Authorization'] == 'xxx'; }).respond(201, ''); $rootScope.saveMessage('whatever'); @@ -1338,15 +1328,12 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { } // TODO(vojta): change params to: method, url, data, headers, callback - function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers) { + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials, responseType) { var xhr = new MockXhr(), expectation = expectations[0], wasExpected = false; - xhr.$$events = eventHandlers; - xhr.upload.$$events = uploadEventHandlers; - function prettyPrint(data) { return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) ? data @@ -1355,11 +1342,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { function wrapResponse(wrapped) { if (!$browser && timeout) { - if (timeout.then) { - timeout.then(handleTimeout); - } else { - $timeout(handleTimeout, timeout); - } + timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout); } return handleResponse; @@ -1410,7 +1393,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { // if $browser specified, we do auto flush all requests ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); } else if (definition.passThrough) { - $delegate(method, url, data, callback, headers, timeout, withCredentials, responseType, eventHandlers, uploadEventHandlers); + $delegate(method, url, data, callback, headers, timeout, withCredentials, responseType); } else throw new Error('No response defined !'); return; } @@ -1428,7 +1411,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * Creates a new backend definition. * * @param {string} method HTTP method. - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. @@ -1440,19 +1423,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * order to change how a matched request is handled. * * - respond – - * ```js - * {function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers, params)} - * ``` + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers, params)}` * – The respond method takes a set of static data to be returned or a function that can - * return an array containing response status (number), response data (Array|Object|string), - * response headers (Object), and the text for the status (string). The respond method returns - * the `requestHandler` object for possible overrides. + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. */ $httpBackend.when = function(method, url, data, headers, keys) { - - assertArgDefined(arguments, 1, 'url'); - var definition = new MockHttpExpectation(method, url, data, headers, keys), chain = { respond: function(status, data, headers, statusText) { @@ -1480,7 +1458,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for GET requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1495,7 +1473,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1510,7 +1488,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1525,7 +1503,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for POST requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. @@ -1542,7 +1520,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives * data string and returns true if the data is as expected. @@ -1559,7 +1537,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. * @returns {requestHandler} Returns an object with `respond` method that controls how a matched @@ -1622,7 +1600,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * Creates a new request expectation. * * @param {string} method HTTP method. - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1635,19 +1613,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * order to change how a matched request is handled. * * - respond – - * ``` - * { function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers, params)} - * ``` + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers, params)}` * – The respond method takes a set of static data to be returned or a function that can - * return an array containing response status (number), response data (Array|Object|string), - * response headers (Object), and the text for the status (string). The respond method returns - * the `requestHandler` object for possible overrides. + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. */ $httpBackend.expect = function(method, url, data, headers, keys) { - - assertArgDefined(arguments, 1, 'url'); - var expectation = new MockHttpExpectation(method, url, data, headers, keys), chain = { respond: function(status, data, headers, statusText) { @@ -1666,7 +1639,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for GET requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {Object=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1681,7 +1654,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for HEAD requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {Object=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1696,7 +1669,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for DELETE requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {Object=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. @@ -1711,7 +1684,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for POST requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1729,7 +1702,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for PUT requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1747,7 +1720,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for PATCH requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that * receives data string and returns true if the data is as expected, or Object if request body @@ -1765,7 +1738,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @description * Creates a new request expectation for JSONP requests. For more info see `expect()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives an url + * @param {string|RegExp|function(string)} url HTTP url or function that receives an url * and returns true if the url matches the current definition. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described above. * @returns {requestHandler} Returns an object with `respond` method that controls how a matched @@ -1796,34 +1769,24 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * @ngdoc method * @name $httpBackend#flush * @description - * Flushes pending requests using the trained responses. Requests are flushed in the order they - * were made, but it is also possible to skip one or more requests (for example to have them - * flushed later). This is useful for simulating scenarios where responses arrive from the server - * in any order. + * Flushes all pending requests using the trained responses. * - * If there are no pending requests to flush when the method is called, an exception is thrown (as - * this is typically a sign of programming error). - * - * @param {number=} count - Number of responses to flush. If undefined/null, all pending requests - * (starting after `skip`) will be flushed. - * @param {number=} [skip=0] - Number of pending requests to skip. For example, a value of `5` - * would skip the first 5 pending requests and start flushing from the 6th onwards. + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). */ - $httpBackend.flush = function(count, skip, digest) { + $httpBackend.flush = function(count, digest) { if (digest !== false) $rootScope.$digest(); - - skip = skip || 0; - if (skip >= responses.length) throw new Error('No pending request to flush !'); + if (!responses.length) throw new Error('No pending request to flush !'); if (angular.isDefined(count) && count !== null) { while (count--) { - var part = responses.splice(skip, 1); - if (!part.length) throw new Error('No more pending request to flush !'); - part[0](); + if (!responses.length) throw new Error('No more pending request to flush !'); + responses.shift()(); } } else { - while (responses.length > skip) { - responses.splice(skip, 1)[0](); + while (responses.length) { + responses.shift()(); } } $httpBackend.verifyNoOutstandingExpectation(digest); @@ -1865,8 +1828,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { * afterEach($httpBackend.verifyNoOutstandingRequest); * ``` */ - $httpBackend.verifyNoOutstandingRequest = function(digest) { - if (digest !== false) $rootScope.$digest(); + $httpBackend.verifyNoOutstandingRequest = function() { if (responses.length) { throw new Error('Unflushed requests: ' + responses.length); } @@ -1892,52 +1854,25 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { function createShortMethods(prefix) { angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { $httpBackend[prefix + method] = function(url, headers, keys) { - assertArgDefined(arguments, 0, 'url'); - - // Change url to `null` if `undefined` to stop it throwing an exception further down - if (angular.isUndefined(url)) url = null; - return $httpBackend[prefix](method, url, undefined, headers, keys); }; }); angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { $httpBackend[prefix + method] = function(url, data, headers, keys) { - assertArgDefined(arguments, 0, 'url'); - - // Change url to `null` if `undefined` to stop it throwing an exception further down - if (angular.isUndefined(url)) url = null; - return $httpBackend[prefix](method, url, data, headers, keys); }; }); } } -function assertArgDefined(args, index, name) { - if (args.length > index && angular.isUndefined(args[index])) { - throw new Error('Undefined argument `' + name + '`; the argument is provided but not defined'); - } -} - - function MockHttpExpectation(method, url, data, headers, keys) { - function getUrlParams(u) { - var params = u.slice(u.indexOf('?') + 1).split('&'); - return params.sort(); - } - - function compareUrl(u) { - return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) && - getUrlParams(url).join() === getUrlParams(u).join()); - } - this.data = data; this.headers = headers; this.match = function(m, u, d, h) { - if (method !== m) return false; + if (method != m) return false; if (!this.matchUrl(u)) return false; if (angular.isDefined(d) && !this.matchData(d)) return false; if (angular.isDefined(h) && !this.matchHeaders(h)) return false; @@ -1948,7 +1883,7 @@ function MockHttpExpectation(method, url, data, headers, keys) { if (!url) return true; if (angular.isFunction(url.test)) return url.test(u); if (angular.isFunction(url)) return url(u); - return (url === u || compareUrl(u)); + return url == u; }; this.matchHeaders = function(h) { @@ -1964,7 +1899,6 @@ function MockHttpExpectation(method, url, data, headers, keys) { if (data && !angular.isString(data)) { return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); } - // eslint-disable-next-line eqeqeq return data == d; }; @@ -1996,7 +1930,7 @@ function MockHttpExpectation(method, url, data, headers, keys) { var obj = {}, key_value, key, queryStr = u.indexOf('?') > -1 ? u.substring(u.indexOf('?') + 1) - : ''; + : ""; angular.forEach(queryStr.split('&'), function(keyValue) { if (keyValue) { @@ -2063,7 +1997,7 @@ function MockXhr() { header = undefined; angular.forEach(this.$$respHeaders, function(headerVal, headerName) { - if (!header && angular.lowercase(headerName) === name) header = headerVal; + if (!header && angular.lowercase(headerName) == name) header = headerVal; }); return header; }; @@ -2078,20 +2012,6 @@ function MockXhr() { }; this.abort = angular.noop; - - // This section simulates the events on a real XHR object (and the upload object) - // When we are testing $httpBackend (inside the angular project) we make partial use of this - // but store the events directly ourselves on `$$events`, instead of going through the `addEventListener` - this.$$events = {}; - this.addEventListener = function(name, listener) { - if (angular.isUndefined(this.$$events[name])) this.$$events[name] = []; - this.$$events[name].push(listener); - }; - - this.upload = { - $$events: {}, - addEventListener: this.addEventListener - }; } @@ -2136,7 +2056,7 @@ angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $ function formatPendingTasksAsString(tasks) { var result = []; angular.forEach(tasks, function(task) { - result.push('{id: ' + task.id + ', time: ' + task.time + '}'); + result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); }); return result.join(', '); @@ -2246,15 +2166,9 @@ angular.mock.$RootElementProvider = function() { angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { return function(expression, locals, later, ident) { if (later && typeof later === 'object') { - var instantiate = $delegate(expression, locals, true, ident); - angular.extend(instantiate.instance, later); - - var instance = instantiate(); - if (instance !== instantiate.instance) { - angular.extend(instance, later); - } - - return instance; + var create = $delegate(expression, locals, true, ident); + angular.extend(create.instance, later); + return create(); } return $delegate(expression, locals, later, ident); }; @@ -2264,21 +2178,12 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { * @ngdoc service * @name $componentController * @description - * A service that can be used to create instances of component controllers. Useful for unit-testing. - * + * A service that can be used to create instances of component controllers. + *
* Be aware that the controller will be instantiated and attached to the scope as specified in - * the component definition object. If you do not provide a `$scope` object in the `locals` param - * then the helper will create a new isolated scope as a child of `$rootScope`. - * - * If you are using `$element` or `$attrs` in the controller, make sure to provide them as `locals`. - * The `$element` must be a jqLite-wrapped DOM element, and `$attrs` should be an object that - * has all properties / functions that you are using in the controller. If this is getting too complex, - * you should compile the component instead and access the component's controller via the - * {@link angular.element#methods `controller`} function. - * - * See also the section on {@link guide/component#unit-testing-component-controllers unit-testing component controllers} - * in the guide. - * + * the component definition object. That means that you must always provide a `$scope` object + * in the `locals` param. + *
* @param {string} componentName the name of the component whose controller we want to instantiate * @param {Object} locals Injection locals for Controller. * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used @@ -2286,9 +2191,8 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { * @param {string=} ident Override the property name to use when attaching the controller to the scope. * @return {Object} Instance of requested controller. */ -angular.mock.$ComponentControllerProvider = ['$compileProvider', - function ComponentControllerProvider($compileProvider) { - this.$get = ['$controller','$injector', '$rootScope', function($controller, $injector, $rootScope) { +angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) { + this.$get = ['$controller','$injector', function($controller,$injector) { return function $componentController(componentName, locals, bindings, ident) { // get all directives associated to the component name var directives = $injector.get(componentName + 'Directive'); @@ -2306,9 +2210,6 @@ angular.mock.$ComponentControllerProvider = ['$compileProvider', } // get the info of the component var directiveInfo = candidateDirectives[0]; - // create a scope if needed - locals = locals || {}; - locals.$scope = locals.$scope || $rootScope.$new(true); return $controller(directiveInfo.controller, locals, bindings, ident || directiveInfo.controllerAs); }; }]; @@ -2330,34 +2231,6 @@ angular.mock.$ComponentControllerProvider = ['$compileProvider', * *
* - * @installation - * - * First, download the file: - * * [Google CDN](https://developers.google.com/speed/libraries/devguide#angularjs) e.g. - * `"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/angular-mocks.js"` - * * [NPM](https://www.npmjs.com/) e.g. `npm install angular-mocks@X.Y.Z` - * * [Bower](http://bower.io) e.g. `bower install angular-mocks#X.Y.Z` - * * [code.angularjs.org](https://code.angularjs.org/) (discouraged for production use) e.g. - * `"//code.angularjs.org/X.Y.Z/angular-mocks.js"` - * - * where X.Y.Z is the AngularJS version you are running. - * - * Then, configure your test runner to load `angular-mocks.js` after `angular.js`. - * This example uses Karma: - * - * ``` - * config.set({ - * files: [ - * 'build/angular.js', // and other module files you need - * 'build/angular-mocks.js', - * '', - * '' - * ] - * }); - * ``` - * - * Including the `angular-mocks.js` file automatically adds the `ngMock` module, so your tests - * are ready to go! */ angular.module('ngMock', ['ng']).provider({ $browser: angular.mock.$BrowserProvider, @@ -2386,7 +2259,6 @@ angular.module('ngMock', ['ng']).provider({ * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. */ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { - $provide.value('$httpBackend', angular.injector(['ng']).get('$httpBackend')); $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); }]); @@ -2398,10 +2270,8 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of * applications that use the {@link ng.$http $http service}. * - *
- * **Note**: For fake http backend implementation suitable for unit testing please see + * *Note*: For fake http backend implementation suitable for unit testing please see * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. - *
* * This implementation can be used to respond with static or dynamic responses via the `when` api * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the @@ -2422,9 +2292,9 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * on the `ngMockE2E` and your application modules and defines the fake backend: * * ```js - * var myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); + * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); * myAppDev.run(function($httpBackend) { - * var phones = [{name: 'phone1'}, {name: 'phone2'}]; + * phones = [{name: 'phone1'}, {name: 'phone2'}]; * * // returns the current list of phones * $httpBackend.whenGET('/phones').respond(phones); @@ -2435,74 +2305,12 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * phones.push(phone); * return [200, phone, {}]; * }); - * $httpBackend.whenGET(/^\/templates\//).passThrough(); // Requests for templates are handled by the real server + * $httpBackend.whenGET(/^\/templates\//).passThrough(); * //... * }); * ``` * * Afterwards, bootstrap your app with this new module. - * - * ## Example - * - * - * var myApp = angular.module('myApp', []); - * - * myApp.controller('MainCtrl', function MainCtrl($http) { - * var ctrl = this; - * - * ctrl.phones = []; - * ctrl.newPhone = { - * name: '' - * }; - * - * ctrl.getPhones = function() { - * $http.get('/phones').then(function(response) { - * ctrl.phones = response.data; - * }); - * }; - * - * ctrl.addPhone = function(phone) { - * $http.post('/phones', phone).then(function() { - * ctrl.newPhone = {name: ''}; - * return ctrl.getPhones(); - * }); - * }; - * - * ctrl.getPhones(); - * }); - * - * - * var myAppDev = angular.module('myAppE2E', ['myApp', 'ngMockE2E']); - * - * myAppDev.run(function($httpBackend) { - * var phones = [{name: 'phone1'}, {name: 'phone2'}]; - * - * // returns the current list of phones - * $httpBackend.whenGET('/phones').respond(phones); - * - * // adds a new phone to the phones array - * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { - * var phone = angular.fromJson(data); - * phones.push(phone); - * return [200, phone, {}]; - * }); - * }); - * - * - *
- *
- * - * - *
- *

Phones

- *
    - *
  • {{phone.name}}
  • - *
- *
- *
- *
- * - * */ /** @@ -2513,7 +2321,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * Creates a new backend definition. * * @param {string} method HTTP method. - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header @@ -2525,13 +2333,11 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * `respond` or `passThrough` again in order to change how a matched request is handled. * * - respond – - * ``` - * { function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers, params)} - * ``` + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers, params)}` * – The respond method takes a set of static data to be returned or a function that can return - * an array containing response status (number), response data (Array|Object|string), response - * headers (Object), and the text for the status (string). + * an array containing response status (number), response data (string), response headers + * (Object), and the text for the status (string). * - passThrough – `{function()}` – Any request matching a backend definition with * `passThrough` handler will be passed through to the real backend (an XHR request will be made * to the server.) @@ -2545,7 +2351,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for GET requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on @@ -2562,7 +2368,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for HEAD requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on @@ -2579,7 +2385,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for DELETE requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Object|function(Object))=} headers HTTP headers. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on @@ -2596,7 +2402,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for POST requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. @@ -2614,7 +2420,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for PUT requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. @@ -2632,7 +2438,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for PATCH requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(string|RegExp)=} data HTTP request body. * @param {(Object|function(Object))=} headers HTTP headers. @@ -2650,7 +2456,7 @@ angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { * @description * Creates a new backend definition for JSONP requests. For more info see `when()`. * - * @param {string|RegExp|function(string)=} url HTTP url or function that receives a url + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url * and returns true if the url matches the current definition. * @param {(Array)=} keys Array of keys to assign to regex matches in request url described on * {@link ngMock.$httpBackend $httpBackend mock}. @@ -2702,7 +2508,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { * @ngdoc method * @name $rootScope.Scope#$countChildScopes * @module ngMock - * @this $rootScope.Scope * @description * Counts all the direct and indirect child scopes of the current scope. * @@ -2711,6 +2516,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { * @returns {number} Total number of child scopes. */ function countChildScopes() { + // jshint validthis: true var count = 0; // exclude the current scope var pendingChildHeads = [this.$$childHead]; var currentScope; @@ -2732,7 +2538,6 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { /** * @ngdoc method * @name $rootScope.Scope#$countWatchers - * @this $rootScope.Scope * @module ngMock * @description * Counts all the watchers of direct and indirect child scopes of the current scope. @@ -2743,6 +2548,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { * @returns {number} Total number of watchers. */ function countWatchers() { + // jshint validthis: true var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope var pendingChildHeads = [this.$$childHead]; var currentScope; @@ -2762,7 +2568,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { }]; -(function(jasmineOrMocha) { +!(function(jasmineOrMocha) { if (!jasmineOrMocha) { return; @@ -2857,7 +2663,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { * * You cannot call `sharedInjector()` from within a context already using `sharedInjector()`. * - * ## Example + * ## Example * * Typically beforeAll is used to make many assertions about a single operation. This can * cut down test run-time as the test setup doesn't need to be re-run, and enabling focussed @@ -2895,14 +2701,14 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { */ module.sharedInjector = function() { if (!(module.$$beforeAllHook && module.$$afterAllHook)) { - throw Error('sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll'); + throw Error("sharedInjector() cannot be used unless your test runner defines beforeAll/afterAll"); } var initialized = false; - module.$$beforeAllHook(/** @this */ function() { + module.$$beforeAllHook(function() { if (injectorState.shared) { - injectorState.sharedError = Error('sharedInjector() cannot be called inside a context that has already called sharedInjector()'); + injectorState.sharedError = Error("sharedInjector() cannot be called inside a context that has already called sharedInjector()"); throw injectorState.sharedError; } initialized = true; @@ -2921,10 +2727,10 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { }; module.$$beforeEach = function() { - if (injectorState.shared && currentSpec && currentSpec !== this) { + if (injectorState.shared && currentSpec && currentSpec != this) { var state = currentSpec; currentSpec = this; - angular.forEach(['$injector','$modules','$providerInjector', '$injectorStrict'], function(k) { + angular.forEach(["$injector","$modules","$providerInjector", "$injectorStrict"], function(k) { currentSpec[k] = state[k]; state[k] = null; }); @@ -2985,7 +2791,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { angular.forEach(angular.callbacks, function(val, key) { delete angular.callbacks[key]; }); - angular.callbacks.$$counter = 0; + angular.callbacks.counter = 0; }; (window.beforeEach || window.setup)(module.$$beforeEach); @@ -3079,7 +2885,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { - var ErrorAddingDeclarationLocationStack = function ErrorAddingDeclarationLocationStack(e, errorForStack) { + var ErrorAddingDeclarationLocationStack = function(e, errorForStack) { this.message = e.message; this.name = e.name; if (e.line) this.line = e.line; @@ -3088,20 +2894,14 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { this.stack = e.stack + '\n' + errorForStack.stack; if (e.stackArray) this.stackArray = e.stackArray; }; - ErrorAddingDeclarationLocationStack.prototype = Error.prototype; + ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; window.inject = angular.mock.inject = function() { var blockFns = Array.prototype.slice.call(arguments, 0); var errorForStack = new Error('Declaration Location'); - // IE10+ and PhanthomJS do not set stack trace information, until the error is thrown - if (!errorForStack.stack) { - try { - throw errorForStack; - } catch (e) { /* empty */ } - } - return wasInjectorCreated() ? WorkFn.call(currentSpec) : WorkFn; + return wasInjectorCreated() ? workFn.call(currentSpec) : workFn; ///////////////////// - function WorkFn() { + function workFn() { var modules = currentSpec.$modules || []; var strictDi = !!currentSpec.$injectorStrict; modules.unshift(['$injector', function($injector) { @@ -3114,7 +2914,7 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { if (strictDi) { // If strictDi is enabled, annotate the providerInjector blocks angular.forEach(modules, function(moduleFn) { - if (typeof moduleFn === 'function') { + if (typeof moduleFn === "function") { angular.injector.$$annotate(moduleFn); } }); @@ -3129,7 +2929,9 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { injector.annotate(blockFns[i]); } try { + /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ injector.invoke(blockFns[i] || angular.noop, this); + /* jshint +W040 */ } catch (e) { if (e.stack && errorForStack) { throw new ErrorAddingDeclarationLocationStack(e, errorForStack); @@ -3168,218 +2970,5 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { } })(window.jasmine || window.mocha); -'use strict'; - -(function() { - /** - * Triggers a browser event. Attempts to choose the right event if one is - * not specified. - * - * @param {Object} element Either a wrapped jQuery/jqLite node or a DOMElement - * @param {string} eventType Optional event type - * @param {Object=} eventData An optional object which contains additional event data (such as x,y - * coordinates, keys, etc...) that are passed into the event when triggered - */ - window.browserTrigger = function browserTrigger(element, eventType, eventData) { - if (element && !element.nodeName) element = element[0]; - if (!element) return; - - eventData = eventData || {}; - var relatedTarget = eventData.relatedTarget || element; - var keys = eventData.keys; - var x = eventData.x; - var y = eventData.y; - - var inputType = (element.type) ? element.type.toLowerCase() : null, - nodeName = element.nodeName.toLowerCase(); - if (!eventType) { - eventType = { - 'text': 'change', - 'textarea': 'change', - 'hidden': 'change', - 'password': 'change', - 'button': 'click', - 'submit': 'click', - 'reset': 'click', - 'image': 'click', - 'checkbox': 'click', - 'radio': 'click', - 'select-one': 'change', - 'select-multiple': 'change', - '_default_': 'click' - }[inputType || '_default_']; - } - - if (nodeName === 'option') { - element.parentNode.value = element.value; - element = element.parentNode; - eventType = 'change'; - } - - keys = keys || []; - function pressed(key) { - return keys.indexOf(key) !== -1; - } - - var evnt; - if (/transitionend/.test(eventType)) { - if (window.WebKitTransitionEvent) { - evnt = new window.WebKitTransitionEvent(eventType, eventData); - evnt.initEvent(eventType, false, true); - } else { - try { - evnt = new window.TransitionEvent(eventType, eventData); - } catch (e) { - evnt = window.document.createEvent('TransitionEvent'); - evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0); - } - } - } else if (/animationend/.test(eventType)) { - if (window.WebKitAnimationEvent) { - evnt = new window.WebKitAnimationEvent(eventType, eventData); - evnt.initEvent(eventType, false, true); - } else { - try { - evnt = new window.AnimationEvent(eventType, eventData); - } catch (e) { - evnt = window.document.createEvent('AnimationEvent'); - evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0); - } - } - } else if (/touch/.test(eventType) && supportsTouchEvents()) { - evnt = createTouchEvent(element, eventType, x, y); - } else if (/key/.test(eventType)) { - evnt = window.document.createEvent('Events'); - evnt.initEvent(eventType, eventData.bubbles, eventData.cancelable); - evnt.view = window; - evnt.ctrlKey = pressed('ctrl'); - evnt.altKey = pressed('alt'); - evnt.shiftKey = pressed('shift'); - evnt.metaKey = pressed('meta'); - evnt.keyCode = eventData.keyCode; - evnt.charCode = eventData.charCode; - evnt.which = eventData.which; - } else { - evnt = window.document.createEvent('MouseEvents'); - x = x || 0; - y = y || 0; - evnt.initMouseEvent(eventType, true, true, window, 0, x, y, x, y, pressed('ctrl'), - pressed('alt'), pressed('shift'), pressed('meta'), 0, relatedTarget); - } - - /* we're unable to change the timeStamp value directly so this - * is only here to allow for testing where the timeStamp value is - * read */ - evnt.$manualTimeStamp = eventData.timeStamp; - - if (!evnt) return; - - var originalPreventDefault = evnt.preventDefault, - appWindow = element.ownerDocument.defaultView, - fakeProcessDefault = true, - finalProcessDefault, - angular = appWindow.angular || {}; - - // igor: temporary fix for https://bugzilla.mozilla.org/show_bug.cgi?id=684208 - angular['ff-684208-preventDefault'] = false; - evnt.preventDefault = function() { - fakeProcessDefault = false; - return originalPreventDefault.apply(evnt, arguments); - }; - - if (!eventData.bubbles || supportsEventBubblingInDetachedTree() || isAttachedToDocument(element)) { - element.dispatchEvent(evnt); - } else { - triggerForPath(element, evnt); - } - - finalProcessDefault = !(angular['ff-684208-preventDefault'] || !fakeProcessDefault); - - delete angular['ff-684208-preventDefault']; - - return finalProcessDefault; - }; - - function supportsTouchEvents() { - if ('_cached' in supportsTouchEvents) { - return supportsTouchEvents._cached; - } - if (!window.document.createTouch || !window.document.createTouchList) { - supportsTouchEvents._cached = false; - return false; - } - try { - window.document.createEvent('TouchEvent'); - } catch (e) { - supportsTouchEvents._cached = false; - return false; - } - supportsTouchEvents._cached = true; - return true; - } - - function createTouchEvent(element, eventType, x, y) { - var evnt = new window.Event(eventType); - x = x || 0; - y = y || 0; - - var touch = window.document.createTouch(window, element, Date.now(), x, y, x, y); - var touches = window.document.createTouchList(touch); - - evnt.touches = touches; - - return evnt; - } - - function supportsEventBubblingInDetachedTree() { - if ('_cached' in supportsEventBubblingInDetachedTree) { - return supportsEventBubblingInDetachedTree._cached; - } - supportsEventBubblingInDetachedTree._cached = false; - var doc = window.document; - if (doc) { - var parent = doc.createElement('div'), - child = parent.cloneNode(); - parent.appendChild(child); - parent.addEventListener('e', function() { - supportsEventBubblingInDetachedTree._cached = true; - }); - var evnt = window.document.createEvent('Events'); - evnt.initEvent('e', true, true); - child.dispatchEvent(evnt); - } - return supportsEventBubblingInDetachedTree._cached; - } - - function triggerForPath(element, evnt) { - var stop = false; - - var _stopPropagation = evnt.stopPropagation; - evnt.stopPropagation = function() { - stop = true; - _stopPropagation.apply(evnt, arguments); - }; - patchEventTargetForBubbling(evnt, element); - do { - element.dispatchEvent(evnt); - // eslint-disable-next-line no-unmodified-loop-condition - } while (!stop && (element = element.parentNode)); - } - - function patchEventTargetForBubbling(event, target) { - event._target = target; - Object.defineProperty(event, 'target', {get: function() { return this._target;}}); - } - - function isAttachedToDocument(element) { - while ((element = element.parentNode)) { - if (element === window) { - return true; - } - } - return false; - } -})(); - })(window, window.angular); diff --git a/public/vendor/angular-mocks/bower.json b/public/vendor/angular-mocks/bower.json index a510d3e75f2..4ce65bbc809 100644 --- a/public/vendor/angular-mocks/bower.json +++ b/public/vendor/angular-mocks/bower.json @@ -1,10 +1,10 @@ { "name": "angular-mocks", - "version": "1.5.9-build.5020+sha.0784977", + "version": "1.5.3", "license": "MIT", "main": "./angular-mocks.js", "ignore": [], "dependencies": { - "angular": "1.5.9-build.5020+sha.0784977" + "angular": "1.5.3" } } diff --git a/public/vendor/angular-mocks/package.json b/public/vendor/angular-mocks/package.json index c23f1279087..42d78038650 100644 --- a/public/vendor/angular-mocks/package.json +++ b/public/vendor/angular-mocks/package.json @@ -1,6 +1,6 @@ { "name": "angular-mocks", - "version": "1.5.9-build.5020+sha.0784977", + "version": "1.5.3", "description": "AngularJS mocks for testing", "main": "angular-mocks.js", "scripts": { @@ -23,12 +23,5 @@ "bugs": { "url": "https://github.com/angular/angular.js/issues" }, - "homepage": "http://angularjs.org", - "jspm": { - "shim": { - "angular-mocks": { - "deps": ["angular"] - } - } - } + "homepage": "http://angularjs.org" } diff --git a/public/vendor/angular-native-dragdrop/.bower.json b/public/vendor/angular-native-dragdrop/.bower.json index 337c894a30e..722289aa9db 100644 --- a/public/vendor/angular-native-dragdrop/.bower.json +++ b/public/vendor/angular-native-dragdrop/.bower.json @@ -31,6 +31,6 @@ "commit": "e630636fdc39fef815c1c534340aa16caf76a04c" }, "_source": "https://github.com/angular-dragdrop/angular-dragdrop.git", - "_target": "~1.2.2", + "_target": "1.2.2", "_originalSource": "angular-native-dragdrop" } \ No newline at end of file diff --git a/public/vendor/angular-native-dragdrop/draganddrop.js b/public/vendor/angular-native-dragdrop/draganddrop.js index f1866c8374b..af8acac934b 100755 --- a/public/vendor/angular-native-dragdrop/draganddrop.js +++ b/public/vendor/angular-native-dragdrop/draganddrop.js @@ -290,12 +290,12 @@ sendData = angular.fromJson(sendData); var dropOffset = calculateDropOffset(e); - + var position = dropOffset ? { x: dropOffset.x - sendData.offset.x, y: dropOffset.y - sendData.offset.y } : null; - + determineEffectAllowed(e); var uiOnDropFn = $parse(attr.uiOnDrop); @@ -305,7 +305,7 @@ element.removeClass(dragEnterClass); dragging = 0; } - + function isDragChannelAccepted(dragChannel, dropChannel) { if (dropChannel === '*') { return true; diff --git a/public/vendor/angular-route/.bower.json b/public/vendor/angular-route/.bower.json index 8f1e6a8c9a0..e059146ff2d 100644 --- a/public/vendor/angular-route/.bower.json +++ b/public/vendor/angular-route/.bower.json @@ -1,20 +1,20 @@ { "name": "angular-route", - "version": "1.5.9-build.5020+sha.0784977", + "version": "1.5.3", "license": "MIT", "main": "./angular-route.js", "ignore": [], "dependencies": { - "angular": "1.5.9-build.5020+sha.0784977" + "angular": "1.5.3" }, "homepage": "https://github.com/angular/bower-angular-route", - "_release": "1.5.9-build.5020+sha.0784977", + "_release": "1.5.3", "_resolution": { "type": "version", - "tag": "v1.5.9-build.5020+sha.0784977", - "commit": "59b887d9b94bc1088f3efed333ae2366fafdad7c" + "tag": "v1.5.3", + "commit": "750e4833612071d30993c8e4a547a6982eba3b84" }, "_source": "https://github.com/angular/bower-angular-route.git", - "_target": "~1.5.9", + "_target": "1.5.3", "_originalSource": "angular-route" } \ No newline at end of file diff --git a/public/vendor/angular-route/LICENSE.md b/public/vendor/angular-route/LICENSE.md deleted file mode 100644 index 2c395eef1ba..00000000000 --- a/public/vendor/angular-route/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Angular - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/public/vendor/angular-route/angular-route.js b/public/vendor/angular-route/angular-route.js index 93ded6fc305..3c9a4e8595e 100644 --- a/public/vendor/angular-route/angular-route.js +++ b/public/vendor/angular-route/angular-route.js @@ -1,44 +1,9 @@ /** - * @license AngularJS v1.5.9-build.5020+sha.0784977 + * @license AngularJS v1.5.3 * (c) 2010-2016 Google, Inc. http://angularjs.org * License: MIT */ -(function(window, angular) {'use strict'; - -/* global shallowCopy: true */ - -/** - * Creates a shallow copy of an object, an array or a primitive. - * - * Assumes that there are no proto properties for objects. - */ -function shallowCopy(src, dst) { - if (isArray(src)) { - dst = dst || []; - - for (var i = 0, ii = src.length; i < ii; i++) { - dst[i] = src[i]; - } - } else if (isObject(src)) { - dst = dst || {}; - - for (var key in src) { - if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } - - return dst || src; -} - -/* global shallowCopy: false */ - -// `isArray` and `isObject` are necessary for `shallowCopy()` (included via `src/shallowCopy.js`). -// They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available. -var isArray; -var isObject; -var isDefined; +(function(window, angular, undefined) {'use strict'; /** * @ngdoc module @@ -55,22 +20,18 @@ var isDefined; * *
*/ -/* global -ngRouteModule */ -var ngRouteModule = angular. - module('ngRoute', []). - provider('$route', $RouteProvider). - // Ensure `$route` will be instantiated in time to capture the initial `$locationChangeSuccess` - // event (unless explicitly disabled). This is necessary in case `ngView` is included in an - // asynchronously loaded template. - run(instantiateRoute); -var $routeMinErr = angular.$$minErr('ngRoute'); -var isEagerInstantiationEnabled; - + /* global -ngRouteModule */ +var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider). + // Ensure `$route` will be instantiated in time to capture the initial + // `$locationChangeSuccess` event. This is necessary in case `ngView` is + // included in an asynchronously loaded template. + run(['$route', angular.noop]), + $routeMinErr = angular.$$minErr('ngRoute'); /** * @ngdoc provider * @name $routeProvider - * @this * * @description * @@ -83,10 +44,6 @@ var isEagerInstantiationEnabled; * Requires the {@link ngRoute `ngRoute`} module to be installed. */ function $RouteProvider() { - isArray = angular.isArray; - isObject = angular.isObject; - isDefined = angular.isDefined; - function inherit(parent, extra) { return angular.extend(Object.create(parent), extra); } @@ -122,12 +79,12 @@ function $RouteProvider() { * * Object properties: * - * - `controller` – `{(string|Function)=}` – Controller fn that should be associated with + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with * newly created scope or the name of a {@link angular.Module#controller registered * controller} if passed as a string. * - `controllerAs` – `{string=}` – An identifier name for a reference to the controller. * If present, the controller will be published to scope under the `controllerAs` name. - * - `template` – `{(string|Function)=}` – html template as a string or a function that + * - `template` – `{string=|function()=}` – html template as a string or a function that * returns an html template as a string which should be used by {@link * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. * This property takes precedence over `templateUrl`. @@ -137,7 +94,7 @@ function $RouteProvider() { * - `{Array.}` - route parameters extracted from the current * `$location.path()` by applying the current route * - * - `templateUrl` – `{(string|Function)=}` – path or function that returns a path to an html + * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html * template that should be used by {@link ngRoute.directive:ngView ngView}. * * If `templateUrl` is a function, it will be called with the following parameters: @@ -145,7 +102,7 @@ function $RouteProvider() { * - `{Array.}` - route parameters extracted from the current * `$location.path()` by applying the current route * - * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * - `resolve` - `{Object.=}` - An optional map of dependencies which should * be injected into the controller. If any of these dependencies are promises, the router * will wait for them all to be resolved or one to be rejected before the controller is * instantiated. @@ -165,7 +122,7 @@ function $RouteProvider() { * The map object is: * * - `key` – `{string}`: a name of a dependency to be injected into the controller. - * - `factory` - `{string|Function}`: If `string` then it is an alias for a service. + * - `factory` - `{string|function}`: If `string` then it is an alias for a service. * Otherwise if function, then it is {@link auto.$injector#invoke injected} * and the return value is treated as the dependency. If the result is a promise, it is * resolved before its value is injected into the controller. Be aware that @@ -175,7 +132,7 @@ function $RouteProvider() { * - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on * the scope of the route. If omitted, defaults to `$resolve`. * - * - `redirectTo` – `{(string|Function)=}` – value to update + * - `redirectTo` – `{(string|function())=}` – value to update * {@link ng.$location $location} path with and trigger route redirection. * * If `redirectTo` is a function, it will be called with the following parameters: @@ -186,31 +143,7 @@ function $RouteProvider() { * - `{Object}` - current `$location.search()` * * The custom `redirectTo` function is expected to return a string which will be used - * to update `$location.url()`. If the function throws an error, no further processing will - * take place and the {@link ngRoute.$route#$routeChangeError $routeChangeError} event will - * be fired. - * - * Routes that specify `redirectTo` will not have their controllers, template functions - * or resolves called, the `$location` will be changed to the redirect url and route - * processing will stop. The exception to this is if the `redirectTo` is a function that - * returns `undefined`. In this case the route transition occurs as though there was no - * redirection. - * - * - `resolveRedirectTo` – `{Function=}` – a function that will (eventually) return the value - * to update {@link ng.$location $location} URL with and trigger route redirection. In - * contrast to `redirectTo`, dependencies can be injected into `resolveRedirectTo` and the - * return value can be either a string or a promise that will be resolved to a string. - * - * Similar to `redirectTo`, if the return value is `undefined` (or a promise that gets - * resolved to `undefined`), no redirection takes place and the route transition occurs as - * though there was no redirection. - * - * If the function throws an error or the returned promise gets rejected, no further - * processing will take place and the - * {@link ngRoute.$route#$routeChangeError $routeChangeError} event will be fired. - * - * `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same - * route definition, will cause the latter to be ignored. + * to update `$location.path()` and `$location.search()`. * * - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()` * or `$location.hash()` changes. @@ -230,7 +163,7 @@ function $RouteProvider() { */ this.when = function(path, route) { //copy original route object to preserve params inherited from proto chain - var routeCopy = shallowCopy(route); + var routeCopy = angular.copy(route); if (angular.isUndefined(routeCopy.reloadOnSearch)) { routeCopy.reloadOnSearch = true; } @@ -244,7 +177,7 @@ function $RouteProvider() { // create redirection for trailing slashes if (path) { - var redirectPath = (path[path.length - 1] === '/') + var redirectPath = (path[path.length - 1] == '/') ? path.substr(0, path.length - 1) : path + '/'; @@ -329,47 +262,6 @@ function $RouteProvider() { return this; }; - /** - * @ngdoc method - * @name $routeProvider#eagerInstantiationEnabled - * @kind function - * - * @description - * Call this method as a setter to enable/disable eager instantiation of the - * {@link ngRoute.$route $route} service upon application bootstrap. You can also call it as a - * getter (i.e. without any arguments) to get the current value of the - * `eagerInstantiationEnabled` flag. - * - * Instantiating `$route` early is necessary for capturing the initial - * {@link ng.$location#$locationChangeStart $locationChangeStart} event and navigating to the - * appropriate route. Usually, `$route` is instantiated in time by the - * {@link ngRoute.ngView ngView} directive. Yet, in cases where `ngView` is included in an - * asynchronously loaded template (e.g. in another directive's template), the directive factory - * might not be called soon enough for `$route` to be instantiated _before_ the initial - * `$locationChangeSuccess` event is fired. Eager instantiation ensures that `$route` is always - * instantiated in time, regardless of when `ngView` will be loaded. - * - * The default value is true. - * - * **Note**:
- * You may want to disable the default behavior when unit-testing modules that depend on - * `ngRoute`, in order to avoid an unexpected request for the default route's template. - * - * @param {boolean=} enabled - If provided, update the internal `eagerInstantiationEnabled` flag. - * - * @returns {*} The current value of the `eagerInstantiationEnabled` flag if used as a getter or - * itself (for chaining) if used as a setter. - */ - isEagerInstantiationEnabled = true; - this.eagerInstantiationEnabled = function eagerInstantiationEnabled(enabled) { - if (isDefined(enabled)) { - isEagerInstantiationEnabled = enabled; - return this; - } - - return isEagerInstantiationEnabled; - }; - this.$get = ['$rootScope', '$location', @@ -463,12 +355,12 @@ function $RouteProvider() { * }) * * .controller('BookController', function($scope, $routeParams) { - * $scope.name = 'BookController'; + * $scope.name = "BookController"; * $scope.params = $routeParams; * }) * * .controller('ChapterController', function($scope, $routeParams) { - * $scope.name = 'ChapterController'; + * $scope.name = "ChapterController"; * $scope.params = $routeParams; * }) * @@ -501,15 +393,15 @@ function $RouteProvider() { * it('should load and compile correct template', function() { * element(by.linkText('Moby: Ch1')).click(); * var content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller: ChapterController/); - * expect(content).toMatch(/Book Id: Moby/); - * expect(content).toMatch(/Chapter Id: 1/); + * expect(content).toMatch(/controller\: ChapterController/); + * expect(content).toMatch(/Book Id\: Moby/); + * expect(content).toMatch(/Chapter Id\: 1/); * * element(by.partialLinkText('Scarlet')).click(); * * content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller: BookController/); - * expect(content).toMatch(/Book Id: Scarlet/); + * expect(content).toMatch(/controller\: BookController/); + * expect(content).toMatch(/Book Id\: Scarlet/); * }); * * @@ -557,14 +449,12 @@ function $RouteProvider() { * @name $route#$routeChangeError * @eventType broadcast on root scope * @description - * Broadcasted if a redirection function fails or any redirection or resolve promises are - * rejected. + * Broadcasted if any of the resolve promises are rejected. * * @param {Object} angularEvent Synthetic event object * @param {Route} current Current route information. * @param {Route} previous Previous route information. - * @param {Route} rejection The thrown error or the rejection reason of the promise. Usually - * the rejection reason is the error that caused the promise to get rejected. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. */ /** @@ -705,136 +595,65 @@ function $RouteProvider() { } else if (nextRoute || lastRoute) { forceReload = false; $route.current = nextRoute; + if (nextRoute) { + if (nextRoute.redirectTo) { + if (angular.isString(nextRoute.redirectTo)) { + $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) + .replace(); + } else { + $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } - var nextRoutePromise = $q.resolve(nextRoute); + $q.when(nextRoute). + then(function() { + if (nextRoute) { + var locals = angular.extend({}, nextRoute.resolve), + template, templateUrl; - nextRoutePromise. - then(getRedirectionData). - then(handlePossibleRedirection). - then(function(keepProcessingRoute) { - return keepProcessingRoute && nextRoutePromise. - then(resolveLocals). - then(function(locals) { - // after route change - if (nextRoute === $route.current) { - if (nextRoute) { - nextRoute.locals = locals; - angular.copy(nextRoute.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); - } + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value, null, null, key); }); - }).catch(function(error) { - if (nextRoute === $route.current) { + + if (angular.isDefined(template = nextRoute.template)) { + if (angular.isFunction(template)) { + template = template(nextRoute.params); + } + } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(nextRoute.params); + } + if (angular.isDefined(templateUrl)) { + nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl); + template = $templateRequest(templateUrl); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + then(function(locals) { + // after route change + if (nextRoute == $route.current) { + if (nextRoute) { + nextRoute.locals = locals; + angular.copy(nextRoute.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); + } + }, function(error) { + if (nextRoute == $route.current) { $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); } }); } } - function getRedirectionData(route) { - var data = { - route: route, - hasRedirection: false - }; - - if (route) { - if (route.redirectTo) { - if (angular.isString(route.redirectTo)) { - data.path = interpolate(route.redirectTo, route.params); - data.search = route.params; - data.hasRedirection = true; - } else { - var oldPath = $location.path(); - var oldSearch = $location.search(); - var newUrl = route.redirectTo(route.pathParams, oldPath, oldSearch); - - if (angular.isDefined(newUrl)) { - data.url = newUrl; - data.hasRedirection = true; - } - } - } else if (route.resolveRedirectTo) { - return $q. - resolve($injector.invoke(route.resolveRedirectTo)). - then(function(newUrl) { - if (angular.isDefined(newUrl)) { - data.url = newUrl; - data.hasRedirection = true; - } - - return data; - }); - } - } - - return data; - } - - function handlePossibleRedirection(data) { - var keepProcessingRoute = true; - - if (data.route !== $route.current) { - keepProcessingRoute = false; - } else if (data.hasRedirection) { - var oldUrl = $location.url(); - var newUrl = data.url; - - if (newUrl) { - $location. - url(newUrl). - replace(); - } else { - newUrl = $location. - path(data.path). - search(data.search). - replace(). - url(); - } - - if (newUrl !== oldUrl) { - // Exit out and don't process current next value, - // wait for next location change from redirect - keepProcessingRoute = false; - } - } - - return keepProcessingRoute; - } - - function resolveLocals(route) { - if (route) { - var locals = angular.extend({}, route.resolve); - angular.forEach(locals, function(value, key) { - locals[key] = angular.isString(value) ? - $injector.get(value) : - $injector.invoke(value, null, null, key); - }); - var template = getTemplateFor(route); - if (angular.isDefined(template)) { - locals['$template'] = template; - } - return $q.all(locals); - } - } - - function getTemplateFor(route) { - var template, templateUrl; - if (angular.isDefined(template = route.template)) { - if (angular.isFunction(template)) { - template = template(route.params); - } - } else if (angular.isDefined(templateUrl = route.templateUrl)) { - if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(route.params); - } - if (angular.isDefined(templateUrl)) { - route.loadedTemplateUrl = $sce.valueOf(templateUrl); - template = $templateRequest(templateUrl); - } - } - return template; - } /** * @returns {Object} the current active route, by matching it against the URL @@ -875,14 +694,6 @@ function $RouteProvider() { }]; } -instantiateRoute.$inject = ['$injector']; -function instantiateRoute($injector) { - if (isEagerInstantiationEnabled) { - // Instantiate `$route` - $injector.get('$route'); - } -} - ngRouteModule.provider('$routeParams', $RouteParamsProvider); @@ -890,7 +701,6 @@ ngRouteModule.provider('$routeParams', $RouteParamsProvider); * @ngdoc service * @name $routeParams * @requires $route - * @this * * @description * The `$routeParams` service allows you to retrieve the current set of route parameters. @@ -1060,17 +870,17 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory); $locationProvider.html5Mode(true); }]) .controller('MainCtrl', ['$route', '$routeParams', '$location', - function MainCtrl($route, $routeParams, $location) { + function($route, $routeParams, $location) { this.$route = $route; this.$location = $location; this.$routeParams = $routeParams; }]) - .controller('BookCtrl', ['$routeParams', function BookCtrl($routeParams) { - this.name = 'BookCtrl'; + .controller('BookCtrl', ['$routeParams', function($routeParams) { + this.name = "BookCtrl"; this.params = $routeParams; }]) - .controller('ChapterCtrl', ['$routeParams', function ChapterCtrl($routeParams) { - this.name = 'ChapterCtrl'; + .controller('ChapterCtrl', ['$routeParams', function($routeParams) { + this.name = "ChapterCtrl"; this.params = $routeParams; }]); @@ -1080,15 +890,15 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory); it('should load and compile correct template', function() { element(by.linkText('Moby: Ch1')).click(); var content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller: ChapterCtrl/); - expect(content).toMatch(/Book Id: Moby/); - expect(content).toMatch(/Chapter Id: 1/); + expect(content).toMatch(/controller\: ChapterCtrl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); element(by.partialLinkText('Scarlet')).click(); content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller: BookCtrl/); - expect(content).toMatch(/Book Id: Scarlet/); + expect(content).toMatch(/controller\: BookCtrl/); + expect(content).toMatch(/Book Id\: Scarlet/); }); diff --git a/public/vendor/angular-route/angular-route.min.js b/public/vendor/angular-route/angular-route.min.js index c600cb6b26e..5d2e84f9498 100644 --- a/public/vendor/angular-route/angular-route.min.js +++ b/public/vendor/angular-route/angular-route.min.js @@ -1,17 +1,15 @@ /* - AngularJS v1.5.9-build.5020+sha.0784977 + AngularJS v1.5.3 (c) 2010-2016 Google, Inc. http://angularjs.org License: MIT */ -(function(I,e){'use strict';function A(e){h&&e.get("$route")}function B(t,u,k){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,d,m){function v(){p&&(k.cancel(p),p=null);l&&(l.$destroy(),l=null);n&&(p=k.leave(n),p.then(function(){p=null}),n=null)}function D(){var b=t.current&&t.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),d=t.current;n=m(b,function(b){k.enter(b,null,n||f).then(function(){!e.isDefined(w)||w&&!a.$eval(w)||u()});v()});l=d.scope= -b;l.$emit("$viewContentLoaded");l.$eval(h)}else v()}var l,n,p,w=b.autoscroll,h=b.onload||"";a.$on("$routeChangeSuccess",D);D()}}}function x(e,h,k){return{restrict:"ECA",priority:-400,link:function(a,f){var b=k.current,d=b.locals;f.html(d.$template);var m=e(f.contents());if(b.controller){d.$scope=a;var v=h(b.controller,d);b.controllerAs&&(a[b.controllerAs]=v);f.data("$ngControllerController",v);f.children().data("$ngControllerController",v)}a[b.resolveAs||"$resolve"]=d;m(a)}}}var y,E,F,z=e.module("ngRoute", -[]).provider("$route",function(){function t(a,f){return e.extend(Object.create(a),f)}function u(a,e){var b=e.caseInsensitiveMatch,d={originalPath:a,regexp:a},k=d.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g,function(a,b,e,d){a="?"===d||"*?"===d?"?":null;d="*"===d||"*?"===d?"*":null;k.push({name:e,optional:!!a});b=b||"";return""+(a?"":b)+"(?:"+(a?b:"")+(d&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=new RegExp("^"+a+"$",b?"i":"");return d} -y=e.isArray;E=e.isObject;F=e.isDefined;var k={};this.when=function(a,f){var b;b=void 0;if(y(f)){b=b||[];for(var d=0,m=f.length;d + + + */ function copy(source, destination) { var stackSource = []; @@ -889,10 +862,10 @@ function copy(source, destination) { if (destination) { if (isTypedArray(destination) || isArrayBuffer(destination)) { - throw ngMinErr('cpta', 'Can\'t copy! TypedArray destination cannot be mutated.'); + throw ngMinErr('cpta', "Can't copy! TypedArray destination cannot be mutated."); } if (source === destination) { - throw ngMinErr('cpi', 'Can\'t copy! Source and destination are identical.'); + throw ngMinErr('cpi', "Can't copy! Source and destination are identical."); } // Empty the destination object @@ -958,7 +931,7 @@ function copy(source, destination) { if (isWindow(source) || isScope(source)) { throw ngMinErr('cpws', - 'Can\'t copy! Making copies of Window or Scope instances is not supported.'); + "Can't copy! Making copies of Window or Scope instances is not supported."); } var needsRecurse = false; @@ -988,16 +961,13 @@ function copy(source, destination) { case '[object Uint8ClampedArray]': case '[object Uint16Array]': case '[object Uint32Array]': - return new source.constructor(copyElement(source.buffer), source.byteOffset, source.length); + return new source.constructor(copyElement(source.buffer)); case '[object ArrayBuffer]': - // Support: IE10 + //Support: IE10 if (!source.slice) { - // If we're in this case we know the environment supports ArrayBuffer - /* eslint-disable no-undef */ var copied = new ArrayBuffer(source.byteLength); new Uint8Array(copied).set(new Uint8Array(source)); - /* eslint-enable */ return copied; } return source.slice(0); @@ -1023,6 +993,31 @@ function copy(source, destination) { } } +/** + * Creates a shallow copy of an object, an array or a primitive. + * + * Assumes that there are no proto properties for objects. + */ +function shallowCopy(src, dst) { + if (isArray(src)) { + dst = dst || []; + + for (var i = 0, ii = src.length; i < ii; i++) { + dst[i] = src[i]; + } + } else if (isObject(src)) { + dst = dst || {}; + + for (var key in src) { + if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + } + + return dst || src; +} + /** * @ngdoc function @@ -1052,51 +1047,16 @@ function copy(source, destination) { * @param {*} o1 Object or value to compare. * @param {*} o2 Object or value to compare. * @returns {boolean} True if arguments are equal. - * - * @example - - -
-
-

User 1

- Name: - Age: - -

User 2

- Name: - Age: - -
-
- -
- User 1:
{{user1 | json}}
- User 2:
{{user2 | json}}
- Equal:
{{result}}
-
-
-
- - angular.module('equalsExample', []).controller('ExampleController', ['$scope', function($scope) { - $scope.user1 = {}; - $scope.user2 = {}; - $scope.compare = function() { - $scope.result = angular.equals($scope.user1, $scope.user2); - }; - }]); - -
*/ function equals(o1, o2) { if (o1 === o2) return true; if (o1 === null || o2 === null) return false; - // eslint-disable-next-line no-self-compare if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN var t1 = typeof o1, t2 = typeof o2, length, key, keySet; - if (t1 === t2 && t1 === 'object') { + if (t1 == t2 && t1 == 'object') { if (isArray(o1)) { if (!isArray(o2)) return false; - if ((length = o1.length) === o2.length) { + if ((length = o1.length) == o2.length) { for (key = 0; key < length; key++) { if (!equals(o1[key], o2[key])) return false; } @@ -1107,7 +1067,7 @@ function equals(o1, o2) { return equals(o1.getTime(), o2.getTime()); } else if (isRegExp(o1)) { if (!isRegExp(o2)) return false; - return o1.toString() === o2.toString(); + return o1.toString() == o2.toString(); } else { if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2) || isDate(o2) || isRegExp(o2)) return false; @@ -1133,8 +1093,8 @@ var csp = function() { if (!isDefined(csp.rules)) { - var ngCspElement = (window.document.querySelector('[ng-csp]') || - window.document.querySelector('[data-ng-csp]')); + var ngCspElement = (document.querySelector('[ng-csp]') || + document.querySelector('[data-ng-csp]')); if (ngCspElement) { var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || @@ -1155,8 +1115,9 @@ var csp = function() { function noUnsafeEval() { try { - // eslint-disable-next-line no-new, no-new-func + /* jshint -W031, -W054 */ new Function(''); + /* jshint +W031, +W054 */ return false; } catch (e) { return true; @@ -1208,8 +1169,7 @@ var jq = function() { var i, ii = ngAttrPrefixes.length, prefix, name; for (i = 0; i < ii; ++i) { prefix = ngAttrPrefixes[i]; - el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]'); - if (el) { + if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { name = el.getAttribute(prefix + 'jq'); break; } @@ -1227,6 +1187,7 @@ function sliceArgs(args, startIndex) { } +/* jshint -W101 */ /** * @ngdoc function * @name angular.bind @@ -1244,6 +1205,7 @@ function sliceArgs(args, startIndex) { * @param {...*} args Optional arguments to be prebound to the `fn` function call. * @returns {function()} Function that wraps the `fn` with all the specified bindings. */ +/* jshint +W101 */ function bind(self, fn) { var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; if (isFunction(fn) && !(fn instanceof RegExp)) { @@ -1259,7 +1221,7 @@ function bind(self, fn) { : fn.call(self); }; } else { - // In IE, native methods are not functions so they cannot be bound (note: they don't need to be). + // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) return fn; } } @@ -1272,7 +1234,7 @@ function toJsonReplacer(key, value) { val = undefined; } else if (isWindow(value)) { val = '$WINDOW'; - } else if (value && window.document === value) { + } else if (value && document === value) { val = '$DOCUMENT'; } else if (isScope(value)) { val = '$SCOPE'; @@ -1296,27 +1258,6 @@ function toJsonReplacer(key, value) { * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. * If set to an integer, the JSON output will contain that many spaces per indentation. * @returns {string|undefined} JSON-ified string representing `obj`. - * @knownIssue - * - * The Safari browser throws a `RangeError` instead of returning `null` when it tries to stringify a `Date` - * object with an invalid date value. The only reliable way to prevent this is to monkeypatch the - * `Date.prototype.toJSON` method as follows: - * - * ``` - * var _DatetoJSON = Date.prototype.toJSON; - * Date.prototype.toJSON = function() { - * try { - * return _DatetoJSON.call(this); - * } catch(e) { - * if (e instanceof RangeError) { - * return null; - * } - * throw e; - * } - * }; - * ``` - * - * See https://github.com/angular/angular.js/pull/14221 for more information. */ function toJson(obj, pretty) { if (isUndefined(obj)) return undefined; @@ -1351,7 +1292,7 @@ function timezoneToOffset(timezone, fallback) { // IE/Edge do not "understand" colon (`:`) in timezone timezone = timezone.replace(ALL_COLONS, ''); var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; - return isNumberNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; + return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; } @@ -1379,7 +1320,7 @@ function startingTag(element) { // turns out IE does not let you set .html() on elements which // are not allowed to have children. So we just ignore it. element.empty(); - } catch (e) { /* empty */ } + } catch (e) {} var elemHtml = jqLite('
').append(element).html(); try { return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : @@ -1407,7 +1348,7 @@ function tryDecodeURIComponent(value) { try { return decodeURIComponent(value); } catch (e) { - // Ignore any invalid uri component. + // Ignore any invalid uri component } } @@ -1418,7 +1359,7 @@ function tryDecodeURIComponent(value) { */ function parseKeyValue(/**string*/keyValue) { var obj = {}; - forEach((keyValue || '').split('&'), function(keyValue) { + forEach((keyValue || "").split('&'), function(keyValue) { var splitPoint, key, val; if (keyValue) { key = keyValue = keyValue.replace(/\+/g,'%20'); @@ -1483,7 +1424,7 @@ function encodeUriSegment(val) { * This method is intended for encoding *key* or *value* parts of query component. We need a custom * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) + * query = *( pchar / "/" / "?" ) * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * pct-encoded = "%" HEXDIG HEXDIG @@ -1556,7 +1497,7 @@ function getNgAttribute(element, ngAttr) { * * `ngApp` is the easiest, and most common way to bootstrap an application. * - +
I can add: {{a}} + {{b}} = {{ a+b }} @@ -1572,7 +1513,7 @@ function getNgAttribute(element, ngAttr) { * * Using `ngStrictDi`, you would see something like this: * - +
@@ -1621,7 +1562,7 @@ function getNgAttribute(element, ngAttr) { }]) .controller('GoodController2', GoodController2); function GoodController2($scope) { - $scope.name = 'World'; + $scope.name = "World"; } GoodController2.$inject = ['$scope']; @@ -1652,7 +1593,7 @@ function angularInit(element, bootstrap) { module, config = {}; - // The element `element` has priority over any other element. + // The element `element` has priority over any other element forEach(ngAttrPrefixes, function(prefix) { var name = prefix + 'app'; @@ -1671,7 +1612,7 @@ function angularInit(element, bootstrap) { } }); if (appElement) { - config.strictDi = getNgAttribute(appElement, 'strict-di') !== null; + config.strictDi = getNgAttribute(appElement, "strict-di") !== null; bootstrap(appElement, module ? [module] : [], config); } } @@ -1745,11 +1686,11 @@ function bootstrap(element, modules, config) { element = jqLite(element); if (element.injector()) { - var tag = (element[0] === window.document) ? 'document' : startingTag(element); - // Encode angle brackets to prevent input from being sanitized to empty string #8683. + var tag = (element[0] === document) ? 'document' : startingTag(element); + //Encode angle brackets to prevent input from being sanitized to empty string #8683 throw ngMinErr( 'btstrpd', - 'App already bootstrapped with this element \'{0}\'', + "App Already Bootstrapped with this Element '{0}'", tag.replace(//,'>')); } @@ -1878,7 +1819,7 @@ function bindJQuery() { jQuery.cleanData = function(elems) { var events; for (var i = 0, elem; (elem = elems[i]) != null; i++) { - events = jQuery._data(elem, 'events'); + events = jQuery._data(elem, "events"); if (events && events.$destroy) { jQuery(elem).triggerHandler('$destroy'); } @@ -1900,7 +1841,7 @@ function bindJQuery() { */ function assertArg(arg, name, reason) { if (!arg) { - throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); + throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); } return arg; } @@ -1922,7 +1863,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) { */ function assertNotHasOwnProperty(name, context) { if (name === 'hasOwnProperty') { - throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); + throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); } } @@ -1992,27 +1933,6 @@ function createMap() { return Object.create(null); } -function stringify(value) { - if (value == null) { // null || undefined - return ''; - } - switch (typeof value) { - case 'string': - break; - case 'number': - value = '' + value; - break; - default: - if (hasCustomToString(value) && !isArray(value) && !isDate(value)) { - value = value.toString(); - } else { - value = toJson(value); - } - } - - return value; -} - var NODE_TYPE_ELEMENT = 1; var NODE_TYPE_ATTRIBUTE = 2; var NODE_TYPE_TEXT = 3; @@ -2111,9 +2031,9 @@ function setupModuleLoader(window) { } return ensure(modules, name, function() { if (!requires) { - throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' + - 'the module name or forgot to load it. If registering a module ensure that you ' + - 'specify the dependencies as the second argument.', name); + throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + + "the module name or forgot to load it. If registering a module ensure that you " + + "specify the dependencies as the second argument.", name); } /** @type {!Array.>} */ @@ -2217,13 +2137,13 @@ function setupModuleLoader(window) { * @ngdoc method * @name angular.Module#decorator * @module ng - * @param {string} name The name of the service to decorate. - * @param {Function} decorFn This function will be invoked when the service needs to be - * instantiated and should return the decorated service instance. + * @param {string} The name of the service to decorate. + * @param {Function} This function will be invoked when the service needs to be + * instantiated and should return the decorated service instance. * @description * See {@link auto.$provide#decorator $provide.decorator()}. */ - decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks), + decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), /** * @ngdoc method @@ -2369,11 +2289,10 @@ function setupModuleLoader(window) { * @param {string} method * @returns {angular.Module} */ - function invokeLaterAndSetModuleName(provider, method, queue) { - if (!queue) queue = invokeQueue; + function invokeLaterAndSetModuleName(provider, method) { return function(recipeName, factoryFunction) { if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; - queue.push([provider, method, arguments]); + invokeQueue.push([provider, method, arguments]); return moduleInstance; }; } @@ -2383,34 +2302,7 @@ function setupModuleLoader(window) { } -/* global shallowCopy: true */ - -/** - * Creates a shallow copy of an object, an array or a primitive. - * - * Assumes that there are no proto properties for objects. - */ -function shallowCopy(src, dst) { - if (isArray(src)) { - dst = dst || []; - - for (var i = 0, ii = src.length; i < ii; i++) { - dst[i] = src[i]; - } - } else if (isObject(src)) { - dst = dst || {}; - - for (var key in src) { - if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } - - return dst || src; -} - -/* global toDebugString: true */ +/* global: toDebugString: true */ function serializeObject(obj) { var seen = []; @@ -2449,6 +2341,7 @@ function toDebugString(obj) { formDirective, scriptDirective, selectDirective, + styleDirective, optionDirective, ngBindDirective, ngBindHtmlDirective, @@ -2502,7 +2395,6 @@ function toDebugString(obj) { $ControllerProvider, $DateProvider, $DocumentProvider, - $$IsDocumentHiddenProvider, $ExceptionHandlerProvider, $FilterProvider, $$ForceReflowProvider, @@ -2514,10 +2406,8 @@ function toDebugString(obj) { $HttpParamSerializerJQLikeProvider, $HttpBackendProvider, $xhrFactoryProvider, - $jsonpCallbacksProvider, $LocationProvider, $LogProvider, - $ModelOptionsProvider, $ParseProvider, $RootScopeProvider, $QProvider, @@ -2553,13 +2443,11 @@ function toDebugString(obj) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - // These placeholder strings will be replaced by grunt's `build` task. - // They need to be double- or single-quoted. - full: '1.5.9-build.5020+sha.0784977', - major: 1, + full: '1.5.3', // all of these placeholder strings will be replaced by grunt's + major: 1, // package task minor: 5, - dot: 9, - codeName: 'snapshot' + dot: 3, + codeName: 'diplohaplontic-meiosis' }; @@ -2590,14 +2478,11 @@ function publishExternalAPI(angular) { 'isDate': isDate, 'lowercase': lowercase, 'uppercase': uppercase, - 'callbacks': {$$counter: 0}, + 'callbacks': {counter: 0}, 'getTestability': getTestability, - 'reloadWithDebugInfo': reloadWithDebugInfo, '$$minErr': minErr, '$$csp': csp, - '$$encodeUriSegment': encodeUriSegment, - '$$encodeUriQuery': encodeUriQuery, - '$$stringify': stringify + 'reloadWithDebugInfo': reloadWithDebugInfo }); angularModule = setupModuleLoader(window); @@ -2616,6 +2501,7 @@ function publishExternalAPI(angular) { form: formDirective, script: scriptDirective, select: selectDirective, + style: styleDirective, option: optionDirective, ngBind: ngBindDirective, ngBindHtml: ngBindHtmlDirective, @@ -2671,7 +2557,6 @@ function publishExternalAPI(angular) { $cacheFactory: $CacheFactoryProvider, $controller: $ControllerProvider, $document: $DocumentProvider, - $$isDocumentHidden: $$IsDocumentHiddenProvider, $exceptionHandler: $ExceptionHandlerProvider, $filter: $FilterProvider, $$forceReflow: $$ForceReflowProvider, @@ -2682,10 +2567,8 @@ function publishExternalAPI(angular) { $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, $httpBackend: $HttpBackendProvider, $xhrFactory: $xhrFactoryProvider, - $jsonpCallbacks: $jsonpCallbacksProvider, $location: $LocationProvider, $log: $LogProvider, - $modelOptions: $ModelOptionsProvider, $parse: $ParseProvider, $rootScope: $RootScopeProvider, $q: $QProvider, @@ -2718,10 +2601,11 @@ function publishExternalAPI(angular) { * Or gives undesired access to variables likes document or window? * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -/* global - JQLitePrototype: true, +/* global JQLitePrototype: true, + addEventListenerFn: true, + removeEventListenerFn: true, BOOLEAN_ATTR: true, - ALIASED_ATTR: true + ALIASED_ATTR: true, */ ////////////////////////////////// @@ -2759,7 +2643,7 @@ function publishExternalAPI(angular) { * ## Angular's jqLite * jqLite provides only the following jQuery methods: * - * - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument + * - [`addClass()`](http://api.jquery.com/addClass/) * - [`after()`](http://api.jquery.com/after/) * - [`append()`](http://api.jquery.com/append/) * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters @@ -2786,12 +2670,12 @@ function publishExternalAPI(angular) { * - [`ready()`](http://api.jquery.com/ready/) * - [`remove()`](http://api.jquery.com/remove/) * - [`removeAttr()`](http://api.jquery.com/removeAttr/) - * - [`removeClass()`](http://api.jquery.com/removeClass/) - Does not support a function as first argument + * - [`removeClass()`](http://api.jquery.com/removeClass/) * - [`removeData()`](http://api.jquery.com/removeData/) * - [`replaceWith()`](http://api.jquery.com/replaceWith/) * - [`text()`](http://api.jquery.com/text/) - * - [`toggleClass()`](http://api.jquery.com/toggleClass/) - Does not support a function as first argument - * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers + * - [`toggleClass()`](http://api.jquery.com/toggleClass/) + * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter * - [`val()`](http://api.jquery.com/val/) * - [`wrap()`](http://api.jquery.com/wrap/) @@ -2820,9 +2704,6 @@ function publishExternalAPI(angular) { * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top * parent element is reached. * - * @knownIssue You cannot spy on `angular.element` if you are using Jasmine version 1.x. See - * https://github.com/angular/angular.js/issues/14251 for more information. - * * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. * @returns {Object} jQuery object. */ @@ -2830,7 +2711,13 @@ function publishExternalAPI(angular) { JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, - jqId = 1; + jqId = 1, + addEventListenerFn = function(element, type, fn) { + element.addEventListener(type, fn, false); + }, + removeEventListenerFn = function(element, type, fn) { + element.removeEventListener(type, fn, false); + }; /* * !!! This is an undocumented "private" function !!! @@ -2843,9 +2730,9 @@ JQLite._data = function(node) { function jqNextId() { return ++jqId; } -var SPECIAL_CHARS_REGEXP = /([:\-_]+(.))/g; +var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; var MOZ_HACK_REGEXP = /^moz([A-Z])/; -var MOUSE_EVENT_MAP = { mouseleave: 'mouseout', mouseenter: 'mouseover' }; +var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; var jqLiteMinErr = minErr('jqLite'); /** @@ -2873,7 +2760,7 @@ var wrapMap = { 'col': [2, '', '
'], 'tr': [2, '', '
'], 'td': [3, '', '
'], - '_default': [0, '', ''] + '_default': [0, "", ""] }; wrapMap.optgroup = wrapMap.option; @@ -2915,10 +2802,10 @@ function jqLiteBuildFragment(html, context) { nodes.push(context.createTextNode(html)); } else { // Convert html into DOM nodes - tmp = fragment.appendChild(context.createElement('div')); - tag = (TAG_NAME_REGEXP.exec(html) || ['', ''])[1].toLowerCase(); + tmp = tmp || fragment.appendChild(context.createElement("div")); + tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); wrap = wrapMap[tag] || wrapMap._default; - tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, '<$1>') + wrap[2]; + tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1>") + wrap[2]; // Descend through wrappers to the right content i = wrap[0]; @@ -2929,12 +2816,12 @@ function jqLiteBuildFragment(html, context) { nodes = concat(nodes, tmp.childNodes); tmp = fragment.firstChild; - tmp.textContent = ''; + tmp.textContent = ""; } // Remove wrapper from fragment - fragment.textContent = ''; - fragment.innerHTML = ''; // Clear inner HTML + fragment.textContent = ""; + fragment.innerHTML = ""; // Clear inner HTML forEach(nodes, function(node) { fragment.appendChild(node); }); @@ -2943,7 +2830,7 @@ function jqLiteBuildFragment(html, context) { } function jqLiteParseHTML(html, context) { - context = context || window.document; + context = context || document; var parsed; if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { @@ -2969,9 +2856,10 @@ function jqLiteWrapNode(node, wrapper) { // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. -var jqLiteContains = window.Node.prototype.contains || /** @this */ function(arg) { - // eslint-disable-next-line no-bitwise +var jqLiteContains = Node.prototype.contains || function(arg) { + // jshint bitwise: false return !!(this.compareDocumentPosition(arg) & 16); + // jshint bitwise: true }; ///////////////////////////////////////////// @@ -2987,7 +2875,7 @@ function JQLite(element) { argIsString = true; } if (!(this instanceof JQLite)) { - if (argIsString && element.charAt(0) !== '<') { + if (argIsString && element.charAt(0) != '<') { throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); } return new JQLite(element); @@ -3027,7 +2915,7 @@ function jqLiteOff(element, type, fn, unsupported) { if (!type) { for (type in events) { if (type !== '$destroy') { - element.removeEventListener(type, handle); + removeEventListenerFn(element, type, handle); } delete events[type]; } @@ -3039,7 +2927,7 @@ function jqLiteOff(element, type, fn, unsupported) { arrayRemove(listenerFns || [], fn); } if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { - element.removeEventListener(type, handle); + removeEventListenerFn(element, type, handle); delete events[type]; } }; @@ -3116,17 +3004,17 @@ function jqLiteData(element, key, value) { function jqLiteHasClass(element, selector) { if (!element.getAttribute) return false; - return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' '). - indexOf(' ' + selector + ' ') > -1); + return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). + indexOf(" " + selector + " ") > -1); } function jqLiteRemoveClass(element, cssClasses) { if (cssClasses && element.setAttribute) { forEach(cssClasses.split(' '), function(cssClass) { element.setAttribute('class', trim( - (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, ' ') - .replace(' ' + trim(cssClass) + ' ', ' ')) + (" " + (element.getAttribute('class') || '') + " ") + .replace(/[\n\t]/g, " ") + .replace(" " + trim(cssClass) + " ", " ")) ); }); } @@ -3135,7 +3023,7 @@ function jqLiteRemoveClass(element, cssClasses) { function jqLiteAddClass(element, cssClasses) { if (cssClasses && element.setAttribute) { var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, ' '); + .replace(/[\n\t]/g, " "); forEach(cssClasses.split(' '), function(cssClass) { cssClass = trim(cssClass); @@ -3182,7 +3070,7 @@ function jqLiteController(element, name) { function jqLiteInheritedData(element, name, value) { // if element is the document object work with the html element instead // this makes $(document).scope() possible - if (element.nodeType === NODE_TYPE_DOCUMENT) { + if (element.nodeType == NODE_TYPE_DOCUMENT) { element = element.documentElement; } var names = isArray(name) ? name : [name]; @@ -3240,13 +3128,14 @@ var JQLitePrototype = JQLite.prototype = { } // check if document is already loaded - if (window.document.readyState === 'complete') { - window.setTimeout(trigger); + if (document.readyState === 'complete') { + setTimeout(trigger); } else { this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 // we can not use jqLite since we are not done loading and jQuery could be loaded later. - // eslint-disable-next-line new-cap + // jshint -W064 JQLite(window).on('load', trigger); // fallback to window.onload for others + // jshint +W064 } }, toString: function() { @@ -3283,8 +3172,7 @@ var ALIASED_ATTR = { 'ngMaxlength': 'maxlength', 'ngMin': 'min', 'ngMax': 'max', - 'ngPattern': 'pattern', - 'ngStep': 'step' + 'ngPattern': 'pattern' }; function getBooleanAttrName(element, name) { @@ -3352,7 +3240,7 @@ forEach({ var lowercasedName = lowercase(name); if (BOOLEAN_ATTR[lowercasedName]) { if (isDefined(value)) { - if (value) { + if (!!value) { element[name] = true; element.setAttribute(name, lowercasedName); } else { @@ -3406,7 +3294,7 @@ forEach({ result.push(option.value || option.text); } }); - return result; + return result.length === 0 ? null : result; } return element.value; } @@ -3434,7 +3322,7 @@ forEach({ // in a way that survives minification. // jqLiteEmpty takes no arguments but is a setter. if (fn !== jqLiteEmpty && - (isUndefined((fn.length === 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { + (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { if (isObject(arg1)) { // we are a write, but the object properties are the key/values @@ -3576,7 +3464,7 @@ forEach({ eventFns = events[type] = []; eventFns.specialHandlerWrapper = specialHandlerWrapper; if (type !== '$destroy' && !noEventListener) { - element.addEventListener(type, handle); + addEventListenerFn(element, type, handle); } } @@ -3776,7 +3664,6 @@ forEach({ // Provider for private $$jqLite service -/** @this */ function $$jqLiteProvider() { this.$get = function $$jqLite() { return extend(JQLite, { @@ -3819,7 +3706,7 @@ function hashKey(obj, nextUidFn) { } var objType = typeof obj; - if (objType === 'function' || (objType === 'object' && obj !== null)) { + if (objType == 'function' || (objType == 'object' && obj !== null)) { key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); } else { key = objType + ':' + obj; @@ -3869,7 +3756,7 @@ HashMap.prototype = { } }; -var $$HashMapProvider = [/** @this */function() { +var $$HashMapProvider = [function() { this.$get = [function() { return HashMap; }]; @@ -3932,7 +3819,6 @@ var $$HashMapProvider = [/** @this */function() { /** * @ngdoc module * @name auto - * @installation * @description * * Implicit module which gets automatically added to each {@link auto.$injector $injector}. @@ -3945,16 +3831,8 @@ var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; var $injectorMinErr = minErr('$injector'); -function stringifyFn(fn) { - // Support: Chrome 50-51 only - // Creating a new string by adding `' '` at the end, to hack around some bug in Chrome v50/51 - // (See https://github.com/angular/angular.js/issues/14487.) - // TODO (gkalpak): Remove workaround when Chrome v52 is released - return Function.prototype.toString.call(fn) + ' '; -} - function extractArgs(fn) { - var fnText = stringifyFn(fn).replace(STRIP_COMMENTS, ''), + var fnText = fn.toString().replace(STRIP_COMMENTS, ''), args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS); return args; } @@ -4197,6 +4075,7 @@ function annotate(fn, strictDi, name) { + /** * @ngdoc service * @name $provide @@ -4221,20 +4100,18 @@ function annotate(fn, strictDi, name) { * these cases the {@link auto.$provide $provide} service has additional helper methods to register * services without specifying a provider. * - * * {@link auto.$provide#provider provider(name, provider)} - registers a **service provider** with the + * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the * {@link auto.$injector $injector} - * * {@link auto.$provide#constant constant(name, obj)} - registers a value/object that can be accessed by + * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by * providers and services. - * * {@link auto.$provide#value value(name, obj)} - registers a value/object that can only be accessed by + * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by * services, not providers. - * * {@link auto.$provide#factory factory(name, fn)} - registers a service **factory function** + * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, * that will be wrapped in a **service provider** object, whose `$get` property will contain the * given factory function. - * * {@link auto.$provide#service service(name, Fn)} - registers a **constructor function** + * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` * that will be wrapped in a **service provider** object, whose `$get` property will instantiate * a new object using the given constructor function. - * * {@link auto.$provide#decorator decorator(name, decorFn)} - registers a **decorator function** that - * will be able to modify or replace the implementation of another service. * * See the individual methods for more information and examples. */ @@ -4491,20 +4368,18 @@ function annotate(fn, strictDi, name) { * @name $provide#decorator * @description * - * Register a **decorator function** with the {@link auto.$injector $injector}. A decorator function + * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator * intercepts the creation of a service, allowing it to override or modify the behavior of the - * service. The return value of the decorator function may be the original service, or a new service - * that replaces (or wraps and delegates to) the original service. - * - * You can find out more about using decorators in the {@link guide/decorators} guide. + * service. The object returned by the decorator may be the original service, or a new service + * object which replaces or wraps and delegates to the original service. * * @param {string} name The name of the service to decorate. * @param {Function|Array.} decorator This function will be invoked when the service needs to be - * provided and should return the decorated service instance. The function is called using + * instantiated and should return the decorated service instance. The function is called using * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. * Local injection arguments: * - * * `$delegate` - The original service instance, which can be replaced, monkey patched, configured, + * * `$delegate` - The original service instance, which can be monkey patched, configured, * decorated or delegated to. * * @example @@ -4540,7 +4415,7 @@ function createInjector(modulesToLoad, strictDi) { if (angular.isString(caller)) { path.push(caller); } - throw $injectorMinErr('unpr', 'Unknown provider: {0}', path.join(' <- ')); + throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); })), instanceCache = {}, protoInstanceInjector = @@ -4579,16 +4454,16 @@ function createInjector(modulesToLoad, strictDi) { provider_ = providerInjector.instantiate(provider_); } if (!provider_.$get) { - throw $injectorMinErr('pget', 'Provider \'{0}\' must define $get factory method.', name); + throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); } - return (providerCache[name + providerSuffix] = provider_); + return providerCache[name + providerSuffix] = provider_; } function enforceReturnValue(name, factory) { - return /** @this */ function enforcedReturnValue() { + return function enforcedReturnValue() { var result = instanceInjector.invoke(factory, this); if (isUndefined(result)) { - throw $injectorMinErr('undef', 'Provider \'{0}\' must return a value from $get factory method.', name); + throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); } return result; }; @@ -4661,15 +4536,15 @@ function createInjector(modulesToLoad, strictDi) { if (isArray(module)) { module = module[module.length - 1]; } - if (e.message && e.stack && e.stack.indexOf(e.message) === -1) { + if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { // Safari & FF's stack traces don't contain error.message content // unlike those of Chrome and IE // So if stack doesn't contain message, we create a new string that contains both. // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. - // eslint-disable-next-line no-ex-assign + /* jshint -W022 */ e = e.message + '\n' + e.stack; } - throw $injectorMinErr('modulerr', 'Failed to instantiate module {0} due to:\n{1}', + throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e); } }); @@ -4693,8 +4568,7 @@ function createInjector(modulesToLoad, strictDi) { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; - cache[serviceName] = factory(serviceName, caller); - return cache[serviceName]; + return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; @@ -4725,16 +4599,13 @@ function createInjector(modulesToLoad, strictDi) { function isClass(func) { // IE 9-11 do not support classes and IE9 leaks with the code below. - if (msie <= 11 || typeof func !== 'function') { + if (msie <= 11) { return false; } - var result = func.$$ngIsClass; - if (!isBoolean(result)) { - // Support: Edge 12-13 only - // See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/6156135/ - result = func.$$ngIsClass = /^(?:class\b|constructor\()/.test(stringifyFn(func)); - } - return result; + // Workaround for MS Edge. + // Check https://connect.microsoft.com/IE/Feedback/Details/2211653 + return typeof func === 'function' + && /^(?:class\s|constructor\()/.test(Function.prototype.toString.call(func)); } function invoke(fn, self, locals, serviceName) { @@ -4787,7 +4658,6 @@ createInjector.$$annotate = annotate; /** * @ngdoc provider * @name $anchorScrollProvider - * @this * * @description * Use `$anchorScrollProvider` to disable automatic scrolling whenever @@ -4826,7 +4696,7 @@ function $AnchorScrollProvider() { * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified * in the - * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#an-indicated-part-of-the-document). + * [HTML5 spec](http://www.w3.org/html/wg/drafts/html/master/browsers.html#the-indicated-part-of-the-document). * * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to * match any anchor whenever it changes. This can be disabled by calling @@ -4859,7 +4729,7 @@ function $AnchorScrollProvider() { *
* * @example - +
Go to bottom @@ -4869,7 +4739,7 @@ function $AnchorScrollProvider() { angular.module('anchorScrollExample', []) .controller('ScrollController', ['$scope', '$location', '$anchorScroll', - function($scope, $location, $anchorScroll) { + function ($scope, $location, $anchorScroll) { $scope.gotoBottom = function() { // set the location.hash to the id of // the element you wish to scroll to. @@ -4898,7 +4768,7 @@ function $AnchorScrollProvider() { * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. * * @example - +
@@ -4915,7 +4785,7 @@ function $AnchorScrollProvider() { $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels }]) .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', - function($anchorScroll, $location, $scope) { + function ($anchorScroll, $location, $scope) { $scope.gotoAnchor = function(x) { var newHash = 'anchor' + x; if ($location.hash() !== newHash) { @@ -5034,7 +4904,7 @@ function $AnchorScrollProvider() { // first anchor with given name :-D else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); - // no element and hash === 'top', scroll to the top of the page + // no element and hash == 'top', scroll to the top of the page else if (hash === 'top') scrollTo(null); } @@ -5109,13 +4979,13 @@ function prepareAnimateOptions(options) { : {}; } -var $$CoreAnimateJsProvider = /** @this */ function() { +var $$CoreAnimateJsProvider = function() { this.$get = noop; }; // this is prefixed with Core since it conflicts with // the animateQueueProvider defined in ngAnimate/animateQueue.js -var $$CoreAnimateQueueProvider = /** @this */ function() { +var $$CoreAnimateQueueProvider = function() { var postDigestQueue = new HashMap(); var postDigestElements = []; @@ -5128,23 +4998,17 @@ var $$CoreAnimateQueueProvider = /** @this */ function() { pin: noop, push: function(element, event, options, domOperation) { - if (domOperation) { - domOperation(); - } + domOperation && domOperation(); options = options || {}; - if (options.from) { - element.css(options.from); - } - if (options.to) { - element.css(options.to); - } + options.from && element.css(options.from); + options.to && element.css(options.to); if (options.addClass || options.removeClass) { addRemoveClassesPostDigest(element, options.addClass, options.removeClass); } - var runner = new $$AnimateRunner(); + var runner = new $$AnimateRunner(); // jshint ignore:line // since there are no animations to run the runner needs to be // notified that the animation call is complete. @@ -5188,12 +5052,8 @@ var $$CoreAnimateQueueProvider = /** @this */ function() { }); forEach(element, function(elm) { - if (toAdd) { - jqLiteAddClass(elm, toAdd); - } - if (toRemove) { - jqLiteRemoveClass(elm, toRemove); - } + toAdd && jqLiteAddClass(elm, toAdd); + toRemove && jqLiteRemoveClass(elm, toRemove); }); postDigestQueue.remove(element); } @@ -5233,7 +5093,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() { * * To see the functional implementation check out `src/ngAnimate/animate.js`. */ -var $AnimateProvider = ['$provide', /** @this */ function($provide) { +var $AnimateProvider = ['$provide', function($provide) { var provider = this; this.$$registeredAnimations = Object.create(null); @@ -5279,7 +5139,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { */ this.register = function(name, factory) { if (name && name.charAt(0) !== '.') { - throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name); + throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); } var key = name + '-animation'; @@ -5305,7 +5165,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { if (arguments.length === 1) { this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; if (this.$$classNameFilter) { - var reservedRegex = new RegExp('(\\s+|\\/)' + NG_ANIMATE_CLASSNAME + '(\\s+|\\/)'); + var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); if (reservedRegex.test(this.$$classNameFilter.toString())) { throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); @@ -5326,11 +5186,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { afterElement = null; } } - if (afterElement) { - afterElement.after(element); - } else { - parentElement.prepend(element); - } + afterElement ? afterElement.after(element) : parentElement.prepend(element); } /** @@ -5396,9 +5252,6 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * // remove all the animation event listeners listening for `enter` * $animate.off('enter'); * - * // remove listeners for all animation events from the container element - * $animate.off(container); - * * // remove all the animation event listeners listening for `enter` on the given element and its children * $animate.off('enter', container); * @@ -5407,9 +5260,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * $animate.off('enter', container, callback); * ``` * - * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move, - * addClass, removeClass, etc...), or the container element. If it is the element, all other - * arguments are ignored. + * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) * @param {DOMElement=} container the container element the event listener was placed on * @param {Function=} callback the callback function that was registered as the listener */ @@ -5473,9 +5324,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * @param {Promise} animationPromise The animation promise that is returned when an animation is started. */ cancel: function(runner) { - if (runner.end) { - runner.end(); - } + runner.end && runner.end(); }, /** @@ -5492,13 +5341,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * @param {DOMElement} parent the parent element which will append the element as * a child (so long as the after element is not present) * @param {DOMElement=} after the sibling element after which the element will be appended - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5524,13 +5367,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * @param {DOMElement} parent the parent element which will append the element as * a child (so long as the after element is not present) * @param {DOMElement=} after the sibling element after which the element will be appended - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5551,13 +5388,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * digest once the animation has completed. * * @param {DOMElement} element the element which will be removed from the DOM - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5581,13 +5412,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * * @param {DOMElement} element the element which the CSS classes will be applied to * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5611,13 +5436,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * * @param {DOMElement} element the element which the CSS classes will be applied to * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5642,13 +5461,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * @param {DOMElement} element the element which the CSS classes will be applied to * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5689,13 +5502,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. * (Note that if no animation is detected then this value will not be applied to the element.) - * @param {object=} options an optional collection of options/styles that will be applied to the element. - * The object can have the following properties: - * - * - **addClass** - `{string}` - space-separated CSS classes to add to element - * - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to` - * - **removeClass** - `{string}` - space-separated CSS classes to remove from element - * - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from` + * @param {object=} options an optional collection of options/styles that will be applied to the element * * @return {Promise} the animation callback promise */ @@ -5712,7 +5519,7 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { }]; }]; -var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { +var $$AnimateAsyncRunFactoryProvider = function() { this.$get = ['$$rAF', function($$rAF) { var waitQueue = []; @@ -5733,19 +5540,15 @@ var $$AnimateAsyncRunFactoryProvider = /** @this */ function() { passed = true; }); return function(callback) { - if (passed) { - callback(); - } else { - waitForTick(callback); - } + passed ? callback() : waitForTick(callback); }; }; }]; }; -var $$AnimateRunnerFactoryProvider = /** @this */ function() { - this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$$isDocumentHidden', '$timeout', - function($q, $sniffer, $$animateAsyncRun, $$isDocumentHidden, $timeout) { +var $$AnimateRunnerFactoryProvider = function() { + this.$get = ['$q', '$sniffer', '$$animateAsyncRun', '$document', '$timeout', + function($q, $sniffer, $$animateAsyncRun, $document, $timeout) { var INITIAL_STATE = 0; var DONE_PENDING_STATE = 1; @@ -5797,7 +5600,11 @@ var $$AnimateRunnerFactoryProvider = /** @this */ function() { this._doneCallbacks = []; this._tick = function(fn) { - if ($$isDocumentHidden()) { + var doc = $document[0]; + + // the document may not be ready or attached + // to the module for some internal tests + if (doc && doc.hidden) { timeoutTick(fn); } else { rafTick(fn); @@ -5826,11 +5633,7 @@ var $$AnimateRunnerFactoryProvider = /** @this */ function() { var self = this; this.promise = $q(function(resolve, reject) { self.done(function(status) { - if (status === false) { - reject(); - } else { - resolve(); - } + status === false ? reject() : resolve(); }); }); } @@ -5900,13 +5703,10 @@ var $$AnimateRunnerFactoryProvider = /** @this */ function() { }]; }; -/* exported $CoreAnimateCssProvider */ - /** * @ngdoc service * @name $animateCss * @kind object - * @this * * @description * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, @@ -5939,6 +5739,7 @@ var $CoreAnimateCssProvider = function() { options.from = null; } + /* jshint newcap: false */ var closed, runner = new $$AnimateRunner(); return { start: run, @@ -6128,7 +5929,7 @@ function Browser(window, document, $log, $sniffer) { // Do the assignment again so that those two variables are referentially identical. lastHistoryState = cachedState; } else { - if (!sameBase) { + if (!sameBase || pendingLocation) { pendingLocation = url; } if (replace) { @@ -6142,9 +5943,6 @@ function Browser(window, document, $log, $sniffer) { pendingLocation = url; } } - if (pendingLocation) { - pendingLocation = url; - } return self; // getter } else { @@ -6152,7 +5950,7 @@ function Browser(window, document, $log, $sniffer) { // the new location.href if a reload happened or if there is a bug like in iOS 9 (see // https://openradar.appspot.com/22186109). // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 - return pendingLocation || location.href.replace(/%27/g,'\''); + return pendingLocation || location.href.replace(/%27/g,"'"); } }; @@ -6277,7 +6075,7 @@ function Browser(window, document, $log, $sniffer) { */ self.baseHref = function() { var href = baseElement.attr('href'); - return href ? href.replace(/^(https?:)?\/\/[^\/]*/, '') : ''; + return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; }; /** @@ -6328,7 +6126,6 @@ function Browser(window, document, $log, $sniffer) { } -/** @this */ function $BrowserProvider() { this.$get = ['$window', '$log', '$sniffer', '$document', function($window, $log, $sniffer, $document) { @@ -6339,7 +6136,6 @@ function $BrowserProvider() { /** * @ngdoc service * @name $cacheFactory - * @this * * @description * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to @@ -6376,7 +6172,7 @@ function $BrowserProvider() { * - `{void}` `destroy()` — Removes references to this cache from $cacheFactory. * * @example - +
@@ -6425,7 +6221,7 @@ function $CacheFactoryProvider() { function cacheFactory(cacheId, options) { if (cacheId in caches) { - throw minErr('$cacheFactory')('iid', 'CacheId \'{0}\' is already taken!', cacheId); + throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); } var size = 0, @@ -6475,7 +6271,7 @@ function $CacheFactoryProvider() { * })); * ``` */ - return (caches[cacheId] = { + return caches[cacheId] = { /** * @ngdoc method @@ -6553,8 +6349,8 @@ function $CacheFactoryProvider() { if (!lruEntry) return; - if (lruEntry === freshEnd) freshEnd = lruEntry.p; - if (lruEntry === staleEnd) staleEnd = lruEntry.n; + if (lruEntry == freshEnd) freshEnd = lruEntry.p; + if (lruEntry == staleEnd) staleEnd = lruEntry.n; link(lruEntry.n,lruEntry.p); delete lruHash[key]; @@ -6619,17 +6415,17 @@ function $CacheFactoryProvider() { info: function() { return extend({}, stats, {size: size}); } - }); + }; /** * makes the `entry` the freshEnd of the LRU linked list */ function refresh(entry) { - if (entry !== freshEnd) { + if (entry != freshEnd) { if (!staleEnd) { staleEnd = entry; - } else if (staleEnd === entry) { + } else if (staleEnd == entry) { staleEnd = entry.n; } @@ -6645,7 +6441,7 @@ function $CacheFactoryProvider() { * bidirectionally links two entries of the LRU linked list */ function link(nextEntry, prevEntry) { - if (nextEntry !== prevEntry) { + if (nextEntry != prevEntry) { if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify } @@ -6693,7 +6489,6 @@ function $CacheFactoryProvider() { /** * @ngdoc service * @name $templateCache - * @this * * @description * The first time a template is used, it is loaded in the template cache for quick retrieval. You @@ -6792,9 +6587,8 @@ function $TemplateCacheProvider() { * There are many different options for a directive. * * The difference resides in the return value of the factory function. - * You can either return a {@link $compile#directive-definition-object Directive Definition Object (see below)} - * that defines the directive properties, or just the `postLink` function (all other properties will have - * the default values). + * You can either return a "Directive Definition Object" (see below) that defines the directive properties, + * or just the `postLink` function (all other properties will have the default values). * *
* **Best Practice:** It's recommended to use the "directive definition object" form. @@ -6807,34 +6601,33 @@ function $TemplateCacheProvider() { * * myModule.directive('directiveName', function factory(injectables) { * var directiveDefinitionObject = { - * {@link $compile#-priority- priority}: 0, - * {@link $compile#-template- template}: '
', // or // function(tElement, tAttrs) { ... }, + * priority: 0, + * template: '
', // or // function(tElement, tAttrs) { ... }, * // or - * // {@link $compile#-templateurl- templateUrl}: 'directive.html', // or // function(tElement, tAttrs) { ... }, - * {@link $compile#-transclude- transclude}: false, - * {@link $compile#-restrict- restrict}: 'A', - * {@link $compile#-templatenamespace- templateNamespace}: 'html', - * {@link $compile#-scope- scope}: false, - * {@link $compile#-controller- controller}: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, - * {@link $compile#-controlleras- controllerAs}: 'stringIdentifier', - * {@link $compile#-bindtocontroller- bindToController}: false, - * {@link $compile#-require- require}: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], - * {@link $compile#-multielement- multiElement}: false, - * {@link $compile#-compile- compile}: function compile(tElement, tAttrs, transclude) { + * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, + * transclude: false, + * restrict: 'A', + * templateNamespace: 'html', + * scope: false, + * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, + * controllerAs: 'stringIdentifier', + * bindToController: false, + * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], + * compile: function compile(tElement, tAttrs, transclude) { * return { - * {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, - * {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } + * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * post: function postLink(scope, iElement, iAttrs, controller) { ... } * } * // or * // return function postLink( ... ) { ... } * }, * // or - * // {@link $compile#-link- link}: { - * // {@link $compile#pre-linking-function pre}: function preLink(scope, iElement, iAttrs, controller) { ... }, - * // {@link $compile#post-linking-function post}: function postLink(scope, iElement, iAttrs, controller) { ... } + * // link: { + * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, + * // post: function postLink(scope, iElement, iAttrs, controller) { ... } * // } * // or - * // {@link $compile#-link- link}: function postLink( ... ) { ... } + * // link: function postLink( ... ) { ... } * }; * return directiveDefinitionObject; * }); @@ -6859,125 +6652,6 @@ function $TemplateCacheProvider() { * }); * ``` * - * ### Life-cycle hooks - * Directive controllers can provide the following methods that are called by Angular at points in the life-cycle of the - * directive: - * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and - * had their bindings initialized (and before the pre & post linking functions for the directives on - * this element). This is a good place to put initialization code for your controller. - * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The - * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an - * object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a - * component such as cloning the bound value to prevent accidental mutation of the outer value. - * * `$doCheck()` - Called on each turn of the digest cycle. Provides an opportunity to detect and act on - * changes. Any actions that you wish to take in response to the changes that you detect must be - * invoked from this hook; implementing this has no effect on when `$onChanges` is called. For example, this hook - * could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not - * be detected by Angular's change detector and thus not trigger `$onChanges`. This hook is invoked with no arguments; - * if detecting changes, you must store the previous value(s) for comparison to the current values. - * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing - * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in - * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent - * components will have their `$onDestroy()` hook called before child components. - * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link - * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. - * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since - * they are waiting for their template to load asynchronously and their own compilation and linking has been - * suspended until that occurs. - * - * #### Comparison with Angular 2 life-cycle hooks - * Angular 2 also uses life-cycle hooks for its components. While the Angular 1 life-cycle hooks are similar there are - * some differences that you should be aware of, especially when it comes to moving your code from Angular 1 to Angular 2: - * - * * Angular 1 hooks are prefixed with `$`, such as `$onInit`. Angular 2 hooks are prefixed with `ng`, such as `ngOnInit`. - * * Angular 1 hooks can be defined on the controller prototype or added to the controller inside its constructor. - * In Angular 2 you can only define hooks on the prototype of the Component class. - * * Due to the differences in change-detection, you may get many more calls to `$doCheck` in Angular 1 than you would to - * `ngDoCheck` in Angular 2 - * * Changes to the model inside `$doCheck` will trigger new turns of the digest loop, which will cause the changes to be - * propagated throughout the application. - * Angular 2 does not allow the `ngDoCheck` hook to trigger a change outside of the component. It will either throw an - * error or do nothing depending upon the state of `enableProdMode()`. - * - * #### Life-cycle hook examples - * - * This example shows how you can check for mutations to a Date object even though the identity of the object - * has not changed. - * - * - * - * angular.module('do-check-module', []) - * .component('app', { - * template: - * 'Month: ' + - * 'Date: {{ $ctrl.date }}' + - * '', - * controller: function() { - * this.date = new Date(); - * this.month = this.date.getMonth(); - * this.updateDate = function() { - * this.date.setMonth(this.month); - * }; - * } - * }) - * .component('test', { - * bindings: { date: '<' }, - * template: - * '
{{ $ctrl.log | json }}
', - * controller: function() { - * var previousValue; - * this.log = []; - * this.$doCheck = function() { - * var currentValue = this.date && this.date.valueOf(); - * if (previousValue !== currentValue) { - * this.log.push('doCheck: date mutated: ' + this.date); - * previousValue = currentValue; - * } - * }; - * } - * }); - *
- * - * - * - *
- * - * This example show how you might use `$doCheck` to trigger changes in your component's inputs even if the - * actual identity of the component doesn't change. (Be aware that cloning and deep equality checks on large - * arrays or objects can have a negative impact on your application performance) - * - * - * - *
- * - * - *
{{ items }}
- * - *
- *
- * - * angular.module('do-check-module', []) - * .component('test', { - * bindings: { items: '<' }, - * template: - * '
{{ $ctrl.log | json }}
', - * controller: function() { - * this.log = []; - * - * this.$doCheck = function() { - * if (this.items_ref !== this.items) { - * this.log.push('doCheck: items changed'); - * this.items_ref = this.items; - * } - * if (!angular.equals(this.items_clone, this.items)) { - * this.log.push('doCheck: items mutated'); - * this.items_clone = angular.copy(this.items); - * } - * }; - * } - * }); - *
- *
* * * ### Directive Definition Object @@ -6986,7 +6660,7 @@ function $TemplateCacheProvider() { * compiler}. The attributes are: * * #### `multiElement` - * When this property is set to true (default is `false`), the HTML compiler will collect DOM nodes between + * When this property is set to true, the HTML compiler will collect DOM nodes between * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them * together as the directive elements. It is recommended that this feature be used on directives * which are not strictly behavioral (such as {@link ngClick}), and which @@ -7007,14 +6681,14 @@ function $TemplateCacheProvider() { * and other directives used in the directive's template will also be excluded from execution. * * #### `scope` - * The scope property can be `false`, `true`, or an object: + * The scope property can be `true`, an object or a falsy value: * - * * **`false` (default):** No scope will be created for the directive. The directive will use its - * parent's scope. + * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. * * * **`true`:** A new child scope that prototypically inherits from its parent will be created for * the directive's element. If multiple directives on the same element request a new scope, - * only one new scope is created. + * only one new scope is created. The new scope rule does not apply for the root of the template + * since the root of the template always gets a new scope. * * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent @@ -7153,6 +6827,25 @@ function $TemplateCacheProvider() { * The `$transclude` function also has a method on it, `$transclude.isSlotFilled(slotName)`, which returns * `true` if the specified slot contains content (i.e. one or more DOM nodes). * + * The controller can provide the following methods that act as life-cycle hooks: + * * `$onInit()` - Called on each controller after all the controllers on an element have been constructed and + * had their bindings initialized (and before the pre & post linking functions for the directives on + * this element). This is a good place to put initialization code for your controller. + * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The + * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an + * object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component + * such as cloning the bound value to prevent accidental mutation of the outer value. + * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing + * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in + * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent + * components will have their `$onDestroy()` hook called before child components. + * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link + * function this hook can be used to set up DOM event handlers and do direct DOM manipulation. + * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since + * they are waiting for their template to load asynchronously and their own compilation and linking has been + * suspended until that occurs. + * + * * #### `require` * Require another directive and inject its controller as the fourth argument to the linking function. The * `require` property can be a string, an array or an object: @@ -7166,9 +6859,8 @@ function $TemplateCacheProvider() { * If the `require` property is an object and `bindToController` is truthy, then the required controllers are * bound to the controller using the keys of the `require` property. This binding occurs after all the controllers * have been constructed but before `$onInit` is called. - * If the name of the required controller is the same as the local name (the key), the name can be - * omitted. For example, `{parentDir: '^^'}` is equivalent to `{parentDir: '^^parentDir'}`. * See the {@link $compileProvider#component} helper for an example of how this can be used. + * * If no such required directive(s) can be found, or if the directive does not have a controller, then an error is * raised (unless no link function is specified and the required controllers are not being bound to the directive * controller, in which case error checking is skipped). The name can be prefixed with: @@ -7350,8 +7042,8 @@ function $TemplateCacheProvider() { * any other controller. * * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. - * This is the same as the `$transclude` parameter of directive controllers, - * see {@link ng.$compile#-controller- the controller section for details}. + * This is the same as the `$transclude` + * parameter of directive controllers, see there for details. * `function([scope], cloneLinkingFn, futureParentElement)`. * * #### Pre-linking function @@ -7438,7 +7130,7 @@ function $TemplateCacheProvider() { * * When you call a transclusion function you can pass in a **clone attach function**. This function accepts * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded - * content and the `scope` is the newly created transclusion scope, which the clone will be linked to. + * content and the `scope` is the newly created transclusion scope, to which the clone is bound. * *
* **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a transclude function @@ -7556,7 +7248,7 @@ function $TemplateCacheProvider() { * to illustrate how `$compile` works. *
* - + -
- - Model as range: -
- Model as number:
- Min:
- Max:
- value = {{value}}
- myForm.range.$valid = {{myForm.range.$valid}}
- myForm.range.$error = {{myForm.range.$error}} -
-
-
- - * ## Range Input with ngMin & ngMax attributes - - * @example - - - -
- Model as range: -
- Model as number:
- Min:
- Max:
- value = {{value}}
- myForm.range.$valid = {{myForm.range.$valid}}
- myForm.range.$error = {{myForm.range.$error}} -
-
-
- - */ - 'range': rangeInputType, /** * @ngdoc input @@ -24527,7 +22940,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { } }; - element.on('keydown', /** @this */ function(event) { + element.on('keydown', function(event) { var key = event.keyCode; // ignore @@ -24552,7 +22965,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { // For these event types, when native validators are present and the browser supports the type, // check for validity changes on various DOM events. if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) { - element.on(PARTIAL_VALIDATION_EVENTS, /** @this */ function(ev) { + element.on(PARTIAL_VALIDATION_EVENTS, function(ev) { if (!timeout) { var validity = this[VALIDITY_STATE_PROPERTY]; var origBadInput = validity.badInput; @@ -24620,7 +23033,7 @@ function createDateParser(regexp, mapping) { // When a date is JSON'ified to wraps itself inside of an extra // set of double quotes. This makes the date parsing code unable // to match the date string and parse it as a date. - if (iso.charAt(0) === '"' && iso.charAt(iso.length - 1) === '"') { + if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { iso = iso.substring(1, iso.length - 1); } if (ISO_DATE_REGEXP.test(iso)) { @@ -24662,7 +23075,7 @@ function createDateInputType(type, regexp, parseDate, format) { return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { badInputChecker(scope, element, attr, ctrl); baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - var timezone = ctrl && ctrl.$options.getOption('timezone'); + var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; var previousDate; ctrl.$$parserName = type; @@ -24741,7 +23154,10 @@ function badInputChecker(scope, element, attr, ctrl) { } } -function numberFormatterParser(ctrl) { +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { + badInputChecker(scope, element, attr, ctrl); + baseInputType(scope, element, attr, ctrl, $sniffer, $browser); + ctrl.$$parserName = 'number'; ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; @@ -24758,19 +23174,6 @@ function numberFormatterParser(ctrl) { } return value; }); -} - -function parseNumberAttrVal(val) { - if (isDefined(val) && !isNumber(val)) { - val = parseFloat(val); - } - return !isNumberNaN(val) ? val : undefined; -} - -function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - badInputChecker(scope, element, attr, ctrl); - numberFormatterParser(ctrl); - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); if (isDefined(attr.min) || attr.ngMin) { var minVal; @@ -24779,7 +23182,10 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { }; attr.$observe('min', function(val) { - minVal = parseNumberAttrVal(val); + if (isDefined(val) && !isNumber(val)) { + val = parseFloat(val, 10); + } + minVal = isNumber(val) && !isNaN(val) ? val : undefined; // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); }); @@ -24792,157 +23198,14 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { }; attr.$observe('max', function(val) { - maxVal = parseNumberAttrVal(val); + if (isDefined(val) && !isNumber(val)) { + val = parseFloat(val, 10); + } + maxVal = isNumber(val) && !isNaN(val) ? val : undefined; // TODO(matsko): implement validateLater to reduce number of validations ctrl.$validate(); }); } - - if (isDefined(attr.step) || attr.ngStep) { - var stepVal; - ctrl.$validators.step = function(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || viewValue % stepVal === 0; - }; - - attr.$observe('step', function(val) { - stepVal = parseNumberAttrVal(val); - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - }); - } -} - -function rangeInputType(scope, element, attr, ctrl, $sniffer, $browser) { - badInputChecker(scope, element, attr, ctrl); - numberFormatterParser(ctrl); - baseInputType(scope, element, attr, ctrl, $sniffer, $browser); - - var supportsRange = ctrl.$$hasNativeValidators && element[0].type === 'range', - minVal = supportsRange ? 0 : undefined, - maxVal = supportsRange ? 100 : undefined, - stepVal = supportsRange ? 1 : undefined, - validity = element[0].validity, - hasMinAttr = isDefined(attr.min), - hasMaxAttr = isDefined(attr.max), - hasStepAttr = isDefined(attr.step); - - var originalRender = ctrl.$render; - - ctrl.$render = supportsRange && isDefined(validity.rangeUnderflow) && isDefined(validity.rangeOverflow) ? - //Browsers that implement range will set these values automatically, but reading the adjusted values after - //$render would cause the min / max validators to be applied with the wrong value - function rangeRender() { - originalRender(); - ctrl.$setViewValue(element.val()); - } : - originalRender; - - if (hasMinAttr) { - ctrl.$validators.min = supportsRange ? - // Since all browsers set the input to a valid value, we don't need to check validity - function noopMinValidator() { return true; } : - // non-support browsers validate the min val - function minValidator(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(minVal) || viewValue >= minVal; - }; - - setInitialValueAndObserver('min', minChange); - } - - if (hasMaxAttr) { - ctrl.$validators.max = supportsRange ? - // Since all browsers set the input to a valid value, we don't need to check validity - function noopMaxValidator() { return true; } : - // non-support browsers validate the max val - function maxValidator(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(maxVal) || viewValue <= maxVal; - }; - - setInitialValueAndObserver('max', maxChange); - } - - if (hasStepAttr) { - ctrl.$validators.step = supportsRange ? - function nativeStepValidator() { - // Currently, only FF implements the spec on step change correctly (i.e. adjusting the - // input element value to a valid value). It's possible that other browsers set the stepMismatch - // validity error instead, so we can at least report an error in that case. - return !validity.stepMismatch; - } : - // ngStep doesn't set the setp attr, so the browser doesn't adjust the input value as setting step would - function stepValidator(modelValue, viewValue) { - return ctrl.$isEmpty(viewValue) || isUndefined(stepVal) || viewValue % stepVal === 0; - }; - - setInitialValueAndObserver('step', stepChange); - } - - function setInitialValueAndObserver(htmlAttrName, changeFn) { - // interpolated attributes set the attribute value only after a digest, but we need the - // attribute value when the input is first rendered, so that the browser can adjust the - // input value based on the min/max value - element.attr(htmlAttrName, attr[htmlAttrName]); - attr.$observe(htmlAttrName, changeFn); - } - - function minChange(val) { - minVal = parseNumberAttrVal(val); - // ignore changes before model is initialized - if (isNumberNaN(ctrl.$modelValue)) { - return; - } - - if (supportsRange) { - var elVal = element.val(); - // IE11 doesn't set the el val correctly if the minVal is greater than the element value - if (minVal > elVal) { - elVal = minVal; - element.val(elVal); - } - ctrl.$setViewValue(elVal); - } else { - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - } - } - - function maxChange(val) { - maxVal = parseNumberAttrVal(val); - // ignore changes before model is initialized - if (isNumberNaN(ctrl.$modelValue)) { - return; - } - - if (supportsRange) { - var elVal = element.val(); - // IE11 doesn't set the el val correctly if the maxVal is less than the element value - if (maxVal < elVal) { - element.val(maxVal); - // IE11 and Chrome don't set the value to the minVal when max < min - elVal = maxVal < minVal ? minVal : maxVal; - } - ctrl.$setViewValue(elVal); - } else { - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - } - } - - function stepChange(val) { - stepVal = parseNumberAttrVal(val); - // ignore changes before model is initialized - if (isNumberNaN(ctrl.$modelValue)) { - return; - } - - // Some browsers don't adjust the input value correctly, but set the stepMismatch error - if (supportsRange && ctrl.$viewValue !== element.val()) { - ctrl.$setViewValue(element.val()); - } else { - // TODO(matsko): implement validateLater to reduce number of validations - ctrl.$validate(); - } - } } function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { @@ -24972,20 +23235,14 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { } function radioInputType(scope, element, attr, ctrl) { - var doTrim = !attr.ngTrim || trim(attr.ngTrim) !== 'false'; // make the name unique, if not defined if (isUndefined(attr.name)) { element.attr('name', nextUid()); } var listener = function(ev) { - var value; if (element[0].checked) { - value = attr.value; - if (doTrim) { - value = trim(value); - } - ctrl.$setViewValue(value, ev && ev.type); + ctrl.$setViewValue(attr.value, ev && ev.type); } }; @@ -24993,11 +23250,6 @@ function radioInputType(scope, element, attr, ctrl) { ctrl.$render = function() { var value = attr.value; - if (doTrim) { - value = trim(value); - } - // Strict comparison would cause a BC - // eslint-disable-next-line eqeqeq element[0].checked = (value == ctrl.$viewValue); }; @@ -25081,20 +23333,6 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt * @param {string=} ngChange Angular expression to be executed when input changes due to user * interaction with the input element. * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. - * - * @knownIssue - * - * When specifying the `placeholder` attribute of `
- * {{text}} + * {{text}} *
* * @@ -31420,7 +29538,7 @@ var ngSwitchDefaultDirective = ngDirective({ * This example shows how to use `NgTransclude` with fallback content, that * is displayed if no transcluded content is provided. * - * + * * * + + +``` + +In Node.js: +```js +// Load the fp build. +var fp = require('lodash/fp'); + +// Load a method category. +var object = require('lodash/fp/object'); + +// Load a single method for smaller builds with browserify/rollup/webpack. +var extend = require('lodash/fp/extend'); +``` + +## Mapping + +Immutable auto-curried iteratee-first data-last methods sound great, but what +does that really mean for each method? Below is a breakdown of the mapping used +to convert each method. + +#### Capped Iteratee Arguments + +Iteratee arguments are capped to avoid gotchas with variadic iteratees. +```js +// The `lodash/map` iteratee receives three arguments: +// (value, index|key, collection) +_.map(['6', '8', '10'], parseInt); +// ➜ [6, NaN, 2] + +// The `lodash/fp/map` iteratee is capped at one argument: +// (value) +fp.map(parseInt)(['6', '8', '10']); +// ➜ [6, 8, 10] +``` + +Methods that cap iteratees to one argument:
+<%= toFuncList(_.keys(_.pickBy(mapping.iterateeAry, _.partial(_.eq, _, 1)))) %> + +Methods that cap iteratees to two arguments:
+<%= toFuncList(_.keys(_.pickBy(mapping.iterateeAry, _.partial(_.eq, _, 2)))) %> + +The iteratee of `mapKeys` is invoked with one argument: (key) + +#### Fixed Arity + +Methods have fixed arities to support auto-currying. +```js +// `lodash/padStart` accepts an optional `chars` param. +_.padStart('a', 3, '-') +// ➜ '--a' + +// `lodash/fp/padStart` does not. +fp.padStart(3)('a'); +// ➜ ' a' +fp.padCharsStart('-')(3)('a'); +// ➜ '--a' +``` + +Methods with a fixed arity of one:
+<%= toFuncList(_.difference(mapping.aryMethod[1], _.keys(mapping.skipFixed))) %> + +Methods with a fixed arity of two:
+<%= toFuncList(_.difference(mapping.aryMethod[2], _.keys(mapping.skipFixed))) %> + +Methods with a fixed arity of three:
+<%= toFuncList(_.difference(mapping.aryMethod[3], _.keys(mapping.skipFixed))) %> + +Methods with a fixed arity of four:
+<%= toFuncList(_.difference(mapping.aryMethod[4], _.keys(mapping.skipFixed))) %> + +#### Rearranged Arguments + +Method arguments are rearranged to make composition easier. +```js +// `lodash/filter` is data-first iteratee-last: +// (collection, iteratee) +var compact = _.partial(_.filter, _, Boolean); +compact(['a', null, 'c']); +// ➜ ['a', 'c'] + +// `lodash/fp/filter` is iteratee-first data-last: +// (iteratee, collection) +var compact = fp.filter(Boolean); +compact(['a', null, 'c']); +// ➜ ['a', 'c'] +``` + +##### Most methods follow these rules + +A fixed arity of two has an argument order of:
+<%= toArgOrder(mapping.aryRearg[2]) %> + +A fixed arity of three has an argument order of:
+<%= toArgOrder(mapping.aryRearg[3]) %> + +A fixed arity of four has an argument order of:
+<%= toArgOrder(mapping.aryRearg[4]) %> + +##### Exceptions to the rules + +Methods that accept an array of arguments:
+<%= toFuncList(_.keys(mapping.methodSpread)) %> + +Methods with unchanged argument orders:
+<%= toFuncList(_.keys(mapping.skipRearg)) %> + +Methods with custom argument orders:
+<%= _.map(_.keys(mapping.methodRearg), function(methodName) { + var orders = mapping.methodRearg[methodName]; + return ' * `_.' + methodName + '` has an order of ' + toArgOrder(orders); +}).join('\n') %> + +#### New Methods + +Not all variadic methods have corresponding new method variants. Feel free to +[request](https://github.com/lodash/lodash/blob/master/.github/CONTRIBUTING.md#feature-requests) +any additions. + +Methods created to accommodate Lodash’s variadic methods:
+<%= toFuncList(_.keys(mapping.remap)) %> + +#### Aliases + +There are <%= _.size(mapping.aliasToReal) %> method aliases:
+<%= _.map(_.keys(mapping.aliasToReal).sort(), function(alias) { + var realName = mapping.aliasToReal[alias]; + return ' * `_.' + alias + '` is an alias of `_.' + realName + '`'; +}).join('\n') %> + +## Placeholders + +The placeholder argument, which defaults to `_`, may be used to fill in method +arguments in a different order. Placeholders are filled by the first available +arguments of the curried returned function. +```js +// The equivalent of `2 > 5`. +_.gt(2)(5); +// ➜ false + +// The equivalent of `_.gt(5, 2)` or `5 > 2`. +_.gt(_, 2)(5); +// ➜ true +``` + +## Chaining + +The `lodash/fp` module **does not** convert chain sequence methods. See +[Izaak Schroeder’s article](https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba) +on using functional composition as an alternative to method chaining. + +## Convert + +Although `lodash/fp` & its method modules come pre-converted, there are times +when you may want to customize the conversion. That’s when the `convert` method +comes in handy. +```js +// Every option is `true` by default. +var _fp = fp.convert({ + // Specify capping iteratee arguments. + 'cap': true, + // Specify currying. + 'curry': true, + // Specify fixed arity. + 'fixed': true, + // Specify immutable operations. + 'immutable': true, + // Specify rearranging arguments. + 'rearg': true +}); + +// The `convert` method is available on each method too. +var mapValuesWithKey = fp.mapValues.convert({ 'cap': false }); + +// Here’s an example of disabling iteratee argument caps to access the `key` param. +mapValuesWithKey(function(value, key) { + return key == 'a' ? -1 : value; +})({ 'a': 1, 'b': 1 }); +// => { 'a': -1, 'b': 1 } +``` + +Manual conversions are also possible with the `convert` module. +```js +var convert = require('lodash/fp/convert'); + +// Convert by name. +var assign = convert('assign', require('lodash.assign')); + +// Convert by object. +var fp = convert({ + 'assign': require('lodash.assign'), + 'chunk': require('lodash.chunk') +}); + +// Convert by `lodash` instance. +var fp = convert(lodash.runInContext()); +``` + +## Tooling + +Use [eslint-plugin-lodash-fp](https://www.npmjs.com/package/eslint-plugin-lodash-fp) +to help use `lodash/fp` more efficiently. diff --git a/public/vendor/lodash/lib/fp/template/modules/_falseOptions.jst b/public/vendor/lodash/lib/fp/template/modules/_falseOptions.jst new file mode 100644 index 00000000000..773235e3437 --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/_falseOptions.jst @@ -0,0 +1,7 @@ +module.exports = { + 'cap': false, + 'curry': false, + 'fixed': false, + 'immutable': false, + 'rearg': false +}; diff --git a/public/vendor/lodash/lib/fp/template/modules/_util.jst b/public/vendor/lodash/lib/fp/template/modules/_util.jst new file mode 100644 index 00000000000..f8148129eb8 --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/_util.jst @@ -0,0 +1,15 @@ +module.exports = { + 'ary': require('../ary'), + 'assign': require('../_baseAssign'), + 'clone': require('../clone'), + 'curry': require('../curry'), + 'forEach': require('../_arrayEach'), + 'isArray': require('../isArray'), + 'isFunction': require('../isFunction'), + 'iteratee': require('../iteratee'), + 'keys': require('../_baseKeys'), + 'rearg': require('../rearg'), + 'spread': require('../spread'), + 'toInteger': require('../toInteger'), + 'toPath': require('../toPath') +}; diff --git a/public/vendor/lodash/lib/fp/template/modules/alias.jst b/public/vendor/lodash/lib/fp/template/modules/alias.jst new file mode 100644 index 00000000000..6d72710a349 --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/alias.jst @@ -0,0 +1 @@ +module.exports = require('./<%= name %>'); diff --git a/public/vendor/lodash/lib/fp/template/modules/category.jst b/public/vendor/lodash/lib/fp/template/modules/category.jst new file mode 100644 index 00000000000..62c2db8a16c --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/category.jst @@ -0,0 +1,2 @@ +var convert = require('./convert'); +module.exports = convert(require('../<%= name %>')); diff --git a/public/vendor/lodash/lib/fp/template/modules/convert.jst b/public/vendor/lodash/lib/fp/template/modules/convert.jst new file mode 100644 index 00000000000..4795dc42460 --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/convert.jst @@ -0,0 +1,18 @@ +var baseConvert = require('./_baseConvert'), + util = require('./_util'); + +/** + * Converts `func` of `name` to an immutable auto-curried iteratee-first data-last + * version with conversion `options` applied. If `name` is an object its methods + * will be converted. + * + * @param {string} name The name of the function to wrap. + * @param {Function} [func] The function to wrap. + * @param {Object} [options] The options object. See `baseConvert` for more details. + * @returns {Function|Object} Returns the converted function or object. + */ +function convert(name, func, options) { + return baseConvert(util, name, func, options); +} + +module.exports = convert; diff --git a/public/vendor/lodash/lib/fp/template/modules/fp.jst b/public/vendor/lodash/lib/fp/template/modules/fp.jst new file mode 100644 index 00000000000..e372dbbdf6d --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/fp.jst @@ -0,0 +1,2 @@ +var _ = require('./lodash.min').runInContext(); +module.exports = require('./fp/_baseConvert')(_, _); diff --git a/public/vendor/lodash/lib/fp/template/modules/module.jst b/public/vendor/lodash/lib/fp/template/modules/module.jst new file mode 100644 index 00000000000..1fb809cb22a --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/module.jst @@ -0,0 +1,5 @@ +var convert = require('./convert'), + func = convert('<%= name %>', require('../<%= _.get(mapping.remap, name, name) %>')); + +func.placeholder = require('./placeholder'); +module.exports = func; diff --git a/public/vendor/lodash/lib/fp/template/modules/thru.jst b/public/vendor/lodash/lib/fp/template/modules/thru.jst new file mode 100644 index 00000000000..838e8b03a81 --- /dev/null +++ b/public/vendor/lodash/lib/fp/template/modules/thru.jst @@ -0,0 +1,5 @@ +var convert = require('./convert'), + func = convert('<%= name %>', require('../<%= _.get(mapping.remap, name, name) %>'), require('./_falseOptions')); + +func.placeholder = require('./placeholder'); +module.exports = func; diff --git a/public/vendor/lodash/lib/main/build-dist.js b/public/vendor/lodash/lib/main/build-dist.js new file mode 100644 index 00000000000..137b6915edc --- /dev/null +++ b/public/vendor/lodash/lib/main/build-dist.js @@ -0,0 +1,31 @@ +'use strict'; + +var async = require('async'), + path = require('path'); + +var file = require('../common/file'), + util = require('../common/util'); + +var basePath = path.join(__dirname, '..', '..'), + distPath = path.join(basePath, 'dist'), + filename = 'lodash.js'; + +var baseLodash = path.join(basePath, filename), + distLodash = path.join(distPath, filename); + +/*----------------------------------------------------------------------------*/ + +/** + * Creates browser builds of Lodash at the `target` path. + * + * @private + * @param {string} target The output directory path. + */ +function build() { + async.series([ + file.copy(baseLodash, distLodash), + file.min(distLodash) + ], util.pitch); +} + +build(); diff --git a/public/vendor/lodash/lib/main/build-doc.js b/public/vendor/lodash/lib/main/build-doc.js new file mode 100644 index 00000000000..94003448bfc --- /dev/null +++ b/public/vendor/lodash/lib/main/build-doc.js @@ -0,0 +1,83 @@ +'use strict'; + +var _ = require('lodash'), + docdown = require('docdown'), + fs = require('fs-extra'), + path = require('path'); + +var util = require('../common/util'); + +var basePath = path.join(__dirname, '..', '..'), + docPath = path.join(basePath, 'doc'), + readmePath = path.join(docPath, 'README.md'); + +var pkg = require('../../package.json'), + version = pkg.version; + +var config = { + 'base': { + 'path': path.join(basePath, 'lodash.js'), + 'title': '
lodash v' + version + '', + 'toc': 'categories', + 'url': 'https://github.com/lodash/lodash/blob/' + version + '/lodash.js' + }, + 'github': { + 'style': 'github', + 'sublinks': [npmLink('Ⓝ', 'See the npm package')] + }, + 'site': { + 'entryLink': '', + 'sourceLink': '[source](${sourceHref})', + 'tocHref': '', + 'tocLink': '', + 'sublinks': [npmLink('npm package')] + } +}; + +/** + * Composes a npm link from `text` and optional `title`. + * + * @private + * @param {string} text The link text. + * @param {string} [title] The link title. + * @returns {string} Returns the composed npm link. + */ +function npmLink(text, title) { + return ( + '<% if (name == "templateSettings" || !/^(?:methods|properties|seq)$/i.test(category)) {' + + 'print(' + + '"[' + text + '](https://www.npmjs.com/package/lodash." + name.toLowerCase() + ' + + '"' + (title == null ? '' : ' \\"' + title + '\\"') + ')"' + + ');' + + '} %>' + ); +} + +/** + * Post-process `markdown` to make adjustments. + * + * @private + * @param {string} markdown The markdown to process. + * @returns {string} Returns the processed markdown. + */ +function postprocess(markdown) { + // Wrap symbol property identifiers in brackets. + return markdown.replace(/\.(Symbol\.(?:[a-z]+[A-Z]?)+)/g, '[$1]'); +} + +/*----------------------------------------------------------------------------*/ + +/** + * Creates the documentation markdown formatted for 'github' or 'site'. + * + * @private + * @param {string} type The format type. + */ +function build(type) { + var options = _.defaults({}, config.base, config[type]), + markdown = docdown(options); + + fs.writeFile(readmePath, postprocess(markdown), util.pitch); +} + +build(_.last(process.argv)); diff --git a/public/vendor/lodash/lib/main/build-modules.js b/public/vendor/lodash/lib/main/build-modules.js new file mode 100644 index 00000000000..e9e3f365b03 --- /dev/null +++ b/public/vendor/lodash/lib/main/build-modules.js @@ -0,0 +1,35 @@ +'use strict'; + +var _ = require('lodash'), + async = require('async'), + path = require('path'); + +var file = require('../common/file'), + util = require('../common/util'); + +var basePath = path.join(__dirname, '..', '..'), + distPath = path.join(basePath, 'dist'); + +var filePairs = [ + [path.join(distPath, 'lodash.core.js'), 'core.js'], + [path.join(distPath, 'lodash.core.min.js'), 'core.min.js'], + [path.join(distPath, 'lodash.min.js'), 'lodash.min.js'] +]; + +/*----------------------------------------------------------------------------*/ + +/** + * Creates supplementary Lodash modules at the `target` path. + * + * @private + * @param {string} target The output directory path. + */ +function build(target) { + var actions = _.map(filePairs, function(pair) { + return file.copy(pair[0], path.join(target, pair[1])); + }); + + async.series(actions, util.pitch); +} + +build(_.last(process.argv)); diff --git a/public/vendor/lodash/lib/main/build-site.js b/public/vendor/lodash/lib/main/build-site.js new file mode 100644 index 00000000000..37c1ab04595 --- /dev/null +++ b/public/vendor/lodash/lib/main/build-site.js @@ -0,0 +1,73 @@ +'use strict'; + +var _ = require('lodash'), + fs = require('fs'), + marky = require('marky-markdown'), + path = require('path'), + util = require('../common/util'); + +var basePath = path.join(__dirname, '..', '..'), + docPath = path.join(basePath, 'doc'), + readmePath = path.join(docPath, 'README.md'); + +function build(type) { + var markdown = fs + // Load markdown. + .readFileSync(readmePath, 'utf8') + // Uncomment docdown HTML hints. + .replace(/(<)!--\s*|\s*--(>)/g, '$1$2'); + + var $ = marky(markdown, { 'sanitize': false }), + $header = $('h1').first().remove(), + version = _.trim($header.find('span').first().text()).slice(1); + + // Remove docdown horizontal rules. + $('hr').remove(); + + // Remove marky-markdown additions. + $('[id^="user-content-"]') + .attr('class', null) + .attr('id', null); + + $(':header:not(h3) > a').each(function() { + var $a = $(this); + $a.replaceWith($a.html()); + }); + + // Fix marky-markdown wrapping around headers. + $('p:empty + h3').prev().remove(); + + $('h3 ~ p:empty').each(function() { + var $p = $(this), + node = this.previousSibling; + + while ((node = node.previousSibling) && node.name != 'h3' && node.name != 'p') { + $p.prepend(node.nextSibling); + } + }); + + $('h3 code em').parent().each(function() { + var $code = $(this); + $code.html($code.html().replace(/<\/?em>/g, '_')); + }); + + var html = [ + // Append YAML front matter. + '---', + 'id: docs', + 'layout: docs', + 'title: Lodash Documentation', + 'version: ' + (version || null), + '---', + '', + // Wrap in raw tags to avoid Liquid template tag processing. + '{% raw %}', + _.trim($.html()), + '{% endraw %}', + '' + ].join('\n'); + + fs.writeFile(path.join(docPath, version + '.html'), html, util.pitch); +} + +build(); diff --git a/public/vendor/lodash/lodash.js b/public/vendor/lodash/lodash.js new file mode 100644 index 00000000000..6b252563043 --- /dev/null +++ b/public/vendor/lodash/lodash.js @@ -0,0 +1,16733 @@ +/** + * @license + * lodash + * Copyright jQuery Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ +;(function() { + + /** Used as a safe reference for `undefined` in pre-ES5 environments. */ + var undefined; + + /** Used as the semantic version number. */ + var VERSION = '4.15.0'; + + /** Used as the size to enable large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** Used as the `TypeError` message for "Functions" methods. */ + var FUNC_ERROR_TEXT = 'Expected a function'; + + /** Used to stand-in for `undefined` hash values. */ + var HASH_UNDEFINED = '__lodash_hash_undefined__'; + + /** Used as the internal argument placeholder. */ + var PLACEHOLDER = '__lodash_placeholder__'; + + /** Used to compose bitmasks for function metadata. */ + var BIND_FLAG = 1, + BIND_KEY_FLAG = 2, + CURRY_BOUND_FLAG = 4, + CURRY_FLAG = 8, + CURRY_RIGHT_FLAG = 16, + PARTIAL_FLAG = 32, + PARTIAL_RIGHT_FLAG = 64, + ARY_FLAG = 128, + REARG_FLAG = 256, + FLIP_FLAG = 512; + + /** Used to compose bitmasks for comparison styles. */ + var UNORDERED_COMPARE_FLAG = 1, + PARTIAL_COMPARE_FLAG = 2; + + /** Used as default options for `_.truncate`. */ + var DEFAULT_TRUNC_LENGTH = 30, + DEFAULT_TRUNC_OMISSION = '...'; + + /** Used to detect hot functions by number of calls within a span of milliseconds. */ + var HOT_COUNT = 150, + HOT_SPAN = 16; + + /** Used to indicate the type of lazy iteratees. */ + var LAZY_FILTER_FLAG = 1, + LAZY_MAP_FLAG = 2, + LAZY_WHILE_FLAG = 3; + + /** Used as references for various `Number` constants. */ + var INFINITY = 1 / 0, + MAX_SAFE_INTEGER = 9007199254740991, + MAX_INTEGER = 1.7976931348623157e+308, + NAN = 0 / 0; + + /** Used as references for the maximum length and index of an array. */ + var MAX_ARRAY_LENGTH = 4294967295, + MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, + HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; + + /** Used to associate wrap methods with their bit flags. */ + var wrapFlags = [ + ['ary', ARY_FLAG], + ['bind', BIND_FLAG], + ['bindKey', BIND_KEY_FLAG], + ['curry', CURRY_FLAG], + ['curryRight', CURRY_RIGHT_FLAG], + ['flip', FLIP_FLAG], + ['partial', PARTIAL_FLAG], + ['partialRight', PARTIAL_RIGHT_FLAG], + ['rearg', REARG_FLAG] + ]; + + /** `Object#toString` result references. */ + var argsTag = '[object Arguments]', + arrayTag = '[object Array]', + boolTag = '[object Boolean]', + dateTag = '[object Date]', + errorTag = '[object Error]', + funcTag = '[object Function]', + genTag = '[object GeneratorFunction]', + mapTag = '[object Map]', + numberTag = '[object Number]', + objectTag = '[object Object]', + promiseTag = '[object Promise]', + regexpTag = '[object RegExp]', + setTag = '[object Set]', + stringTag = '[object String]', + symbolTag = '[object Symbol]', + weakMapTag = '[object WeakMap]', + weakSetTag = '[object WeakSet]'; + + var arrayBufferTag = '[object ArrayBuffer]', + dataViewTag = '[object DataView]', + float32Tag = '[object Float32Array]', + float64Tag = '[object Float64Array]', + int8Tag = '[object Int8Array]', + int16Tag = '[object Int16Array]', + int32Tag = '[object Int32Array]', + uint8Tag = '[object Uint8Array]', + uint8ClampedTag = '[object Uint8ClampedArray]', + uint16Tag = '[object Uint16Array]', + uint32Tag = '[object Uint32Array]'; + + /** Used to match empty string literals in compiled template source. */ + var reEmptyStringLeading = /\b__p \+= '';/g, + reEmptyStringMiddle = /\b(__p \+=) '' \+/g, + reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + + /** Used to match HTML entities and HTML characters. */ + var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g, + reUnescapedHtml = /[&<>"'`]/g, + reHasEscapedHtml = RegExp(reEscapedHtml.source), + reHasUnescapedHtml = RegExp(reUnescapedHtml.source); + + /** Used to match template delimiters. */ + var reEscape = /<%-([\s\S]+?)%>/g, + reEvaluate = /<%([\s\S]+?)%>/g, + reInterpolate = /<%=([\s\S]+?)%>/g; + + /** Used to match property names within property paths. */ + var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, + reIsPlainProp = /^\w*$/, + reLeadingDot = /^\./, + rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; + + /** + * Used to match `RegExp` + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). + */ + var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, + reHasRegExpChar = RegExp(reRegExpChar.source); + + /** Used to match leading and trailing whitespace. */ + var reTrim = /^\s+|\s+$/g, + reTrimStart = /^\s+/, + reTrimEnd = /\s+$/; + + /** Used to match wrap detail comments. */ + var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, + reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, + reSplitDetails = /,? & /; + + /** Used to match words composed of alphanumeric characters. */ + var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; + + /** Used to match backslashes in property paths. */ + var reEscapeChar = /\\(\\)?/g; + + /** + * Used to match + * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). + */ + var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; + + /** Used to match `RegExp` flags from their coerced string values. */ + var reFlags = /\w*$/; + + /** Used to detect hexadecimal string values. */ + var reHasHexPrefix = /^0x/i; + + /** Used to detect bad signed hexadecimal string values. */ + var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; + + /** Used to detect binary string values. */ + var reIsBinary = /^0b[01]+$/i; + + /** Used to detect host constructors (Safari). */ + var reIsHostCtor = /^\[object .+?Constructor\]$/; + + /** Used to detect octal string values. */ + var reIsOctal = /^0o[0-7]+$/i; + + /** Used to detect unsigned integer values. */ + var reIsUint = /^(?:0|[1-9]\d*)$/; + + /** Used to match Latin Unicode letters (excluding mathematical operators). */ + var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; + + /** Used to ensure capturing order of template delimiters. */ + var reNoMatch = /($^)/; + + /** Used to match unescaped characters in compiled string literals. */ + var reUnescapedString = /['\n\r\u2028\u2029\\]/g; + + /** Used to compose unicode character classes. */ + var rsAstralRange = '\\ud800-\\udfff', + rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23', + rsComboSymbolsRange = '\\u20d0-\\u20f0', + rsDingbatRange = '\\u2700-\\u27bf', + rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff', + rsMathOpRange = '\\xac\\xb1\\xd7\\xf7', + rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf', + rsPunctuationRange = '\\u2000-\\u206f', + rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000', + rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde', + rsVarRange = '\\ufe0e\\ufe0f', + rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; + + /** Used to compose unicode capture groups. */ + var rsApos = "['\u2019]", + rsAstral = '[' + rsAstralRange + ']', + rsBreak = '[' + rsBreakRange + ']', + rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']', + rsDigits = '\\d+', + rsDingbat = '[' + rsDingbatRange + ']', + rsLower = '[' + rsLowerRange + ']', + rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']', + rsFitz = '\\ud83c[\\udffb-\\udfff]', + rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')', + rsNonAstral = '[^' + rsAstralRange + ']', + rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}', + rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]', + rsUpper = '[' + rsUpperRange + ']', + rsZWJ = '\\u200d'; + + /** Used to compose unicode regexes. */ + var rsLowerMisc = '(?:' + rsLower + '|' + rsMisc + ')', + rsUpperMisc = '(?:' + rsUpper + '|' + rsMisc + ')', + rsOptLowerContr = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?', + rsOptUpperContr = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?', + reOptMod = rsModifier + '?', + rsOptVar = '[' + rsVarRange + ']?', + rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*', + rsSeq = rsOptVar + reOptMod + rsOptJoin, + rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq, + rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')'; + + /** Used to match apostrophes. */ + var reApos = RegExp(rsApos, 'g'); + + /** + * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and + * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols). + */ + var reComboMark = RegExp(rsCombo, 'g'); + + /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */ + var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g'); + + /** Used to match complex or compound words. */ + var reUnicodeWord = RegExp([ + rsUpper + '?' + rsLower + '+' + rsOptLowerContr + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', + rsUpperMisc + '+' + rsOptUpperContr + '(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')', + rsUpper + '?' + rsLowerMisc + '+' + rsOptLowerContr, + rsUpper + '+' + rsOptUpperContr, + rsDigits, + rsEmoji + ].join('|'), 'g'); + + /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */ + var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']'); + + /** Used to detect strings that need a more robust regexp to match words. */ + var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; + + /** Used to assign default `context` object properties. */ + var contextProps = [ + 'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', + 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', + 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', + 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', + '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout' + ]; + + /** Used to make template sourceURLs easier to identify. */ + var templateCounter = -1; + + /** Used to identify `toStringTag` values of typed arrays. */ + var typedArrayTags = {}; + typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = + typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = + typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = + typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = + typedArrayTags[uint32Tag] = true; + typedArrayTags[argsTag] = typedArrayTags[arrayTag] = + typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = + typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = + typedArrayTags[errorTag] = typedArrayTags[funcTag] = + typedArrayTags[mapTag] = typedArrayTags[numberTag] = + typedArrayTags[objectTag] = typedArrayTags[regexpTag] = + typedArrayTags[setTag] = typedArrayTags[stringTag] = + typedArrayTags[weakMapTag] = false; + + /** Used to identify `toStringTag` values supported by `_.clone`. */ + var cloneableTags = {}; + cloneableTags[argsTag] = cloneableTags[arrayTag] = + cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = + cloneableTags[boolTag] = cloneableTags[dateTag] = + cloneableTags[float32Tag] = cloneableTags[float64Tag] = + cloneableTags[int8Tag] = cloneableTags[int16Tag] = + cloneableTags[int32Tag] = cloneableTags[mapTag] = + cloneableTags[numberTag] = cloneableTags[objectTag] = + cloneableTags[regexpTag] = cloneableTags[setTag] = + cloneableTags[stringTag] = cloneableTags[symbolTag] = + cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = + cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; + cloneableTags[errorTag] = cloneableTags[funcTag] = + cloneableTags[weakMapTag] = false; + + /** Used to map Latin Unicode letters to basic Latin letters. */ + var deburredLetters = { + // Latin-1 Supplement block. + '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A', + '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a', + '\xc7': 'C', '\xe7': 'c', + '\xd0': 'D', '\xf0': 'd', + '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E', + '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e', + '\xcc': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I', + '\xec': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i', + '\xd1': 'N', '\xf1': 'n', + '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O', + '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o', + '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U', + '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u', + '\xdd': 'Y', '\xfd': 'y', '\xff': 'y', + '\xc6': 'Ae', '\xe6': 'ae', + '\xde': 'Th', '\xfe': 'th', + '\xdf': 'ss', + // Latin Extended-A block. + '\u0100': 'A', '\u0102': 'A', '\u0104': 'A', + '\u0101': 'a', '\u0103': 'a', '\u0105': 'a', + '\u0106': 'C', '\u0108': 'C', '\u010a': 'C', '\u010c': 'C', + '\u0107': 'c', '\u0109': 'c', '\u010b': 'c', '\u010d': 'c', + '\u010e': 'D', '\u0110': 'D', '\u010f': 'd', '\u0111': 'd', + '\u0112': 'E', '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E', + '\u0113': 'e', '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e', + '\u011c': 'G', '\u011e': 'G', '\u0120': 'G', '\u0122': 'G', + '\u011d': 'g', '\u011f': 'g', '\u0121': 'g', '\u0123': 'g', + '\u0124': 'H', '\u0126': 'H', '\u0125': 'h', '\u0127': 'h', + '\u0128': 'I', '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I', + '\u0129': 'i', '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i', + '\u0134': 'J', '\u0135': 'j', + '\u0136': 'K', '\u0137': 'k', '\u0138': 'k', + '\u0139': 'L', '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L', + '\u013a': 'l', '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l', + '\u0143': 'N', '\u0145': 'N', '\u0147': 'N', '\u014a': 'N', + '\u0144': 'n', '\u0146': 'n', '\u0148': 'n', '\u014b': 'n', + '\u014c': 'O', '\u014e': 'O', '\u0150': 'O', + '\u014d': 'o', '\u014f': 'o', '\u0151': 'o', + '\u0154': 'R', '\u0156': 'R', '\u0158': 'R', + '\u0155': 'r', '\u0157': 'r', '\u0159': 'r', + '\u015a': 'S', '\u015c': 'S', '\u015e': 'S', '\u0160': 'S', + '\u015b': 's', '\u015d': 's', '\u015f': 's', '\u0161': 's', + '\u0162': 'T', '\u0164': 'T', '\u0166': 'T', + '\u0163': 't', '\u0165': 't', '\u0167': 't', + '\u0168': 'U', '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U', + '\u0169': 'u', '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u', + '\u0174': 'W', '\u0175': 'w', + '\u0176': 'Y', '\u0177': 'y', '\u0178': 'Y', + '\u0179': 'Z', '\u017b': 'Z', '\u017d': 'Z', + '\u017a': 'z', '\u017c': 'z', '\u017e': 'z', + '\u0132': 'IJ', '\u0133': 'ij', + '\u0152': 'Oe', '\u0153': 'oe', + '\u0149': "'n", '\u017f': 'ss' + }; + + /** Used to map characters to HTML entities. */ + var htmlEscapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + /** Used to map HTML entities to characters. */ + var htmlUnescapes = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': "'", + '`': '`' + }; + + /** Used to escape characters for inclusion in compiled string literals. */ + var stringEscapes = { + '\\': '\\', + "'": "'", + '\n': 'n', + '\r': 'r', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + /** Built-in method references without a dependency on `root`. */ + var freeParseFloat = parseFloat, + freeParseInt = parseInt; + + /** Detect free variable `global` from Node.js. */ + var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; + + /** Detect free variable `self`. */ + var freeSelf = typeof self == 'object' && self && self.Object === Object && self; + + /** Used as a reference to the global object. */ + var root = freeGlobal || freeSelf || Function('return this')(); + + /** Detect free variable `exports`. */ + var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; + + /** Detect free variable `module`. */ + var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; + + /** Detect the popular CommonJS extension `module.exports`. */ + var moduleExports = freeModule && freeModule.exports === freeExports; + + /** Detect free variable `process` from Node.js. */ + var freeProcess = moduleExports && freeGlobal.process; + + /** Used to access faster Node.js helpers. */ + var nodeUtil = (function() { + try { + return freeProcess && freeProcess.binding('util'); + } catch (e) {} + }()); + + /* Node.js helper references. */ + var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, + nodeIsDate = nodeUtil && nodeUtil.isDate, + nodeIsMap = nodeUtil && nodeUtil.isMap, + nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, + nodeIsSet = nodeUtil && nodeUtil.isSet, + nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; + + /*--------------------------------------------------------------------------*/ + + /** + * Adds the key-value `pair` to `map`. + * + * @private + * @param {Object} map The map to modify. + * @param {Array} pair The key-value pair to add. + * @returns {Object} Returns `map`. + */ + function addMapEntry(map, pair) { + // Don't return `map.set` because it's not chainable in IE 11. + map.set(pair[0], pair[1]); + return map; + } + + /** + * Adds `value` to `set`. + * + * @private + * @param {Object} set The set to modify. + * @param {*} value The value to add. + * @returns {Object} Returns `set`. + */ + function addSetEntry(set, value) { + // Don't return `set.add` because it's not chainable in IE 11. + set.add(value); + return set; + } + + /** + * A faster alternative to `Function#apply`, this function invokes `func` + * with the `this` binding of `thisArg` and the arguments of `args`. + * + * @private + * @param {Function} func The function to invoke. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} args The arguments to invoke `func` with. + * @returns {*} Returns the result of `func`. + */ + function apply(func, thisArg, args) { + switch (args.length) { + case 0: return func.call(thisArg); + case 1: return func.call(thisArg, args[0]); + case 2: return func.call(thisArg, args[0], args[1]); + case 3: return func.call(thisArg, args[0], args[1], args[2]); + } + return func.apply(thisArg, args); + } + + /** + * A specialized version of `baseAggregator` for arrays. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform keys. + * @param {Object} accumulator The initial aggregated object. + * @returns {Function} Returns `accumulator`. + */ + function arrayAggregator(array, setter, iteratee, accumulator) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + var value = array[index]; + setter(accumulator, value, iteratee(value), array); + } + return accumulator; + } + + /** + * A specialized version of `_.forEach` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEach(array, iteratee) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (iteratee(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * A specialized version of `_.forEachRight` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns `array`. + */ + function arrayEachRight(array, iteratee) { + var length = array ? array.length : 0; + + while (length--) { + if (iteratee(array[length], length, array) === false) { + break; + } + } + return array; + } + + /** + * A specialized version of `_.every` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + */ + function arrayEvery(array, predicate) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (!predicate(array[index], index, array)) { + return false; + } + } + return true; + } + + /** + * A specialized version of `_.filter` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function arrayFilter(array, predicate) { + var index = -1, + length = array ? array.length : 0, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * A specialized version of `_.includes` for arrays without support for + * specifying an index to search from. + * + * @private + * @param {Array} [array] The array to inspect. + * @param {*} target The value to search for. + * @returns {boolean} Returns `true` if `target` is found, else `false`. + */ + function arrayIncludes(array, value) { + var length = array ? array.length : 0; + return !!length && baseIndexOf(array, value, 0) > -1; + } + + /** + * This function is like `arrayIncludes` except that it accepts a comparator. + * + * @private + * @param {Array} [array] The array to inspect. + * @param {*} target The value to search for. + * @param {Function} comparator The comparator invoked per element. + * @returns {boolean} Returns `true` if `target` is found, else `false`. + */ + function arrayIncludesWith(array, value, comparator) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (comparator(value, array[index])) { + return true; + } + } + return false; + } + + /** + * A specialized version of `_.map` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function arrayMap(array, iteratee) { + var index = -1, + length = array ? array.length : 0, + result = Array(length); + + while (++index < length) { + result[index] = iteratee(array[index], index, array); + } + return result; + } + + /** + * Appends the elements of `values` to `array`. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to append. + * @returns {Array} Returns `array`. + */ + function arrayPush(array, values) { + var index = -1, + length = values.length, + offset = array.length; + + while (++index < length) { + array[offset + index] = values[index]; + } + return array; + } + + /** + * A specialized version of `_.reduce` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the first element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduce(array, iteratee, accumulator, initAccum) { + var index = -1, + length = array ? array.length : 0; + + if (initAccum && length) { + accumulator = array[++index]; + } + while (++index < length) { + accumulator = iteratee(accumulator, array[index], index, array); + } + return accumulator; + } + + /** + * A specialized version of `_.reduceRight` for arrays without support for + * iteratee shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @param {boolean} [initAccum] Specify using the last element of `array` as + * the initial value. + * @returns {*} Returns the accumulated value. + */ + function arrayReduceRight(array, iteratee, accumulator, initAccum) { + var length = array ? array.length : 0; + if (initAccum && length) { + accumulator = array[--length]; + } + while (length--) { + accumulator = iteratee(accumulator, array[length], length, array); + } + return accumulator; + } + + /** + * A specialized version of `_.some` for arrays without support for iteratee + * shorthands. + * + * @private + * @param {Array} [array] The array to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function arraySome(array, predicate) { + var index = -1, + length = array ? array.length : 0; + + while (++index < length) { + if (predicate(array[index], index, array)) { + return true; + } + } + return false; + } + + /** + * Gets the size of an ASCII `string`. + * + * @private + * @param {string} string The string inspect. + * @returns {number} Returns the string size. + */ + var asciiSize = baseProperty('length'); + + /** + * Converts an ASCII `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function asciiToArray(string) { + return string.split(''); + } + + /** + * Splits an ASCII `string` into an array of its words. + * + * @private + * @param {string} The string to inspect. + * @returns {Array} Returns the words of `string`. + */ + function asciiWords(string) { + return string.match(reAsciiWord) || []; + } + + /** + * The base implementation of methods like `_.findKey` and `_.findLastKey`, + * without support for iteratee shorthands, which iterates over `collection` + * using `eachFunc`. + * + * @private + * @param {Array|Object} collection The collection to inspect. + * @param {Function} predicate The function invoked per iteration. + * @param {Function} eachFunc The function to iterate over `collection`. + * @returns {*} Returns the found element or its key, else `undefined`. + */ + function baseFindKey(collection, predicate, eachFunc) { + var result; + eachFunc(collection, function(value, key, collection) { + if (predicate(value, key, collection)) { + result = key; + return false; + } + }); + return result; + } + + /** + * The base implementation of `_.findIndex` and `_.findLastIndex` without + * support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} predicate The function invoked per iteration. + * @param {number} fromIndex The index to search from. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseFindIndex(array, predicate, fromIndex, fromRight) { + var length = array.length, + index = fromIndex + (fromRight ? 1 : -1); + + while ((fromRight ? index-- : ++index < length)) { + if (predicate(array[index], index, array)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.indexOf` without `fromIndex` bounds checks. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOf(array, value, fromIndex) { + if (value !== value) { + return baseFindIndex(array, baseIsNaN, fromIndex); + } + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * This function is like `baseIndexOf` except that it accepts a comparator. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @param {Function} comparator The comparator invoked per element. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function baseIndexOfWith(array, value, fromIndex, comparator) { + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (comparator(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * The base implementation of `_.isNaN` without support for number objects. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + */ + function baseIsNaN(value) { + return value !== value; + } + + /** + * The base implementation of `_.mean` and `_.meanBy` without support for + * iteratee shorthands. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {number} Returns the mean. + */ + function baseMean(array, iteratee) { + var length = array ? array.length : 0; + return length ? (baseSum(array, iteratee) / length) : NAN; + } + + /** + * The base implementation of `_.property` without support for deep paths. + * + * @private + * @param {string} key The key of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function baseProperty(key) { + return function(object) { + return object == null ? undefined : object[key]; + }; + } + + /** + * The base implementation of `_.propertyOf` without support for deep paths. + * + * @private + * @param {Object} object The object to query. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyOf(object) { + return function(key) { + return object == null ? undefined : object[key]; + }; + } + + /** + * The base implementation of `_.reduce` and `_.reduceRight`, without support + * for iteratee shorthands, which iterates over `collection` using `eachFunc`. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {*} accumulator The initial value. + * @param {boolean} initAccum Specify using the first or last element of + * `collection` as the initial value. + * @param {Function} eachFunc The function to iterate over `collection`. + * @returns {*} Returns the accumulated value. + */ + function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { + eachFunc(collection, function(value, index, collection) { + accumulator = initAccum + ? (initAccum = false, value) + : iteratee(accumulator, value, index, collection); + }); + return accumulator; + } + + /** + * The base implementation of `_.sortBy` which uses `comparer` to define the + * sort order of `array` and replaces criteria objects with their corresponding + * values. + * + * @private + * @param {Array} array The array to sort. + * @param {Function} comparer The function to define sort order. + * @returns {Array} Returns `array`. + */ + function baseSortBy(array, comparer) { + var length = array.length; + + array.sort(comparer); + while (length--) { + array[length] = array[length].value; + } + return array; + } + + /** + * The base implementation of `_.sum` and `_.sumBy` without support for + * iteratee shorthands. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {number} Returns the sum. + */ + function baseSum(array, iteratee) { + var result, + index = -1, + length = array.length; + + while (++index < length) { + var current = iteratee(array[index]); + if (current !== undefined) { + result = result === undefined ? current : (result + current); + } + } + return result; + } + + /** + * The base implementation of `_.times` without support for iteratee shorthands + * or max array length checks. + * + * @private + * @param {number} n The number of times to invoke `iteratee`. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the array of results. + */ + function baseTimes(n, iteratee) { + var index = -1, + result = Array(n); + + while (++index < n) { + result[index] = iteratee(index); + } + return result; + } + + /** + * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array + * of key-value pairs for `object` corresponding to the property names of `props`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} props The property names to get values for. + * @returns {Object} Returns the key-value pairs. + */ + function baseToPairs(object, props) { + return arrayMap(props, function(key) { + return [key, object[key]]; + }); + } + + /** + * The base implementation of `_.unary` without support for storing metadata. + * + * @private + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + */ + function baseUnary(func) { + return function(value) { + return func(value); + }; + } + + /** + * The base implementation of `_.values` and `_.valuesIn` which creates an + * array of `object` property values corresponding to the property names + * of `props`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} props The property names to get values for. + * @returns {Object} Returns the array of property values. + */ + function baseValues(object, props) { + return arrayMap(props, function(key) { + return object[key]; + }); + } + + /** + * Checks if a cache value for `key` exists. + * + * @private + * @param {Object} cache The cache to query. + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function cacheHas(cache, key) { + return cache.has(key); + } + + /** + * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the first unmatched string symbol. + */ + function charsStartIndex(strSymbols, chrSymbols) { + var index = -1, + length = strSymbols.length; + + while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol + * that is not found in the character symbols. + * + * @private + * @param {Array} strSymbols The string symbols to inspect. + * @param {Array} chrSymbols The character symbols to find. + * @returns {number} Returns the index of the last unmatched string symbol. + */ + function charsEndIndex(strSymbols, chrSymbols) { + var index = strSymbols.length; + + while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {} + return index; + } + + /** + * Gets the number of `placeholder` occurrences in `array`. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} placeholder The placeholder to search for. + * @returns {number} Returns the placeholder count. + */ + function countHolders(array, placeholder) { + var length = array.length, + result = 0; + + while (length--) { + if (array[length] === placeholder) { + result++; + } + } + return result; + } + + /** + * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A + * letters to basic Latin letters. + * + * @private + * @param {string} letter The matched letter to deburr. + * @returns {string} Returns the deburred letter. + */ + var deburrLetter = basePropertyOf(deburredLetters); + + /** + * Used by `_.escape` to convert characters to HTML entities. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + var escapeHtmlChar = basePropertyOf(htmlEscapes); + + /** + * Used by `_.template` to escape characters for inclusion in compiled string literals. + * + * @private + * @param {string} chr The matched character to escape. + * @returns {string} Returns the escaped character. + */ + function escapeStringChar(chr) { + return '\\' + stringEscapes[chr]; + } + + /** + * Gets the value at `key` of `object`. + * + * @private + * @param {Object} [object] The object to query. + * @param {string} key The key of the property to get. + * @returns {*} Returns the property value. + */ + function getValue(object, key) { + return object == null ? undefined : object[key]; + } + + /** + * Checks if `string` contains Unicode symbols. + * + * @private + * @param {string} string The string to inspect. + * @returns {boolean} Returns `true` if a symbol is found, else `false`. + */ + function hasUnicode(string) { + return reHasUnicode.test(string); + } + + /** + * Checks if `string` contains a word composed of Unicode symbols. + * + * @private + * @param {string} string The string to inspect. + * @returns {boolean} Returns `true` if a word is found, else `false`. + */ + function hasUnicodeWord(string) { + return reHasUnicodeWord.test(string); + } + + /** + * Checks if `value` is a host object in IE < 9. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a host object, else `false`. + */ + function isHostObject(value) { + // Many host objects are `Object` objects that can coerce to strings + // despite having improperly defined `toString` methods. + var result = false; + if (value != null && typeof value.toString != 'function') { + try { + result = !!(value + ''); + } catch (e) {} + } + return result; + } + + /** + * Converts `iterator` to an array. + * + * @private + * @param {Object} iterator The iterator to convert. + * @returns {Array} Returns the converted array. + */ + function iteratorToArray(iterator) { + var data, + result = []; + + while (!(data = iterator.next()).done) { + result.push(data.value); + } + return result; + } + + /** + * Converts `map` to its key-value pairs. + * + * @private + * @param {Object} map The map to convert. + * @returns {Array} Returns the key-value pairs. + */ + function mapToArray(map) { + var index = -1, + result = Array(map.size); + + map.forEach(function(value, key) { + result[++index] = [key, value]; + }); + return result; + } + + /** + * Creates a unary function that invokes `func` with its argument transformed. + * + * @private + * @param {Function} func The function to wrap. + * @param {Function} transform The argument transform. + * @returns {Function} Returns the new function. + */ + function overArg(func, transform) { + return function(arg) { + return func(transform(arg)); + }; + } + + /** + * Replaces all `placeholder` elements in `array` with an internal placeholder + * and returns an array of their indexes. + * + * @private + * @param {Array} array The array to modify. + * @param {*} placeholder The placeholder to replace. + * @returns {Array} Returns the new array of placeholder indexes. + */ + function replaceHolders(array, placeholder) { + var index = -1, + length = array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value === placeholder || value === PLACEHOLDER) { + array[index] = PLACEHOLDER; + result[resIndex++] = index; + } + } + return result; + } + + /** + * Converts `set` to an array of its values. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the values. + */ + function setToArray(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = value; + }); + return result; + } + + /** + * Converts `set` to its value-value pairs. + * + * @private + * @param {Object} set The set to convert. + * @returns {Array} Returns the value-value pairs. + */ + function setToPairs(set) { + var index = -1, + result = Array(set.size); + + set.forEach(function(value) { + result[++index] = [value, value]; + }); + return result; + } + + /** + * Gets the number of symbols in `string`. + * + * @private + * @param {string} string The string to inspect. + * @returns {number} Returns the string size. + */ + function stringSize(string) { + return hasUnicode(string) + ? unicodeSize(string) + : asciiSize(string); + } + + /** + * Converts `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function stringToArray(string) { + return hasUnicode(string) + ? unicodeToArray(string) + : asciiToArray(string); + } + + /** + * Used by `_.unescape` to convert HTML entities to characters. + * + * @private + * @param {string} chr The matched character to unescape. + * @returns {string} Returns the unescaped character. + */ + var unescapeHtmlChar = basePropertyOf(htmlUnescapes); + + /** + * Gets the size of a Unicode `string`. + * + * @private + * @param {string} string The string inspect. + * @returns {number} Returns the string size. + */ + function unicodeSize(string) { + var result = reUnicode.lastIndex = 0; + while (reUnicode.test(string)) { + result++; + } + return result; + } + + /** + * Converts a Unicode `string` to an array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the converted array. + */ + function unicodeToArray(string) { + return string.match(reUnicode) || []; + } + + /** + * Splits a Unicode `string` into an array of its words. + * + * @private + * @param {string} The string to inspect. + * @returns {Array} Returns the words of `string`. + */ + function unicodeWords(string) { + return string.match(reUnicodeWord) || []; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Create a new pristine `lodash` function using the `context` object. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Util + * @param {Object} [context=root] The context object. + * @returns {Function} Returns a new `lodash` function. + * @example + * + * _.mixin({ 'foo': _.constant('foo') }); + * + * var lodash = _.runInContext(); + * lodash.mixin({ 'bar': lodash.constant('bar') }); + * + * _.isFunction(_.foo); + * // => true + * _.isFunction(_.bar); + * // => false + * + * lodash.isFunction(lodash.foo); + * // => false + * lodash.isFunction(lodash.bar); + * // => true + * + * // Use `context` to stub `Date#getTime` use in `_.now`. + * var stubbed = _.runInContext({ + * 'Date': function() { + * return { 'getTime': stubGetTime }; + * } + * }); + * + * // Create a suped-up `defer` in Node.js. + * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer; + */ + function runInContext(context) { + context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root; + + /** Built-in constructor references. */ + var Array = context.Array, + Date = context.Date, + Error = context.Error, + Function = context.Function, + Math = context.Math, + Object = context.Object, + RegExp = context.RegExp, + String = context.String, + TypeError = context.TypeError; + + /** Used for built-in method references. */ + var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype; + + /** Used to detect overreaching core-js shims. */ + var coreJsData = context['__core-js_shared__']; + + /** Used to detect methods masquerading as native. */ + var maskSrcKey = (function() { + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); + return uid ? ('Symbol(src)_1.' + uid) : ''; + }()); + + /** Used to resolve the decompiled source of functions. */ + var funcToString = funcProto.toString; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + + /** Used to generate unique IDs. */ + var idCounter = 0; + + /** Used to infer the `Object` constructor. */ + var objectCtorString = funcToString.call(Object); + + /** + * Used to resolve the + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) + * of values. + */ + var objectToString = objectProto.toString; + + /** Used to restore the original `_` reference in `_.noConflict`. */ + var oldDash = root._; + + /** Used to detect if a method is native. */ + var reIsNative = RegExp('^' + + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' + ); + + /** Built-in value references. */ + var Buffer = moduleExports ? context.Buffer : undefined, + Symbol = context.Symbol, + Uint8Array = context.Uint8Array, + getPrototype = overArg(Object.getPrototypeOf, Object), + iteratorSymbol = Symbol ? Symbol.iterator : undefined, + objectCreate = Object.create, + propertyIsEnumerable = objectProto.propertyIsEnumerable, + splice = arrayProto.splice, + spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined; + + /** Mocked built-ins. */ + var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout, + ctxNow = Date && Date.now !== root.Date.now && Date.now, + ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout; + + /* Built-in method references for those with the same name as other `lodash` methods. */ + var nativeCeil = Math.ceil, + nativeFloor = Math.floor, + nativeGetSymbols = Object.getOwnPropertySymbols, + nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, + nativeIsFinite = context.isFinite, + nativeJoin = arrayProto.join, + nativeKeys = overArg(Object.keys, Object), + nativeMax = Math.max, + nativeMin = Math.min, + nativeParseInt = context.parseInt, + nativeRandom = Math.random, + nativeReverse = arrayProto.reverse; + + /* Built-in method references that are verified to be native. */ + var DataView = getNative(context, 'DataView'), + Map = getNative(context, 'Map'), + Promise = getNative(context, 'Promise'), + Set = getNative(context, 'Set'), + WeakMap = getNative(context, 'WeakMap'), + nativeCreate = getNative(Object, 'create'); + + /* Used to set `toString` methods. */ + var defineProperty = (function() { + var func = getNative(Object, 'defineProperty'), + name = getNative.name; + + return (name && name.length > 2) ? func : undefined; + }()); + + /** Used to store function metadata. */ + var metaMap = WeakMap && new WeakMap; + + /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */ + var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf'); + + /** Used to lookup unminified function names. */ + var realNames = {}; + + /** Used to detect maps, sets, and weakmaps. */ + var dataViewCtorString = toSource(DataView), + mapCtorString = toSource(Map), + promiseCtorString = toSource(Promise), + setCtorString = toSource(Set), + weakMapCtorString = toSource(WeakMap); + + /** Used to convert symbols to primitives and strings. */ + var symbolProto = Symbol ? Symbol.prototype : undefined, + symbolValueOf = symbolProto ? symbolProto.valueOf : undefined, + symbolToString = symbolProto ? symbolProto.toString : undefined; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a `lodash` object which wraps `value` to enable implicit method + * chain sequences. Methods that operate on and return arrays, collections, + * and functions can be chained together. Methods that retrieve a single value + * or may return a primitive value will automatically end the chain sequence + * and return the unwrapped value. Otherwise, the value must be unwrapped + * with `_#value`. + * + * Explicit chain sequences, which must be unwrapped with `_#value`, may be + * enabled using `_.chain`. + * + * The execution of chained methods is lazy, that is, it's deferred until + * `_#value` is implicitly or explicitly called. + * + * Lazy evaluation allows several methods to support shortcut fusion. + * Shortcut fusion is an optimization to merge iteratee calls; this avoids + * the creation of intermediate arrays and can greatly reduce the number of + * iteratee executions. Sections of a chain sequence qualify for shortcut + * fusion if the section is applied to an array of at least `200` elements + * and any iteratees accept only one argument. The heuristic for whether a + * section qualifies for shortcut fusion is subject to change. + * + * Chaining is supported in custom builds as long as the `_#value` method is + * directly or indirectly included in the build. + * + * In addition to lodash methods, wrappers have `Array` and `String` methods. + * + * The wrapper `Array` methods are: + * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` + * + * The wrapper `String` methods are: + * `replace` and `split` + * + * The wrapper methods that support shortcut fusion are: + * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, + * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, + * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` + * + * The chainable wrapper methods are: + * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, + * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, + * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, + * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, + * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, + * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, + * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, + * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, + * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, + * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, + * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, + * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, + * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, + * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, + * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, + * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, + * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, + * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, + * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, + * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, + * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, + * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, + * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, + * `zipObject`, `zipObjectDeep`, and `zipWith` + * + * The wrapper methods that are **not** chainable by default are: + * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, + * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`, + * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`, + * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`, + * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`, + * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`, + * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`, + * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, + * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, + * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, + * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, + * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, + * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`, + * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`, + * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, + * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`, + * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`, + * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`, + * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`, + * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`, + * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`, + * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`, + * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`, + * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`, + * `upperFirst`, `value`, and `words` + * + * @name _ + * @constructor + * @category Seq + * @param {*} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * function square(n) { + * return n * n; + * } + * + * var wrapped = _([1, 2, 3]); + * + * // Returns an unwrapped value. + * wrapped.reduce(_.add); + * // => 6 + * + * // Returns a wrapped value. + * var squares = wrapped.map(square); + * + * _.isArray(squares); + * // => false + * + * _.isArray(squares.value()); + * // => true + */ + function lodash(value) { + if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) { + if (value instanceof LodashWrapper) { + return value; + } + if (hasOwnProperty.call(value, '__wrapped__')) { + return wrapperClone(value); + } + } + return new LodashWrapper(value); + } + + /** + * The function whose prototype chain sequence wrappers inherit from. + * + * @private + */ + function baseLodash() { + // No operation performed. + } + + /** + * The base constructor for creating `lodash` wrapper objects. + * + * @private + * @param {*} value The value to wrap. + * @param {boolean} [chainAll] Enable explicit method chain sequences. + */ + function LodashWrapper(value, chainAll) { + this.__wrapped__ = value; + this.__actions__ = []; + this.__chain__ = !!chainAll; + this.__index__ = 0; + this.__values__ = undefined; + } + + /** + * By default, the template delimiters used by lodash are like those in + * embedded Ruby (ERB). Change the following template settings to use + * alternative delimiters. + * + * @static + * @memberOf _ + * @type {Object} + */ + lodash.templateSettings = { + + /** + * Used to detect `data` property values to be HTML-escaped. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'escape': reEscape, + + /** + * Used to detect code to be evaluated. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'evaluate': reEvaluate, + + /** + * Used to detect `data` property values to inject. + * + * @memberOf _.templateSettings + * @type {RegExp} + */ + 'interpolate': reInterpolate, + + /** + * Used to reference the data object in the template text. + * + * @memberOf _.templateSettings + * @type {string} + */ + 'variable': '', + + /** + * Used to import variables into the compiled template. + * + * @memberOf _.templateSettings + * @type {Object} + */ + 'imports': { + + /** + * A reference to the `lodash` function. + * + * @memberOf _.templateSettings.imports + * @type {Function} + */ + '_': lodash + } + }; + + // Ensure wrappers are instances of `baseLodash`. + lodash.prototype = baseLodash.prototype; + lodash.prototype.constructor = lodash; + + LodashWrapper.prototype = baseCreate(baseLodash.prototype); + LodashWrapper.prototype.constructor = LodashWrapper; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation. + * + * @private + * @constructor + * @param {*} value The value to wrap. + */ + function LazyWrapper(value) { + this.__wrapped__ = value; + this.__actions__ = []; + this.__dir__ = 1; + this.__filtered__ = false; + this.__iteratees__ = []; + this.__takeCount__ = MAX_ARRAY_LENGTH; + this.__views__ = []; + } + + /** + * Creates a clone of the lazy wrapper object. + * + * @private + * @name clone + * @memberOf LazyWrapper + * @returns {Object} Returns the cloned `LazyWrapper` object. + */ + function lazyClone() { + var result = new LazyWrapper(this.__wrapped__); + result.__actions__ = copyArray(this.__actions__); + result.__dir__ = this.__dir__; + result.__filtered__ = this.__filtered__; + result.__iteratees__ = copyArray(this.__iteratees__); + result.__takeCount__ = this.__takeCount__; + result.__views__ = copyArray(this.__views__); + return result; + } + + /** + * Reverses the direction of lazy iteration. + * + * @private + * @name reverse + * @memberOf LazyWrapper + * @returns {Object} Returns the new reversed `LazyWrapper` object. + */ + function lazyReverse() { + if (this.__filtered__) { + var result = new LazyWrapper(this); + result.__dir__ = -1; + result.__filtered__ = true; + } else { + result = this.clone(); + result.__dir__ *= -1; + } + return result; + } + + /** + * Extracts the unwrapped value from its lazy wrapper. + * + * @private + * @name value + * @memberOf LazyWrapper + * @returns {*} Returns the unwrapped value. + */ + function lazyValue() { + var array = this.__wrapped__.value(), + dir = this.__dir__, + isArr = isArray(array), + isRight = dir < 0, + arrLength = isArr ? array.length : 0, + view = getView(0, arrLength, this.__views__), + start = view.start, + end = view.end, + length = end - start, + index = isRight ? end : (start - 1), + iteratees = this.__iteratees__, + iterLength = iteratees.length, + resIndex = 0, + takeCount = nativeMin(length, this.__takeCount__); + + if (!isArr || arrLength < LARGE_ARRAY_SIZE || + (arrLength == length && takeCount == length)) { + return baseWrapperValue(array, this.__actions__); + } + var result = []; + + outer: + while (length-- && resIndex < takeCount) { + index += dir; + + var iterIndex = -1, + value = array[index]; + + while (++iterIndex < iterLength) { + var data = iteratees[iterIndex], + iteratee = data.iteratee, + type = data.type, + computed = iteratee(value); + + if (type == LAZY_MAP_FLAG) { + value = computed; + } else if (!computed) { + if (type == LAZY_FILTER_FLAG) { + continue outer; + } else { + break outer; + } + } + } + result[resIndex++] = value; + } + return result; + } + + // Ensure `LazyWrapper` is an instance of `baseLodash`. + LazyWrapper.prototype = baseCreate(baseLodash.prototype); + LazyWrapper.prototype.constructor = LazyWrapper; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a hash object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Hash(entries) { + var index = -1, + length = entries ? entries.length : 0; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the hash. + * + * @private + * @name clear + * @memberOf Hash + */ + function hashClear() { + this.__data__ = nativeCreate ? nativeCreate(null) : {}; + } + + /** + * Removes `key` and its value from the hash. + * + * @private + * @name delete + * @memberOf Hash + * @param {Object} hash The hash to modify. + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function hashDelete(key) { + return this.has(key) && delete this.__data__[key]; + } + + /** + * Gets the hash value for `key`. + * + * @private + * @name get + * @memberOf Hash + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function hashGet(key) { + var data = this.__data__; + if (nativeCreate) { + var result = data[key]; + return result === HASH_UNDEFINED ? undefined : result; + } + return hasOwnProperty.call(data, key) ? data[key] : undefined; + } + + /** + * Checks if a hash value for `key` exists. + * + * @private + * @name has + * @memberOf Hash + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function hashHas(key) { + var data = this.__data__; + return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); + } + + /** + * Sets the hash `key` to `value`. + * + * @private + * @name set + * @memberOf Hash + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the hash instance. + */ + function hashSet(key, value) { + var data = this.__data__; + data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; + return this; + } + + // Add methods to `Hash`. + Hash.prototype.clear = hashClear; + Hash.prototype['delete'] = hashDelete; + Hash.prototype.get = hashGet; + Hash.prototype.has = hashHas; + Hash.prototype.set = hashSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates an list cache object. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function ListCache(entries) { + var index = -1, + length = entries ? entries.length : 0; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the list cache. + * + * @private + * @name clear + * @memberOf ListCache + */ + function listCacheClear() { + this.__data__ = []; + } + + /** + * Removes `key` and its value from the list cache. + * + * @private + * @name delete + * @memberOf ListCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function listCacheDelete(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + return false; + } + var lastIndex = data.length - 1; + if (index == lastIndex) { + data.pop(); + } else { + splice.call(data, index, 1); + } + return true; + } + + /** + * Gets the list cache value for `key`. + * + * @private + * @name get + * @memberOf ListCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function listCacheGet(key) { + var data = this.__data__, + index = assocIndexOf(data, key); + + return index < 0 ? undefined : data[index][1]; + } + + /** + * Checks if a list cache value for `key` exists. + * + * @private + * @name has + * @memberOf ListCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function listCacheHas(key) { + return assocIndexOf(this.__data__, key) > -1; + } + + /** + * Sets the list cache `key` to `value`. + * + * @private + * @name set + * @memberOf ListCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the list cache instance. + */ + function listCacheSet(key, value) { + var data = this.__data__, + index = assocIndexOf(data, key); + + if (index < 0) { + data.push([key, value]); + } else { + data[index][1] = value; + } + return this; + } + + // Add methods to `ListCache`. + ListCache.prototype.clear = listCacheClear; + ListCache.prototype['delete'] = listCacheDelete; + ListCache.prototype.get = listCacheGet; + ListCache.prototype.has = listCacheHas; + ListCache.prototype.set = listCacheSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a map cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function MapCache(entries) { + var index = -1, + length = entries ? entries.length : 0; + + this.clear(); + while (++index < length) { + var entry = entries[index]; + this.set(entry[0], entry[1]); + } + } + + /** + * Removes all key-value entries from the map. + * + * @private + * @name clear + * @memberOf MapCache + */ + function mapCacheClear() { + this.__data__ = { + 'hash': new Hash, + 'map': new (Map || ListCache), + 'string': new Hash + }; + } + + /** + * Removes `key` and its value from the map. + * + * @private + * @name delete + * @memberOf MapCache + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function mapCacheDelete(key) { + return getMapData(this, key)['delete'](key); + } + + /** + * Gets the map value for `key`. + * + * @private + * @name get + * @memberOf MapCache + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function mapCacheGet(key) { + return getMapData(this, key).get(key); + } + + /** + * Checks if a map value for `key` exists. + * + * @private + * @name has + * @memberOf MapCache + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function mapCacheHas(key) { + return getMapData(this, key).has(key); + } + + /** + * Sets the map `key` to `value`. + * + * @private + * @name set + * @memberOf MapCache + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the map cache instance. + */ + function mapCacheSet(key, value) { + getMapData(this, key).set(key, value); + return this; + } + + // Add methods to `MapCache`. + MapCache.prototype.clear = mapCacheClear; + MapCache.prototype['delete'] = mapCacheDelete; + MapCache.prototype.get = mapCacheGet; + MapCache.prototype.has = mapCacheHas; + MapCache.prototype.set = mapCacheSet; + + /*------------------------------------------------------------------------*/ + + /** + * + * Creates an array cache object to store unique values. + * + * @private + * @constructor + * @param {Array} [values] The values to cache. + */ + function SetCache(values) { + var index = -1, + length = values ? values.length : 0; + + this.__data__ = new MapCache; + while (++index < length) { + this.add(values[index]); + } + } + + /** + * Adds `value` to the array cache. + * + * @private + * @name add + * @memberOf SetCache + * @alias push + * @param {*} value The value to cache. + * @returns {Object} Returns the cache instance. + */ + function setCacheAdd(value) { + this.__data__.set(value, HASH_UNDEFINED); + return this; + } + + /** + * Checks if `value` is in the array cache. + * + * @private + * @name has + * @memberOf SetCache + * @param {*} value The value to search for. + * @returns {number} Returns `true` if `value` is found, else `false`. + */ + function setCacheHas(value) { + return this.__data__.has(value); + } + + // Add methods to `SetCache`. + SetCache.prototype.add = SetCache.prototype.push = setCacheAdd; + SetCache.prototype.has = setCacheHas; + + /*------------------------------------------------------------------------*/ + + /** + * Creates a stack cache object to store key-value pairs. + * + * @private + * @constructor + * @param {Array} [entries] The key-value pairs to cache. + */ + function Stack(entries) { + this.__data__ = new ListCache(entries); + } + + /** + * Removes all key-value entries from the stack. + * + * @private + * @name clear + * @memberOf Stack + */ + function stackClear() { + this.__data__ = new ListCache; + } + + /** + * Removes `key` and its value from the stack. + * + * @private + * @name delete + * @memberOf Stack + * @param {string} key The key of the value to remove. + * @returns {boolean} Returns `true` if the entry was removed, else `false`. + */ + function stackDelete(key) { + return this.__data__['delete'](key); + } + + /** + * Gets the stack value for `key`. + * + * @private + * @name get + * @memberOf Stack + * @param {string} key The key of the value to get. + * @returns {*} Returns the entry value. + */ + function stackGet(key) { + return this.__data__.get(key); + } + + /** + * Checks if a stack value for `key` exists. + * + * @private + * @name has + * @memberOf Stack + * @param {string} key The key of the entry to check. + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + */ + function stackHas(key) { + return this.__data__.has(key); + } + + /** + * Sets the stack `key` to `value`. + * + * @private + * @name set + * @memberOf Stack + * @param {string} key The key of the value to set. + * @param {*} value The value to set. + * @returns {Object} Returns the stack cache instance. + */ + function stackSet(key, value) { + var cache = this.__data__; + if (cache instanceof ListCache) { + var pairs = cache.__data__; + if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) { + pairs.push([key, value]); + return this; + } + cache = this.__data__ = new MapCache(pairs); + } + cache.set(key, value); + return this; + } + + // Add methods to `Stack`. + Stack.prototype.clear = stackClear; + Stack.prototype['delete'] = stackDelete; + Stack.prototype.get = stackGet; + Stack.prototype.has = stackHas; + Stack.prototype.set = stackSet; + + /*------------------------------------------------------------------------*/ + + /** + * Creates an array of the enumerable property names of the array-like `value`. + * + * @private + * @param {*} value The value to query. + * @param {boolean} inherited Specify returning inherited property names. + * @returns {Array} Returns the array of property names. + */ + function arrayLikeKeys(value, inherited) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + // Safari 9 makes `arguments.length` enumerable in strict mode. + var result = (isArray(value) || isArguments(value)) + ? baseTimes(value.length, String) + : []; + + var length = result.length, + skipIndexes = !!length; + + for (var key in value) { + if ((inherited || hasOwnProperty.call(value, key)) && + !(skipIndexes && (key == 'length' || isIndex(key, length)))) { + result.push(key); + } + } + return result; + } + + /** + * Used by `_.defaults` to customize its `_.assignIn` use. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to assign. + * @param {Object} object The parent object of `objValue`. + * @returns {*} Returns the value to assign. + */ + function assignInDefaults(objValue, srcValue, key, object) { + if (objValue === undefined || + (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { + return srcValue; + } + return objValue; + } + + /** + * This function is like `assignValue` except that it doesn't assign + * `undefined` values. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignMergeValue(object, key, value) { + if ((value !== undefined && !eq(object[key], value)) || + (typeof key == 'number' && value === undefined && !(key in object))) { + object[key] = value; + } + } + + /** + * Assigns `value` to `key` of `object` if the existing value is not equivalent + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @private + * @param {Object} object The object to modify. + * @param {string} key The key of the property to assign. + * @param {*} value The value to assign. + */ + function assignValue(object, key, value) { + var objValue = object[key]; + if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || + (value === undefined && !(key in object))) { + object[key] = value; + } + } + + /** + * Gets the index at which the `key` is found in `array` of key-value pairs. + * + * @private + * @param {Array} array The array to inspect. + * @param {*} key The key to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + */ + function assocIndexOf(array, key) { + var length = array.length; + while (length--) { + if (eq(array[length][0], key)) { + return length; + } + } + return -1; + } + + /** + * Aggregates elements of `collection` on `accumulator` with keys transformed + * by `iteratee` and values set by `setter`. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform keys. + * @param {Object} accumulator The initial aggregated object. + * @returns {Function} Returns `accumulator`. + */ + function baseAggregator(collection, setter, iteratee, accumulator) { + baseEach(collection, function(value, key, collection) { + setter(accumulator, value, iteratee(value), collection); + }); + return accumulator; + } + + /** + * The base implementation of `_.assign` without support for multiple sources + * or `customizer` functions. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @returns {Object} Returns `object`. + */ + function baseAssign(object, source) { + return object && copyObject(source, keys(source), object); + } + + /** + * The base implementation of `_.at` without support for individual paths. + * + * @private + * @param {Object} object The object to iterate over. + * @param {string[]} paths The property paths of elements to pick. + * @returns {Array} Returns the picked elements. + */ + function baseAt(object, paths) { + var index = -1, + isNil = object == null, + length = paths.length, + result = Array(length); + + while (++index < length) { + result[index] = isNil ? undefined : get(object, paths[index]); + } + return result; + } + + /** + * The base implementation of `_.clamp` which doesn't coerce arguments. + * + * @private + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + */ + function baseClamp(number, lower, upper) { + if (number === number) { + if (upper !== undefined) { + number = number <= upper ? number : upper; + } + if (lower !== undefined) { + number = number >= lower ? number : lower; + } + } + return number; + } + + /** + * The base implementation of `_.clone` and `_.cloneDeep` which tracks + * traversed objects. + * + * @private + * @param {*} value The value to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @param {boolean} [isFull] Specify a clone including symbols. + * @param {Function} [customizer] The function to customize cloning. + * @param {string} [key] The key of `value`. + * @param {Object} [object] The parent object of `value`. + * @param {Object} [stack] Tracks traversed objects and their clone counterparts. + * @returns {*} Returns the cloned value. + */ + function baseClone(value, isDeep, isFull, customizer, key, object, stack) { + var result; + if (customizer) { + result = object ? customizer(value, key, object, stack) : customizer(value); + } + if (result !== undefined) { + return result; + } + if (!isObject(value)) { + return value; + } + var isArr = isArray(value); + if (isArr) { + result = initCloneArray(value); + if (!isDeep) { + return copyArray(value, result); + } + } else { + var tag = getTag(value), + isFunc = tag == funcTag || tag == genTag; + + if (isBuffer(value)) { + return cloneBuffer(value, isDeep); + } + if (tag == objectTag || tag == argsTag || (isFunc && !object)) { + if (isHostObject(value)) { + return object ? value : {}; + } + result = initCloneObject(isFunc ? {} : value); + if (!isDeep) { + return copySymbols(value, baseAssign(result, value)); + } + } else { + if (!cloneableTags[tag]) { + return object ? value : {}; + } + result = initCloneByTag(value, tag, baseClone, isDeep); + } + } + // Check for circular references and return its corresponding clone. + stack || (stack = new Stack); + var stacked = stack.get(value); + if (stacked) { + return stacked; + } + stack.set(value, result); + + if (!isArr) { + var props = isFull ? getAllKeys(value) : keys(value); + } + arrayEach(props || value, function(subValue, key) { + if (props) { + key = subValue; + subValue = value[key]; + } + // Recursively populate clone (susceptible to call stack limits). + assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack)); + }); + return result; + } + + /** + * The base implementation of `_.conforms` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property predicates to conform to. + * @returns {Function} Returns the new spec function. + */ + function baseConforms(source) { + var props = keys(source); + return function(object) { + return baseConformsTo(object, source, props); + }; + } + + /** + * The base implementation of `_.conformsTo` which accepts `props` to check. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. + */ + function baseConformsTo(object, source, props) { + var length = props.length; + if (object == null) { + return !length; + } + object = Object(object); + while (length--) { + var key = props[length], + predicate = source[key], + value = object[key]; + + if ((value === undefined && !(key in object)) || !predicate(value)) { + return false; + } + } + return true; + } + + /** + * The base implementation of `_.create` without support for assigning + * properties to the created object. + * + * @private + * @param {Object} prototype The object to inherit from. + * @returns {Object} Returns the new object. + */ + function baseCreate(proto) { + return isObject(proto) ? objectCreate(proto) : {}; + } + + /** + * The base implementation of `_.delay` and `_.defer` which accepts `args` + * to provide to `func`. + * + * @private + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @param {Array} args The arguments to provide to `func`. + * @returns {number|Object} Returns the timer id or timeout object. + */ + function baseDelay(func, wait, args) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + return setTimeout(function() { func.apply(undefined, args); }, wait); + } + + /** + * The base implementation of methods like `_.difference` without support + * for excluding multiple arrays or iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Array} values The values to exclude. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + */ + function baseDifference(array, values, iteratee, comparator) { + var index = -1, + includes = arrayIncludes, + isCommon = true, + length = array.length, + result = [], + valuesLength = values.length; + + if (!length) { + return result; + } + if (iteratee) { + values = arrayMap(values, baseUnary(iteratee)); + } + if (comparator) { + includes = arrayIncludesWith; + isCommon = false; + } + else if (values.length >= LARGE_ARRAY_SIZE) { + includes = cacheHas; + isCommon = false; + values = new SetCache(values); + } + outer: + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var valuesIndex = valuesLength; + while (valuesIndex--) { + if (values[valuesIndex] === computed) { + continue outer; + } + } + result.push(value); + } + else if (!includes(values, computed, comparator)) { + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.forEach` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ + var baseEach = createBaseEach(baseForOwn); + + /** + * The base implementation of `_.forEachRight` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + */ + var baseEachRight = createBaseEach(baseForOwnRight, true); + + /** + * The base implementation of `_.every` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false` + */ + function baseEvery(collection, predicate) { + var result = true; + baseEach(collection, function(value, index, collection) { + result = !!predicate(value, index, collection); + return result; + }); + return result; + } + + /** + * The base implementation of methods like `_.max` and `_.min` which accepts a + * `comparator` to determine the extremum value. + * + * @private + * @param {Array} array The array to iterate over. + * @param {Function} iteratee The iteratee invoked per iteration. + * @param {Function} comparator The comparator used to compare values. + * @returns {*} Returns the extremum value. + */ + function baseExtremum(array, iteratee, comparator) { + var index = -1, + length = array.length; + + while (++index < length) { + var value = array[index], + current = iteratee(value); + + if (current != null && (computed === undefined + ? (current === current && !isSymbol(current)) + : comparator(current, computed) + )) { + var computed = current, + result = value; + } + } + return result; + } + + /** + * The base implementation of `_.fill` without an iteratee call guard. + * + * @private + * @param {Array} array The array to fill. + * @param {*} value The value to fill `array` with. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns `array`. + */ + function baseFill(array, value, start, end) { + var length = array.length; + + start = toInteger(start); + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = (end === undefined || end > length) ? length : toInteger(end); + if (end < 0) { + end += length; + } + end = start > end ? 0 : toLength(end); + while (start < end) { + array[start++] = value; + } + return array; + } + + /** + * The base implementation of `_.filter` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + */ + function baseFilter(collection, predicate) { + var result = []; + baseEach(collection, function(value, index, collection) { + if (predicate(value, index, collection)) { + result.push(value); + } + }); + return result; + } + + /** + * The base implementation of `_.flatten` with support for restricting flattening. + * + * @private + * @param {Array} array The array to flatten. + * @param {number} depth The maximum recursion depth. + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration. + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks. + * @param {Array} [result=[]] The initial result value. + * @returns {Array} Returns the new flattened array. + */ + function baseFlatten(array, depth, predicate, isStrict, result) { + var index = -1, + length = array.length; + + predicate || (predicate = isFlattenable); + result || (result = []); + + while (++index < length) { + var value = array[index]; + if (depth > 0 && predicate(value)) { + if (depth > 1) { + // Recursively flatten arrays (susceptible to call stack limits). + baseFlatten(value, depth - 1, predicate, isStrict, result); + } else { + arrayPush(result, value); + } + } else if (!isStrict) { + result[result.length] = value; + } + } + return result; + } + + /** + * The base implementation of `baseForOwn` which iterates over `object` + * properties returned by `keysFunc` and invokes `iteratee` for each property. + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseFor = createBaseFor(); + + /** + * This function is like `baseFor` except that it iterates over properties + * in the opposite order. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @param {Function} keysFunc The function to get the keys of `object`. + * @returns {Object} Returns `object`. + */ + var baseForRight = createBaseFor(true); + + /** + * The base implementation of `_.forOwn` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwn(object, iteratee) { + return object && baseFor(object, iteratee, keys); + } + + /** + * The base implementation of `_.forOwnRight` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Object} Returns `object`. + */ + function baseForOwnRight(object, iteratee) { + return object && baseForRight(object, iteratee, keys); + } + + /** + * The base implementation of `_.functions` which creates an array of + * `object` function property names filtered from `props`. + * + * @private + * @param {Object} object The object to inspect. + * @param {Array} props The property names to filter. + * @returns {Array} Returns the function names. + */ + function baseFunctions(object, props) { + return arrayFilter(props, function(key) { + return isFunction(object[key]); + }); + } + + /** + * The base implementation of `_.get` without support for default values. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @returns {*} Returns the resolved value. + */ + function baseGet(object, path) { + path = isKey(path, object) ? [path] : castPath(path); + + var index = 0, + length = path.length; + + while (object != null && index < length) { + object = object[toKey(path[index++])]; + } + return (index && index == length) ? object : undefined; + } + + /** + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses + * `keysFunc` and `symbolsFunc` to get the enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Function} keysFunc The function to get the keys of `object`. + * @param {Function} symbolsFunc The function to get the symbols of `object`. + * @returns {Array} Returns the array of property names and symbols. + */ + function baseGetAllKeys(object, keysFunc, symbolsFunc) { + var result = keysFunc(object); + return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); + } + + /** + * The base implementation of `getTag`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + function baseGetTag(value) { + return objectToString.call(value); + } + + /** + * The base implementation of `_.gt` which doesn't coerce arguments. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. + */ + function baseGt(value, other) { + return value > other; + } + + /** + * The base implementation of `_.has` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ + function baseHas(object, key) { + return object != null && hasOwnProperty.call(object, key); + } + + /** + * The base implementation of `_.hasIn` without support for deep paths. + * + * @private + * @param {Object} [object] The object to query. + * @param {Array|string} key The key to check. + * @returns {boolean} Returns `true` if `key` exists, else `false`. + */ + function baseHasIn(object, key) { + return object != null && key in Object(object); + } + + /** + * The base implementation of `_.inRange` which doesn't coerce arguments. + * + * @private + * @param {number} number The number to check. + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. + */ + function baseInRange(number, start, end) { + return number >= nativeMin(start, end) && number < nativeMax(start, end); + } + + /** + * The base implementation of methods like `_.intersection`, without support + * for iteratee shorthands, that accepts an array of arrays to inspect. + * + * @private + * @param {Array} arrays The arrays to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of shared values. + */ + function baseIntersection(arrays, iteratee, comparator) { + var includes = comparator ? arrayIncludesWith : arrayIncludes, + length = arrays[0].length, + othLength = arrays.length, + othIndex = othLength, + caches = Array(othLength), + maxLength = Infinity, + result = []; + + while (othIndex--) { + var array = arrays[othIndex]; + if (othIndex && iteratee) { + array = arrayMap(array, baseUnary(iteratee)); + } + maxLength = nativeMin(array.length, maxLength); + caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) + ? new SetCache(othIndex && array) + : undefined; + } + array = arrays[0]; + + var index = -1, + seen = caches[0]; + + outer: + while (++index < length && result.length < maxLength) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (!(seen + ? cacheHas(seen, computed) + : includes(result, computed, comparator) + )) { + othIndex = othLength; + while (--othIndex) { + var cache = caches[othIndex]; + if (!(cache + ? cacheHas(cache, computed) + : includes(arrays[othIndex], computed, comparator)) + ) { + continue outer; + } + } + if (seen) { + seen.push(computed); + } + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.invert` and `_.invertBy` which inverts + * `object` with values transformed by `iteratee` and set by `setter`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} setter The function to set `accumulator` values. + * @param {Function} iteratee The iteratee to transform values. + * @param {Object} accumulator The initial inverted object. + * @returns {Function} Returns `accumulator`. + */ + function baseInverter(object, setter, iteratee, accumulator) { + baseForOwn(object, function(value, key, object) { + setter(accumulator, iteratee(value), key, object); + }); + return accumulator; + } + + /** + * The base implementation of `_.invoke` without support for individual + * method arguments. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {Array} args The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + */ + function baseInvoke(object, path, args) { + if (!isKey(path, object)) { + path = castPath(path); + object = parent(object, path); + path = last(path); + } + var func = object == null ? object : object[toKey(path)]; + return func == null ? undefined : apply(func, object, args); + } + + /** + * The base implementation of `_.isArrayBuffer` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + */ + function baseIsArrayBuffer(value) { + return isObjectLike(value) && objectToString.call(value) == arrayBufferTag; + } + + /** + * The base implementation of `_.isDate` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + */ + function baseIsDate(value) { + return isObjectLike(value) && objectToString.call(value) == dateTag; + } + + /** + * The base implementation of `_.isEqual` which supports partial comparisons + * and tracks traversed objects. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparisons. + * @param {boolean} [bitmask] The bitmask of comparison flags. + * The bitmask may be composed of the following flags: + * 1 - Unordered comparison + * 2 - Partial comparison + * @param {Object} [stack] Tracks traversed `value` and `other` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + */ + function baseIsEqual(value, other, customizer, bitmask, stack) { + if (value === other) { + return true; + } + if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) { + return value !== value && other !== other; + } + return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack); + } + + /** + * A specialized version of `baseIsEqual` for arrays and objects which performs + * deep comparisons and tracks traversed objects enabling objects with circular + * references to be compared. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} [customizer] The function to customize comparisons. + * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual` + * for more details. + * @param {Object} [stack] Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) { + var objIsArr = isArray(object), + othIsArr = isArray(other), + objTag = arrayTag, + othTag = arrayTag; + + if (!objIsArr) { + objTag = getTag(object); + objTag = objTag == argsTag ? objectTag : objTag; + } + if (!othIsArr) { + othTag = getTag(other); + othTag = othTag == argsTag ? objectTag : othTag; + } + var objIsObj = objTag == objectTag && !isHostObject(object), + othIsObj = othTag == objectTag && !isHostObject(other), + isSameTag = objTag == othTag; + + if (isSameTag && !objIsObj) { + stack || (stack = new Stack); + return (objIsArr || isTypedArray(object)) + ? equalArrays(object, other, equalFunc, customizer, bitmask, stack) + : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack); + } + if (!(bitmask & PARTIAL_COMPARE_FLAG)) { + var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'), + othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__'); + + if (objIsWrapped || othIsWrapped) { + var objUnwrapped = objIsWrapped ? object.value() : object, + othUnwrapped = othIsWrapped ? other.value() : other; + + stack || (stack = new Stack); + return equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack); + } + } + if (!isSameTag) { + return false; + } + stack || (stack = new Stack); + return equalObjects(object, other, equalFunc, customizer, bitmask, stack); + } + + /** + * The base implementation of `_.isMap` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + */ + function baseIsMap(value) { + return isObjectLike(value) && getTag(value) == mapTag; + } + + /** + * The base implementation of `_.isMatch` without support for iteratee shorthands. + * + * @private + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Array} matchData The property names, values, and compare flags to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + */ + function baseIsMatch(object, source, matchData, customizer) { + var index = matchData.length, + length = index, + noCustomizer = !customizer; + + if (object == null) { + return !length; + } + object = Object(object); + while (index--) { + var data = matchData[index]; + if ((noCustomizer && data[2]) + ? data[1] !== object[data[0]] + : !(data[0] in object) + ) { + return false; + } + } + while (++index < length) { + data = matchData[index]; + var key = data[0], + objValue = object[key], + srcValue = data[1]; + + if (noCustomizer && data[2]) { + if (objValue === undefined && !(key in object)) { + return false; + } + } else { + var stack = new Stack; + if (customizer) { + var result = customizer(objValue, srcValue, key, object, source, stack); + } + if (!(result === undefined + ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack) + : result + )) { + return false; + } + } + } + return true; + } + + /** + * The base implementation of `_.isNative` without bad shim checks. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + */ + function baseIsNative(value) { + if (!isObject(value) || isMasked(value)) { + return false; + } + var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor; + return pattern.test(toSource(value)); + } + + /** + * The base implementation of `_.isRegExp` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + */ + function baseIsRegExp(value) { + return isObject(value) && objectToString.call(value) == regexpTag; + } + + /** + * The base implementation of `_.isSet` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + */ + function baseIsSet(value) { + return isObjectLike(value) && getTag(value) == setTag; + } + + /** + * The base implementation of `_.isTypedArray` without Node.js optimizations. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + */ + function baseIsTypedArray(value) { + return isObjectLike(value) && + isLength(value.length) && !!typedArrayTags[objectToString.call(value)]; + } + + /** + * The base implementation of `_.iteratee`. + * + * @private + * @param {*} [value=_.identity] The value to convert to an iteratee. + * @returns {Function} Returns the iteratee. + */ + function baseIteratee(value) { + // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9. + // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details. + if (typeof value == 'function') { + return value; + } + if (value == null) { + return identity; + } + if (typeof value == 'object') { + return isArray(value) + ? baseMatchesProperty(value[0], value[1]) + : baseMatches(value); + } + return property(value); + } + + /** + * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeys(object) { + if (!isPrototype(object)) { + return nativeKeys(object); + } + var result = []; + for (var key in Object(object)) { + if (hasOwnProperty.call(object, key) && key != 'constructor') { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function baseKeysIn(object) { + if (!isObject(object)) { + return nativeKeysIn(object); + } + var isProto = isPrototype(object), + result = []; + + for (var key in object) { + if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { + result.push(key); + } + } + return result; + } + + /** + * The base implementation of `_.lt` which doesn't coerce arguments. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + */ + function baseLt(value, other) { + return value < other; + } + + /** + * The base implementation of `_.map` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} iteratee The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + */ + function baseMap(collection, iteratee) { + var index = -1, + result = isArrayLike(collection) ? Array(collection.length) : []; + + baseEach(collection, function(value, key, collection) { + result[++index] = iteratee(value, key, collection); + }); + return result; + } + + /** + * The base implementation of `_.matches` which doesn't clone `source`. + * + * @private + * @param {Object} source The object of property values to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatches(source) { + var matchData = getMatchData(source); + if (matchData.length == 1 && matchData[0][2]) { + return matchesStrictComparable(matchData[0][0], matchData[0][1]); + } + return function(object) { + return object === source || baseIsMatch(object, source, matchData); + }; + } + + /** + * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`. + * + * @private + * @param {string} path The path of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function baseMatchesProperty(path, srcValue) { + if (isKey(path) && isStrictComparable(srcValue)) { + return matchesStrictComparable(toKey(path), srcValue); + } + return function(object) { + var objValue = get(object, path); + return (objValue === undefined && objValue === srcValue) + ? hasIn(object, path) + : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG); + }; + } + + /** + * The base implementation of `_.merge` without support for multiple sources. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {number} srcIndex The index of `source`. + * @param {Function} [customizer] The function to customize merged values. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + */ + function baseMerge(object, source, srcIndex, customizer, stack) { + if (object === source) { + return; + } + if (!(isArray(source) || isTypedArray(source))) { + var props = baseKeysIn(source); + } + arrayEach(props || source, function(srcValue, key) { + if (props) { + key = srcValue; + srcValue = source[key]; + } + if (isObject(srcValue)) { + stack || (stack = new Stack); + baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); + } + else { + var newValue = customizer + ? customizer(object[key], srcValue, (key + ''), object, source, stack) + : undefined; + + if (newValue === undefined) { + newValue = srcValue; + } + assignMergeValue(object, key, newValue); + } + }); + } + + /** + * A specialized version of `baseMerge` for arrays and objects which performs + * deep merges and tracks traversed objects enabling objects with circular + * references to be merged. + * + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {string} key The key of the value to merge. + * @param {number} srcIndex The index of `source`. + * @param {Function} mergeFunc The function to merge values. + * @param {Function} [customizer] The function to customize assigned values. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + */ + function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { + var objValue = object[key], + srcValue = source[key], + stacked = stack.get(srcValue); + + if (stacked) { + assignMergeValue(object, key, stacked); + return; + } + var newValue = customizer + ? customizer(objValue, srcValue, (key + ''), object, source, stack) + : undefined; + + var isCommon = newValue === undefined; + + if (isCommon) { + newValue = srcValue; + if (isArray(srcValue) || isTypedArray(srcValue)) { + if (isArray(objValue)) { + newValue = objValue; + } + else if (isArrayLikeObject(objValue)) { + newValue = copyArray(objValue); + } + else { + isCommon = false; + newValue = baseClone(srcValue, true); + } + } + else if (isPlainObject(srcValue) || isArguments(srcValue)) { + if (isArguments(objValue)) { + newValue = toPlainObject(objValue); + } + else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { + isCommon = false; + newValue = baseClone(srcValue, true); + } + else { + newValue = objValue; + } + } + else { + isCommon = false; + } + } + if (isCommon) { + // Recursively merge objects and arrays (susceptible to call stack limits). + stack.set(srcValue, newValue); + mergeFunc(newValue, srcValue, srcIndex, customizer, stack); + stack['delete'](srcValue); + } + assignMergeValue(object, key, newValue); + } + + /** + * The base implementation of `_.nth` which doesn't coerce arguments. + * + * @private + * @param {Array} array The array to query. + * @param {number} n The index of the element to return. + * @returns {*} Returns the nth element of `array`. + */ + function baseNth(array, n) { + var length = array.length; + if (!length) { + return; + } + n += n < 0 ? length : 0; + return isIndex(n, length) ? array[n] : undefined; + } + + /** + * The base implementation of `_.orderBy` without param guards. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by. + * @param {string[]} orders The sort orders of `iteratees`. + * @returns {Array} Returns the new sorted array. + */ + function baseOrderBy(collection, iteratees, orders) { + var index = -1; + iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee())); + + var result = baseMap(collection, function(value, key, collection) { + var criteria = arrayMap(iteratees, function(iteratee) { + return iteratee(value); + }); + return { 'criteria': criteria, 'index': ++index, 'value': value }; + }); + + return baseSortBy(result, function(object, other) { + return compareMultiple(object, other, orders); + }); + } + + /** + * The base implementation of `_.pick` without support for individual + * property identifiers. + * + * @private + * @param {Object} object The source object. + * @param {string[]} props The property identifiers to pick. + * @returns {Object} Returns the new object. + */ + function basePick(object, props) { + object = Object(object); + return basePickBy(object, props, function(value, key) { + return key in object; + }); + } + + /** + * The base implementation of `_.pickBy` without support for iteratee shorthands. + * + * @private + * @param {Object} object The source object. + * @param {string[]} props The property identifiers to pick from. + * @param {Function} predicate The function invoked per property. + * @returns {Object} Returns the new object. + */ + function basePickBy(object, props, predicate) { + var index = -1, + length = props.length, + result = {}; + + while (++index < length) { + var key = props[index], + value = object[key]; + + if (predicate(value, key)) { + result[key] = value; + } + } + return result; + } + + /** + * A specialized version of `baseProperty` which supports deep paths. + * + * @private + * @param {Array|string} path The path of the property to get. + * @returns {Function} Returns the new accessor function. + */ + function basePropertyDeep(path) { + return function(object) { + return baseGet(object, path); + }; + } + + /** + * The base implementation of `_.pullAllBy` without support for iteratee + * shorthands. + * + * @private + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns `array`. + */ + function basePullAll(array, values, iteratee, comparator) { + var indexOf = comparator ? baseIndexOfWith : baseIndexOf, + index = -1, + length = values.length, + seen = array; + + if (array === values) { + values = copyArray(values); + } + if (iteratee) { + seen = arrayMap(array, baseUnary(iteratee)); + } + while (++index < length) { + var fromIndex = 0, + value = values[index], + computed = iteratee ? iteratee(value) : value; + + while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) { + if (seen !== array) { + splice.call(seen, fromIndex, 1); + } + splice.call(array, fromIndex, 1); + } + } + return array; + } + + /** + * The base implementation of `_.pullAt` without support for individual + * indexes or capturing the removed elements. + * + * @private + * @param {Array} array The array to modify. + * @param {number[]} indexes The indexes of elements to remove. + * @returns {Array} Returns `array`. + */ + function basePullAt(array, indexes) { + var length = array ? indexes.length : 0, + lastIndex = length - 1; + + while (length--) { + var index = indexes[length]; + if (length == lastIndex || index !== previous) { + var previous = index; + if (isIndex(index)) { + splice.call(array, index, 1); + } + else if (!isKey(index, array)) { + var path = castPath(index), + object = parent(array, path); + + if (object != null) { + delete object[toKey(last(path))]; + } + } + else { + delete array[toKey(index)]; + } + } + } + return array; + } + + /** + * The base implementation of `_.random` without support for returning + * floating-point numbers. + * + * @private + * @param {number} lower The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the random number. + */ + function baseRandom(lower, upper) { + return lower + nativeFloor(nativeRandom() * (upper - lower + 1)); + } + + /** + * The base implementation of `_.range` and `_.rangeRight` which doesn't + * coerce arguments. + * + * @private + * @param {number} start The start of the range. + * @param {number} end The end of the range. + * @param {number} step The value to increment or decrement by. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the range of numbers. + */ + function baseRange(start, end, step, fromRight) { + var index = -1, + length = nativeMax(nativeCeil((end - start) / (step || 1)), 0), + result = Array(length); + + while (length--) { + result[fromRight ? length : ++index] = start; + start += step; + } + return result; + } + + /** + * The base implementation of `_.repeat` which doesn't coerce arguments. + * + * @private + * @param {string} string The string to repeat. + * @param {number} n The number of times to repeat the string. + * @returns {string} Returns the repeated string. + */ + function baseRepeat(string, n) { + var result = ''; + if (!string || n < 1 || n > MAX_SAFE_INTEGER) { + return result; + } + // Leverage the exponentiation by squaring algorithm for a faster repeat. + // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details. + do { + if (n % 2) { + result += string; + } + n = nativeFloor(n / 2); + if (n) { + string += string; + } + } while (n); + + return result; + } + + /** + * The base implementation of `_.rest` which doesn't validate or coerce arguments. + * + * @private + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + */ + function baseRest(func, start) { + start = nativeMax(start === undefined ? (func.length - 1) : start, 0); + return function() { + var args = arguments, + index = -1, + length = nativeMax(args.length - start, 0), + array = Array(length); + + while (++index < length) { + array[index] = args[start + index]; + } + index = -1; + var otherArgs = Array(start + 1); + while (++index < start) { + otherArgs[index] = args[index]; + } + otherArgs[start] = array; + return apply(func, this, otherArgs); + }; + } + + /** + * The base implementation of `_.set`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize path creation. + * @returns {Object} Returns `object`. + */ + function baseSet(object, path, value, customizer) { + if (!isObject(object)) { + return object; + } + path = isKey(path, object) ? [path] : castPath(path); + + var index = -1, + length = path.length, + lastIndex = length - 1, + nested = object; + + while (nested != null && ++index < length) { + var key = toKey(path[index]), + newValue = value; + + if (index != lastIndex) { + var objValue = nested[key]; + newValue = customizer ? customizer(objValue, key, nested) : undefined; + if (newValue === undefined) { + newValue = isObject(objValue) + ? objValue + : (isIndex(path[index + 1]) ? [] : {}); + } + } + assignValue(nested, key, newValue); + nested = nested[key]; + } + return object; + } + + /** + * The base implementation of `setData` without support for hot loop detection. + * + * @private + * @param {Function} func The function to associate metadata with. + * @param {*} data The metadata. + * @returns {Function} Returns `func`. + */ + var baseSetData = !metaMap ? identity : function(func, data) { + metaMap.set(func, data); + return func; + }; + + /** + * The base implementation of `_.slice` without an iteratee call guard. + * + * @private + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. + */ + function baseSlice(array, start, end) { + var index = -1, + length = array.length; + + if (start < 0) { + start = -start > length ? 0 : (length + start); + } + end = end > length ? length : end; + if (end < 0) { + end += length; + } + length = start > end ? 0 : ((end - start) >>> 0); + start >>>= 0; + + var result = Array(length); + while (++index < length) { + result[index] = array[index + start]; + } + return result; + } + + /** + * The base implementation of `_.some` without support for iteratee shorthands. + * + * @private + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} predicate The function invoked per iteration. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + */ + function baseSome(collection, predicate) { + var result; + + baseEach(collection, function(value, index, collection) { + result = predicate(value, index, collection); + return !result; + }); + return !!result; + } + + /** + * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which + * performs a binary search of `array` to determine the index at which `value` + * should be inserted into `array` in order to maintain its sort order. + * + * @private + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {boolean} [retHighest] Specify returning the highest qualified index. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + */ + function baseSortedIndex(array, value, retHighest) { + var low = 0, + high = array ? array.length : low; + + if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) { + while (low < high) { + var mid = (low + high) >>> 1, + computed = array[mid]; + + if (computed !== null && !isSymbol(computed) && + (retHighest ? (computed <= value) : (computed < value))) { + low = mid + 1; + } else { + high = mid; + } + } + return high; + } + return baseSortedIndexBy(array, value, identity, retHighest); + } + + /** + * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy` + * which invokes `iteratee` for `value` and each element of `array` to compute + * their sort ranking. The iteratee is invoked with one argument; (value). + * + * @private + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} iteratee The iteratee invoked per element. + * @param {boolean} [retHighest] Specify returning the highest qualified index. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + */ + function baseSortedIndexBy(array, value, iteratee, retHighest) { + value = iteratee(value); + + var low = 0, + high = array ? array.length : 0, + valIsNaN = value !== value, + valIsNull = value === null, + valIsSymbol = isSymbol(value), + valIsUndefined = value === undefined; + + while (low < high) { + var mid = nativeFloor((low + high) / 2), + computed = iteratee(array[mid]), + othIsDefined = computed !== undefined, + othIsNull = computed === null, + othIsReflexive = computed === computed, + othIsSymbol = isSymbol(computed); + + if (valIsNaN) { + var setLow = retHighest || othIsReflexive; + } else if (valIsUndefined) { + setLow = othIsReflexive && (retHighest || othIsDefined); + } else if (valIsNull) { + setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull); + } else if (valIsSymbol) { + setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol); + } else if (othIsNull || othIsSymbol) { + setLow = false; + } else { + setLow = retHighest ? (computed <= value) : (computed < value); + } + if (setLow) { + low = mid + 1; + } else { + high = mid; + } + } + return nativeMin(high, MAX_ARRAY_INDEX); + } + + /** + * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without + * support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. + */ + function baseSortedUniq(array, iteratee) { + var index = -1, + length = array.length, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + if (!index || !eq(computed, seen)) { + var seen = computed; + result[resIndex++] = value === 0 ? 0 : value; + } + } + return result; + } + + /** + * The base implementation of `_.toNumber` which doesn't ensure correct + * conversions of binary, hexadecimal, or octal string values. + * + * @private + * @param {*} value The value to process. + * @returns {number} Returns the number. + */ + function baseToNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + return +value; + } + + /** + * The base implementation of `_.toString` which doesn't convert nullish + * values to empty strings. + * + * @private + * @param {*} value The value to process. + * @returns {string} Returns the string. + */ + function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + if (isSymbol(value)) { + return symbolToString ? symbolToString.call(value) : ''; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; + } + + /** + * The base implementation of `_.uniqBy` without support for iteratee shorthands. + * + * @private + * @param {Array} array The array to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new duplicate free array. + */ + function baseUniq(array, iteratee, comparator) { + var index = -1, + includes = arrayIncludes, + length = array.length, + isCommon = true, + result = [], + seen = result; + + if (comparator) { + isCommon = false; + includes = arrayIncludesWith; + } + else if (length >= LARGE_ARRAY_SIZE) { + var set = iteratee ? null : createSet(array); + if (set) { + return setToArray(set); + } + isCommon = false; + includes = cacheHas; + seen = new SetCache; + } + else { + seen = iteratee ? [] : result; + } + outer: + while (++index < length) { + var value = array[index], + computed = iteratee ? iteratee(value) : value; + + value = (comparator || value !== 0) ? value : 0; + if (isCommon && computed === computed) { + var seenIndex = seen.length; + while (seenIndex--) { + if (seen[seenIndex] === computed) { + continue outer; + } + } + if (iteratee) { + seen.push(computed); + } + result.push(value); + } + else if (!includes(seen, computed, comparator)) { + if (seen !== result) { + seen.push(computed); + } + result.push(value); + } + } + return result; + } + + /** + * The base implementation of `_.unset`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + */ + function baseUnset(object, path) { + path = isKey(path, object) ? [path] : castPath(path); + object = parent(object, path); + + var key = toKey(last(path)); + return !(object != null && hasOwnProperty.call(object, key)) || delete object[key]; + } + + /** + * The base implementation of `_.update`. + * + * @private + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to update. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize path creation. + * @returns {Object} Returns `object`. + */ + function baseUpdate(object, path, updater, customizer) { + return baseSet(object, path, updater(baseGet(object, path)), customizer); + } + + /** + * The base implementation of methods like `_.dropWhile` and `_.takeWhile` + * without support for iteratee shorthands. + * + * @private + * @param {Array} array The array to query. + * @param {Function} predicate The function invoked per iteration. + * @param {boolean} [isDrop] Specify dropping elements instead of taking them. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Array} Returns the slice of `array`. + */ + function baseWhile(array, predicate, isDrop, fromRight) { + var length = array.length, + index = fromRight ? length : -1; + + while ((fromRight ? index-- : ++index < length) && + predicate(array[index], index, array)) {} + + return isDrop + ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length)) + : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index)); + } + + /** + * The base implementation of `wrapperValue` which returns the result of + * performing a sequence of actions on the unwrapped `value`, where each + * successive action is supplied the return value of the previous. + * + * @private + * @param {*} value The unwrapped value. + * @param {Array} actions Actions to perform to resolve the unwrapped value. + * @returns {*} Returns the resolved value. + */ + function baseWrapperValue(value, actions) { + var result = value; + if (result instanceof LazyWrapper) { + result = result.value(); + } + return arrayReduce(actions, function(result, action) { + return action.func.apply(action.thisArg, arrayPush([result], action.args)); + }, result); + } + + /** + * The base implementation of methods like `_.xor`, without support for + * iteratee shorthands, that accepts an array of arrays to inspect. + * + * @private + * @param {Array} arrays The arrays to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of values. + */ + function baseXor(arrays, iteratee, comparator) { + var index = -1, + length = arrays.length; + + while (++index < length) { + var result = result + ? arrayPush( + baseDifference(result, arrays[index], iteratee, comparator), + baseDifference(arrays[index], result, iteratee, comparator) + ) + : arrays[index]; + } + return (result && result.length) ? baseUniq(result, iteratee, comparator) : []; + } + + /** + * This base implementation of `_.zipObject` which assigns values using `assignFunc`. + * + * @private + * @param {Array} props The property identifiers. + * @param {Array} values The property values. + * @param {Function} assignFunc The function to assign values. + * @returns {Object} Returns the new object. + */ + function baseZipObject(props, values, assignFunc) { + var index = -1, + length = props.length, + valsLength = values.length, + result = {}; + + while (++index < length) { + var value = index < valsLength ? values[index] : undefined; + assignFunc(result, props[index], value); + } + return result; + } + + /** + * Casts `value` to an empty array if it's not an array like object. + * + * @private + * @param {*} value The value to inspect. + * @returns {Array|Object} Returns the cast array-like object. + */ + function castArrayLikeObject(value) { + return isArrayLikeObject(value) ? value : []; + } + + /** + * Casts `value` to `identity` if it's not a function. + * + * @private + * @param {*} value The value to inspect. + * @returns {Function} Returns cast function. + */ + function castFunction(value) { + return typeof value == 'function' ? value : identity; + } + + /** + * Casts `value` to a path array if it's not one. + * + * @private + * @param {*} value The value to inspect. + * @returns {Array} Returns the cast property path array. + */ + function castPath(value) { + return isArray(value) ? value : stringToPath(value); + } + + /** + * Casts `array` to a slice if it's needed. + * + * @private + * @param {Array} array The array to inspect. + * @param {number} start The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the cast slice. + */ + function castSlice(array, start, end) { + var length = array.length; + end = end === undefined ? length : end; + return (!start && end >= length) ? array : baseSlice(array, start, end); + } + + /** + * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout). + * + * @private + * @param {number|Object} id The timer id or timeout object of the timer to clear. + */ + var clearTimeout = ctxClearTimeout || function(id) { + return root.clearTimeout(id); + }; + + /** + * Creates a clone of `buffer`. + * + * @private + * @param {Buffer} buffer The buffer to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Buffer} Returns the cloned buffer. + */ + function cloneBuffer(buffer, isDeep) { + if (isDeep) { + return buffer.slice(); + } + var result = new buffer.constructor(buffer.length); + buffer.copy(result); + return result; + } + + /** + * Creates a clone of `arrayBuffer`. + * + * @private + * @param {ArrayBuffer} arrayBuffer The array buffer to clone. + * @returns {ArrayBuffer} Returns the cloned array buffer. + */ + function cloneArrayBuffer(arrayBuffer) { + var result = new arrayBuffer.constructor(arrayBuffer.byteLength); + new Uint8Array(result).set(new Uint8Array(arrayBuffer)); + return result; + } + + /** + * Creates a clone of `dataView`. + * + * @private + * @param {Object} dataView The data view to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned data view. + */ + function cloneDataView(dataView, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; + return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); + } + + /** + * Creates a clone of `map`. + * + * @private + * @param {Object} map The map to clone. + * @param {Function} cloneFunc The function to clone values. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned map. + */ + function cloneMap(map, isDeep, cloneFunc) { + var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map); + return arrayReduce(array, addMapEntry, new map.constructor); + } + + /** + * Creates a clone of `regexp`. + * + * @private + * @param {Object} regexp The regexp to clone. + * @returns {Object} Returns the cloned regexp. + */ + function cloneRegExp(regexp) { + var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); + result.lastIndex = regexp.lastIndex; + return result; + } + + /** + * Creates a clone of `set`. + * + * @private + * @param {Object} set The set to clone. + * @param {Function} cloneFunc The function to clone values. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned set. + */ + function cloneSet(set, isDeep, cloneFunc) { + var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set); + return arrayReduce(array, addSetEntry, new set.constructor); + } + + /** + * Creates a clone of the `symbol` object. + * + * @private + * @param {Object} symbol The symbol object to clone. + * @returns {Object} Returns the cloned symbol object. + */ + function cloneSymbol(symbol) { + return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; + } + + /** + * Creates a clone of `typedArray`. + * + * @private + * @param {Object} typedArray The typed array to clone. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the cloned typed array. + */ + function cloneTypedArray(typedArray, isDeep) { + var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; + return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); + } + + /** + * Compares values to sort them in ascending order. + * + * @private + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {number} Returns the sort order indicator for `value`. + */ + function compareAscending(value, other) { + if (value !== other) { + var valIsDefined = value !== undefined, + valIsNull = value === null, + valIsReflexive = value === value, + valIsSymbol = isSymbol(value); + + var othIsDefined = other !== undefined, + othIsNull = other === null, + othIsReflexive = other === other, + othIsSymbol = isSymbol(other); + + if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) || + (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) || + (valIsNull && othIsDefined && othIsReflexive) || + (!valIsDefined && othIsReflexive) || + !valIsReflexive) { + return 1; + } + if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) || + (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) || + (othIsNull && valIsDefined && valIsReflexive) || + (!othIsDefined && valIsReflexive) || + !othIsReflexive) { + return -1; + } + } + return 0; + } + + /** + * Used by `_.orderBy` to compare multiple properties of a value to another + * and stable sort them. + * + * If `orders` is unspecified, all values are sorted in ascending order. Otherwise, + * specify an order of "desc" for descending or "asc" for ascending sort order + * of corresponding values. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {boolean[]|string[]} orders The order to sort by for each property. + * @returns {number} Returns the sort order indicator for `object`. + */ + function compareMultiple(object, other, orders) { + var index = -1, + objCriteria = object.criteria, + othCriteria = other.criteria, + length = objCriteria.length, + ordersLength = orders.length; + + while (++index < length) { + var result = compareAscending(objCriteria[index], othCriteria[index]); + if (result) { + if (index >= ordersLength) { + return result; + } + var order = orders[index]; + return result * (order == 'desc' ? -1 : 1); + } + } + // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications + // that causes it, under certain circumstances, to provide the same value for + // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247 + // for more details. + // + // This also ensures a stable sort in V8 and other engines. + // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details. + return object.index - other.index; + } + + /** + * Creates an array that is the composition of partially applied arguments, + * placeholders, and provided arguments into a single array of arguments. + * + * @private + * @param {Array} args The provided arguments. + * @param {Array} partials The arguments to prepend to those provided. + * @param {Array} holders The `partials` placeholder indexes. + * @params {boolean} [isCurried] Specify composing for a curried function. + * @returns {Array} Returns the new array of composed arguments. + */ + function composeArgs(args, partials, holders, isCurried) { + var argsIndex = -1, + argsLength = args.length, + holdersLength = holders.length, + leftIndex = -1, + leftLength = partials.length, + rangeLength = nativeMax(argsLength - holdersLength, 0), + result = Array(leftLength + rangeLength), + isUncurried = !isCurried; + + while (++leftIndex < leftLength) { + result[leftIndex] = partials[leftIndex]; + } + while (++argsIndex < holdersLength) { + if (isUncurried || argsIndex < argsLength) { + result[holders[argsIndex]] = args[argsIndex]; + } + } + while (rangeLength--) { + result[leftIndex++] = args[argsIndex++]; + } + return result; + } + + /** + * This function is like `composeArgs` except that the arguments composition + * is tailored for `_.partialRight`. + * + * @private + * @param {Array} args The provided arguments. + * @param {Array} partials The arguments to append to those provided. + * @param {Array} holders The `partials` placeholder indexes. + * @params {boolean} [isCurried] Specify composing for a curried function. + * @returns {Array} Returns the new array of composed arguments. + */ + function composeArgsRight(args, partials, holders, isCurried) { + var argsIndex = -1, + argsLength = args.length, + holdersIndex = -1, + holdersLength = holders.length, + rightIndex = -1, + rightLength = partials.length, + rangeLength = nativeMax(argsLength - holdersLength, 0), + result = Array(rangeLength + rightLength), + isUncurried = !isCurried; + + while (++argsIndex < rangeLength) { + result[argsIndex] = args[argsIndex]; + } + var offset = argsIndex; + while (++rightIndex < rightLength) { + result[offset + rightIndex] = partials[rightIndex]; + } + while (++holdersIndex < holdersLength) { + if (isUncurried || argsIndex < argsLength) { + result[offset + holders[holdersIndex]] = args[argsIndex++]; + } + } + return result; + } + + /** + * Copies the values of `source` to `array`. + * + * @private + * @param {Array} source The array to copy values from. + * @param {Array} [array=[]] The array to copy values to. + * @returns {Array} Returns `array`. + */ + function copyArray(source, array) { + var index = -1, + length = source.length; + + array || (array = Array(length)); + while (++index < length) { + array[index] = source[index]; + } + return array; + } + + /** + * Copies properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy properties from. + * @param {Array} props The property identifiers to copy. + * @param {Object} [object={}] The object to copy properties to. + * @param {Function} [customizer] The function to customize copied values. + * @returns {Object} Returns `object`. + */ + function copyObject(source, props, object, customizer) { + object || (object = {}); + + var index = -1, + length = props.length; + + while (++index < length) { + var key = props[index]; + + var newValue = customizer + ? customizer(object[key], source[key], key, object, source) + : undefined; + + assignValue(object, key, newValue === undefined ? source[key] : newValue); + } + return object; + } + + /** + * Copies own symbol properties of `source` to `object`. + * + * @private + * @param {Object} source The object to copy symbols from. + * @param {Object} [object={}] The object to copy symbols to. + * @returns {Object} Returns `object`. + */ + function copySymbols(source, object) { + return copyObject(source, getSymbols(source), object); + } + + /** + * Creates a function like `_.groupBy`. + * + * @private + * @param {Function} setter The function to set accumulator values. + * @param {Function} [initializer] The accumulator object initializer. + * @returns {Function} Returns the new aggregator function. + */ + function createAggregator(setter, initializer) { + return function(collection, iteratee) { + var func = isArray(collection) ? arrayAggregator : baseAggregator, + accumulator = initializer ? initializer() : {}; + + return func(collection, setter, getIteratee(iteratee, 2), accumulator); + }; + } + + /** + * Creates a function like `_.assign`. + * + * @private + * @param {Function} assigner The function to assign values. + * @returns {Function} Returns the new assigner function. + */ + function createAssigner(assigner) { + return baseRest(function(object, sources) { + var index = -1, + length = sources.length, + customizer = length > 1 ? sources[length - 1] : undefined, + guard = length > 2 ? sources[2] : undefined; + + customizer = (assigner.length > 3 && typeof customizer == 'function') + ? (length--, customizer) + : undefined; + + if (guard && isIterateeCall(sources[0], sources[1], guard)) { + customizer = length < 3 ? undefined : customizer; + length = 1; + } + object = Object(object); + while (++index < length) { + var source = sources[index]; + if (source) { + assigner(object, source, index, customizer); + } + } + return object; + }); + } + + /** + * Creates a `baseEach` or `baseEachRight` function. + * + * @private + * @param {Function} eachFunc The function to iterate over a collection. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseEach(eachFunc, fromRight) { + return function(collection, iteratee) { + if (collection == null) { + return collection; + } + if (!isArrayLike(collection)) { + return eachFunc(collection, iteratee); + } + var length = collection.length, + index = fromRight ? length : -1, + iterable = Object(collection); + + while ((fromRight ? index-- : ++index < length)) { + if (iteratee(iterable[index], index, iterable) === false) { + break; + } + } + return collection; + }; + } + + /** + * Creates a base function for methods like `_.forIn` and `_.forOwn`. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new base function. + */ + function createBaseFor(fromRight) { + return function(object, iteratee, keysFunc) { + var index = -1, + iterable = Object(object), + props = keysFunc(object), + length = props.length; + + while (length--) { + var key = props[fromRight ? length : ++index]; + if (iteratee(iterable[key], key, iterable) === false) { + break; + } + } + return object; + }; + } + + /** + * Creates a function that wraps `func` to invoke it with the optional `this` + * binding of `thisArg`. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} [thisArg] The `this` binding of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createBind(func, bitmask, thisArg) { + var isBind = bitmask & BIND_FLAG, + Ctor = createCtor(func); + + function wrapper() { + var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + return fn.apply(isBind ? thisArg : this, arguments); + } + return wrapper; + } + + /** + * Creates a function like `_.lowerFirst`. + * + * @private + * @param {string} methodName The name of the `String` case method to use. + * @returns {Function} Returns the new case function. + */ + function createCaseFirst(methodName) { + return function(string) { + string = toString(string); + + var strSymbols = hasUnicode(string) + ? stringToArray(string) + : undefined; + + var chr = strSymbols + ? strSymbols[0] + : string.charAt(0); + + var trailing = strSymbols + ? castSlice(strSymbols, 1).join('') + : string.slice(1); + + return chr[methodName]() + trailing; + }; + } + + /** + * Creates a function like `_.camelCase`. + * + * @private + * @param {Function} callback The function to combine each word. + * @returns {Function} Returns the new compounder function. + */ + function createCompounder(callback) { + return function(string) { + return arrayReduce(words(deburr(string).replace(reApos, '')), callback, ''); + }; + } + + /** + * Creates a function that produces an instance of `Ctor` regardless of + * whether it was invoked as part of a `new` expression or by `call` or `apply`. + * + * @private + * @param {Function} Ctor The constructor to wrap. + * @returns {Function} Returns the new wrapped function. + */ + function createCtor(Ctor) { + return function() { + // Use a `switch` statement to work with class constructors. See + // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist + // for more details. + var args = arguments; + switch (args.length) { + case 0: return new Ctor; + case 1: return new Ctor(args[0]); + case 2: return new Ctor(args[0], args[1]); + case 3: return new Ctor(args[0], args[1], args[2]); + case 4: return new Ctor(args[0], args[1], args[2], args[3]); + case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]); + case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]); + case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); + } + var thisBinding = baseCreate(Ctor.prototype), + result = Ctor.apply(thisBinding, args); + + // Mimic the constructor's `return` behavior. + // See https://es5.github.io/#x13.2.2 for more details. + return isObject(result) ? result : thisBinding; + }; + } + + /** + * Creates a function that wraps `func` to enable currying. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {number} arity The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createCurry(func, bitmask, arity) { + var Ctor = createCtor(func); + + function wrapper() { + var length = arguments.length, + args = Array(length), + index = length, + placeholder = getHolder(wrapper); + + while (index--) { + args[index] = arguments[index]; + } + var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder) + ? [] + : replaceHolders(args, placeholder); + + length -= holders.length; + if (length < arity) { + return createRecurry( + func, bitmask, createHybrid, wrapper.placeholder, undefined, + args, holders, undefined, undefined, arity - length); + } + var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + return apply(fn, this, args); + } + return wrapper; + } + + /** + * Creates a `_.find` or `_.findLast` function. + * + * @private + * @param {Function} findIndexFunc The function to find the collection index. + * @returns {Function} Returns the new find function. + */ + function createFind(findIndexFunc) { + return function(collection, predicate, fromIndex) { + var iterable = Object(collection); + if (!isArrayLike(collection)) { + var iteratee = getIteratee(predicate, 3); + collection = keys(collection); + predicate = function(key) { return iteratee(iterable[key], key, iterable); }; + } + var index = findIndexFunc(collection, predicate, fromIndex); + return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined; + }; + } + + /** + * Creates a `_.flow` or `_.flowRight` function. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new flow function. + */ + function createFlow(fromRight) { + return baseRest(function(funcs) { + funcs = baseFlatten(funcs, 1); + + var length = funcs.length, + index = length, + prereq = LodashWrapper.prototype.thru; + + if (fromRight) { + funcs.reverse(); + } + while (index--) { + var func = funcs[index]; + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + if (prereq && !wrapper && getFuncName(func) == 'wrapper') { + var wrapper = new LodashWrapper([], true); + } + } + index = wrapper ? index : length; + while (++index < length) { + func = funcs[index]; + + var funcName = getFuncName(func), + data = funcName == 'wrapper' ? getData(func) : undefined; + + if (data && isLaziable(data[0]) && + data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) && + !data[4].length && data[9] == 1 + ) { + wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]); + } else { + wrapper = (func.length == 1 && isLaziable(func)) + ? wrapper[funcName]() + : wrapper.thru(func); + } + } + return function() { + var args = arguments, + value = args[0]; + + if (wrapper && args.length == 1 && + isArray(value) && value.length >= LARGE_ARRAY_SIZE) { + return wrapper.plant(value).value(); + } + var index = 0, + result = length ? funcs[index].apply(this, args) : value; + + while (++index < length) { + result = funcs[index].call(this, result); + } + return result; + }; + }); + } + + /** + * Creates a function that wraps `func` to invoke it with optional `this` + * binding of `thisArg`, partial application, and currying. + * + * @private + * @param {Function|string} func The function or method name to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to prepend to those provided to + * the new function. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [partialsRight] The arguments to append to those provided + * to the new function. + * @param {Array} [holdersRight] The `partialsRight` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) { + var isAry = bitmask & ARY_FLAG, + isBind = bitmask & BIND_FLAG, + isBindKey = bitmask & BIND_KEY_FLAG, + isCurried = bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG), + isFlip = bitmask & FLIP_FLAG, + Ctor = isBindKey ? undefined : createCtor(func); + + function wrapper() { + var length = arguments.length, + args = Array(length), + index = length; + + while (index--) { + args[index] = arguments[index]; + } + if (isCurried) { + var placeholder = getHolder(wrapper), + holdersCount = countHolders(args, placeholder); + } + if (partials) { + args = composeArgs(args, partials, holders, isCurried); + } + if (partialsRight) { + args = composeArgsRight(args, partialsRight, holdersRight, isCurried); + } + length -= holdersCount; + if (isCurried && length < arity) { + var newHolders = replaceHolders(args, placeholder); + return createRecurry( + func, bitmask, createHybrid, wrapper.placeholder, thisArg, + args, newHolders, argPos, ary, arity - length + ); + } + var thisBinding = isBind ? thisArg : this, + fn = isBindKey ? thisBinding[func] : func; + + length = args.length; + if (argPos) { + args = reorder(args, argPos); + } else if (isFlip && length > 1) { + args.reverse(); + } + if (isAry && ary < length) { + args.length = ary; + } + if (this && this !== root && this instanceof wrapper) { + fn = Ctor || createCtor(fn); + } + return fn.apply(thisBinding, args); + } + return wrapper; + } + + /** + * Creates a function like `_.invertBy`. + * + * @private + * @param {Function} setter The function to set accumulator values. + * @param {Function} toIteratee The function to resolve iteratees. + * @returns {Function} Returns the new inverter function. + */ + function createInverter(setter, toIteratee) { + return function(object, iteratee) { + return baseInverter(object, setter, toIteratee(iteratee), {}); + }; + } + + /** + * Creates a function that performs a mathematical operation on two values. + * + * @private + * @param {Function} operator The function to perform the operation. + * @param {number} [defaultValue] The value used for `undefined` arguments. + * @returns {Function} Returns the new mathematical operation function. + */ + function createMathOperation(operator, defaultValue) { + return function(value, other) { + var result; + if (value === undefined && other === undefined) { + return defaultValue; + } + if (value !== undefined) { + result = value; + } + if (other !== undefined) { + if (result === undefined) { + return other; + } + if (typeof value == 'string' || typeof other == 'string') { + value = baseToString(value); + other = baseToString(other); + } else { + value = baseToNumber(value); + other = baseToNumber(other); + } + result = operator(value, other); + } + return result; + }; + } + + /** + * Creates a function like `_.over`. + * + * @private + * @param {Function} arrayFunc The function to iterate over iteratees. + * @returns {Function} Returns the new over function. + */ + function createOver(arrayFunc) { + return baseRest(function(iteratees) { + iteratees = (iteratees.length == 1 && isArray(iteratees[0])) + ? arrayMap(iteratees[0], baseUnary(getIteratee())) + : arrayMap(baseFlatten(iteratees, 1), baseUnary(getIteratee())); + + return baseRest(function(args) { + var thisArg = this; + return arrayFunc(iteratees, function(iteratee) { + return apply(iteratee, thisArg, args); + }); + }); + }); + } + + /** + * Creates the padding for `string` based on `length`. The `chars` string + * is truncated if the number of characters exceeds `length`. + * + * @private + * @param {number} length The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padding for `string`. + */ + function createPadding(length, chars) { + chars = chars === undefined ? ' ' : baseToString(chars); + + var charsLength = chars.length; + if (charsLength < 2) { + return charsLength ? baseRepeat(chars, length) : chars; + } + var result = baseRepeat(chars, nativeCeil(length / stringSize(chars))); + return hasUnicode(chars) + ? castSlice(stringToArray(result), 0, length).join('') + : result.slice(0, length); + } + + /** + * Creates a function that wraps `func` to invoke it with the `this` binding + * of `thisArg` and `partials` prepended to the arguments it receives. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {*} thisArg The `this` binding of `func`. + * @param {Array} partials The arguments to prepend to those provided to + * the new function. + * @returns {Function} Returns the new wrapped function. + */ + function createPartial(func, bitmask, thisArg, partials) { + var isBind = bitmask & BIND_FLAG, + Ctor = createCtor(func); + + function wrapper() { + var argsIndex = -1, + argsLength = arguments.length, + leftIndex = -1, + leftLength = partials.length, + args = Array(leftLength + argsLength), + fn = (this && this !== root && this instanceof wrapper) ? Ctor : func; + + while (++leftIndex < leftLength) { + args[leftIndex] = partials[leftIndex]; + } + while (argsLength--) { + args[leftIndex++] = arguments[++argsIndex]; + } + return apply(fn, isBind ? thisArg : this, args); + } + return wrapper; + } + + /** + * Creates a `_.range` or `_.rangeRight` function. + * + * @private + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {Function} Returns the new range function. + */ + function createRange(fromRight) { + return function(start, end, step) { + if (step && typeof step != 'number' && isIterateeCall(start, end, step)) { + end = step = undefined; + } + // Ensure the sign of `-0` is preserved. + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + step = step === undefined ? (start < end ? 1 : -1) : toFinite(step); + return baseRange(start, end, step, fromRight); + }; + } + + /** + * Creates a function that performs a relational operation on two values. + * + * @private + * @param {Function} operator The function to perform the operation. + * @returns {Function} Returns the new relational operation function. + */ + function createRelationalOperation(operator) { + return function(value, other) { + if (!(typeof value == 'string' && typeof other == 'string')) { + value = toNumber(value); + other = toNumber(other); + } + return operator(value, other); + }; + } + + /** + * Creates a function that wraps `func` to continue currying. + * + * @private + * @param {Function} func The function to wrap. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @param {Function} wrapFunc The function to create the `func` wrapper. + * @param {*} placeholder The placeholder value. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to prepend to those provided to + * the new function. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) { + var isCurry = bitmask & CURRY_FLAG, + newHolders = isCurry ? holders : undefined, + newHoldersRight = isCurry ? undefined : holders, + newPartials = isCurry ? partials : undefined, + newPartialsRight = isCurry ? undefined : partials; + + bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG); + bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG); + + if (!(bitmask & CURRY_BOUND_FLAG)) { + bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG); + } + var newData = [ + func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, + newHoldersRight, argPos, ary, arity + ]; + + var result = wrapFunc.apply(undefined, newData); + if (isLaziable(func)) { + setData(result, newData); + } + result.placeholder = placeholder; + return setWrapToString(result, func, bitmask); + } + + /** + * Creates a function like `_.round`. + * + * @private + * @param {string} methodName The name of the `Math` method to use when rounding. + * @returns {Function} Returns the new round function. + */ + function createRound(methodName) { + var func = Math[methodName]; + return function(number, precision) { + number = toNumber(number); + precision = nativeMin(toInteger(precision), 292); + if (precision) { + // Shift with exponential notation to avoid floating-point issues. + // See [MDN](https://mdn.io/round#Examples) for more details. + var pair = (toString(number) + 'e').split('e'), + value = func(pair[0] + 'e' + (+pair[1] + precision)); + + pair = (toString(value) + 'e').split('e'); + return +(pair[0] + 'e' + (+pair[1] - precision)); + } + return func(number); + }; + } + + /** + * Creates a set object of `values`. + * + * @private + * @param {Array} values The values to add to the set. + * @returns {Object} Returns the new set. + */ + var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) { + return new Set(values); + }; + + /** + * Creates a `_.toPairs` or `_.toPairsIn` function. + * + * @private + * @param {Function} keysFunc The function to get the keys of a given object. + * @returns {Function} Returns the new pairs function. + */ + function createToPairs(keysFunc) { + return function(object) { + var tag = getTag(object); + if (tag == mapTag) { + return mapToArray(object); + } + if (tag == setTag) { + return setToPairs(object); + } + return baseToPairs(object, keysFunc(object)); + }; + } + + /** + * Creates a function that either curries or invokes `func` with optional + * `this` binding and partially applied arguments. + * + * @private + * @param {Function|string} func The function or method name to wrap. + * @param {number} bitmask The bitmask flags. + * The bitmask may be composed of the following flags: + * 1 - `_.bind` + * 2 - `_.bindKey` + * 4 - `_.curry` or `_.curryRight` of a bound function + * 8 - `_.curry` + * 16 - `_.curryRight` + * 32 - `_.partial` + * 64 - `_.partialRight` + * 128 - `_.rearg` + * 256 - `_.ary` + * 512 - `_.flip` + * @param {*} [thisArg] The `this` binding of `func`. + * @param {Array} [partials] The arguments to be partially applied. + * @param {Array} [holders] The `partials` placeholder indexes. + * @param {Array} [argPos] The argument positions of the new function. + * @param {number} [ary] The arity cap of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new wrapped function. + */ + function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) { + var isBindKey = bitmask & BIND_KEY_FLAG; + if (!isBindKey && typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + var length = partials ? partials.length : 0; + if (!length) { + bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG); + partials = holders = undefined; + } + ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0); + arity = arity === undefined ? arity : toInteger(arity); + length -= holders ? holders.length : 0; + + if (bitmask & PARTIAL_RIGHT_FLAG) { + var partialsRight = partials, + holdersRight = holders; + + partials = holders = undefined; + } + var data = isBindKey ? undefined : getData(func); + + var newData = [ + func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, + argPos, ary, arity + ]; + + if (data) { + mergeData(newData, data); + } + func = newData[0]; + bitmask = newData[1]; + thisArg = newData[2]; + partials = newData[3]; + holders = newData[4]; + arity = newData[9] = newData[9] == null + ? (isBindKey ? 0 : func.length) + : nativeMax(newData[9] - length, 0); + + if (!arity && bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG)) { + bitmask &= ~(CURRY_FLAG | CURRY_RIGHT_FLAG); + } + if (!bitmask || bitmask == BIND_FLAG) { + var result = createBind(func, bitmask, thisArg); + } else if (bitmask == CURRY_FLAG || bitmask == CURRY_RIGHT_FLAG) { + result = createCurry(func, bitmask, arity); + } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !holders.length) { + result = createPartial(func, bitmask, thisArg, partials); + } else { + result = createHybrid.apply(undefined, newData); + } + var setter = data ? baseSetData : setData; + return setWrapToString(setter(result, newData), func, bitmask); + } + + /** + * A specialized version of `baseIsEqualDeep` for arrays with support for + * partial deep comparisons. + * + * @private + * @param {Array} array The array to compare. + * @param {Array} other The other array to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} customizer The function to customize comparisons. + * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` + * for more details. + * @param {Object} stack Tracks traversed `array` and `other` objects. + * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`. + */ + function equalArrays(array, other, equalFunc, customizer, bitmask, stack) { + var isPartial = bitmask & PARTIAL_COMPARE_FLAG, + arrLength = array.length, + othLength = other.length; + + if (arrLength != othLength && !(isPartial && othLength > arrLength)) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(array); + if (stacked && stack.get(other)) { + return stacked == other; + } + var index = -1, + result = true, + seen = (bitmask & UNORDERED_COMPARE_FLAG) ? new SetCache : undefined; + + stack.set(array, other); + stack.set(other, array); + + // Ignore non-index properties. + while (++index < arrLength) { + var arrValue = array[index], + othValue = other[index]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, arrValue, index, other, array, stack) + : customizer(arrValue, othValue, index, array, other, stack); + } + if (compared !== undefined) { + if (compared) { + continue; + } + result = false; + break; + } + // Recursively compare arrays (susceptible to call stack limits). + if (seen) { + if (!arraySome(other, function(othValue, othIndex) { + if (!seen.has(othIndex) && + (arrValue === othValue || equalFunc(arrValue, othValue, customizer, bitmask, stack))) { + return seen.add(othIndex); + } + })) { + result = false; + break; + } + } else if (!( + arrValue === othValue || + equalFunc(arrValue, othValue, customizer, bitmask, stack) + )) { + result = false; + break; + } + } + stack['delete'](array); + stack['delete'](other); + return result; + } + + /** + * A specialized version of `baseIsEqualDeep` for comparing objects of + * the same `toStringTag`. + * + * **Note:** This function only supports comparing values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {string} tag The `toStringTag` of the objects to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} customizer The function to customize comparisons. + * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` + * for more details. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) { + switch (tag) { + case dataViewTag: + if ((object.byteLength != other.byteLength) || + (object.byteOffset != other.byteOffset)) { + return false; + } + object = object.buffer; + other = other.buffer; + + case arrayBufferTag: + if ((object.byteLength != other.byteLength) || + !equalFunc(new Uint8Array(object), new Uint8Array(other))) { + return false; + } + return true; + + case boolTag: + case dateTag: + case numberTag: + // Coerce booleans to `1` or `0` and dates to milliseconds. + // Invalid dates are coerced to `NaN`. + return eq(+object, +other); + + case errorTag: + return object.name == other.name && object.message == other.message; + + case regexpTag: + case stringTag: + // Coerce regexes to strings and treat strings, primitives and objects, + // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring + // for more details. + return object == (other + ''); + + case mapTag: + var convert = mapToArray; + + case setTag: + var isPartial = bitmask & PARTIAL_COMPARE_FLAG; + convert || (convert = setToArray); + + if (object.size != other.size && !isPartial) { + return false; + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked) { + return stacked == other; + } + bitmask |= UNORDERED_COMPARE_FLAG; + + // Recursively compare objects (susceptible to call stack limits). + stack.set(object, other); + var result = equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack); + stack['delete'](object); + return result; + + case symbolTag: + if (symbolValueOf) { + return symbolValueOf.call(object) == symbolValueOf.call(other); + } + } + return false; + } + + /** + * A specialized version of `baseIsEqualDeep` for objects with support for + * partial deep comparisons. + * + * @private + * @param {Object} object The object to compare. + * @param {Object} other The other object to compare. + * @param {Function} equalFunc The function to determine equivalents of values. + * @param {Function} customizer The function to customize comparisons. + * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual` + * for more details. + * @param {Object} stack Tracks traversed `object` and `other` objects. + * @returns {boolean} Returns `true` if the objects are equivalent, else `false`. + */ + function equalObjects(object, other, equalFunc, customizer, bitmask, stack) { + var isPartial = bitmask & PARTIAL_COMPARE_FLAG, + objProps = keys(object), + objLength = objProps.length, + othProps = keys(other), + othLength = othProps.length; + + if (objLength != othLength && !isPartial) { + return false; + } + var index = objLength; + while (index--) { + var key = objProps[index]; + if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) { + return false; + } + } + // Assume cyclic values are equal. + var stacked = stack.get(object); + if (stacked && stack.get(other)) { + return stacked == other; + } + var result = true; + stack.set(object, other); + stack.set(other, object); + + var skipCtor = isPartial; + while (++index < objLength) { + key = objProps[index]; + var objValue = object[key], + othValue = other[key]; + + if (customizer) { + var compared = isPartial + ? customizer(othValue, objValue, key, other, object, stack) + : customizer(objValue, othValue, key, object, other, stack); + } + // Recursively compare objects (susceptible to call stack limits). + if (!(compared === undefined + ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack)) + : compared + )) { + result = false; + break; + } + skipCtor || (skipCtor = key == 'constructor'); + } + if (result && !skipCtor) { + var objCtor = object.constructor, + othCtor = other.constructor; + + // Non `Object` object instances with different constructors are not equal. + if (objCtor != othCtor && + ('constructor' in object && 'constructor' in other) && + !(typeof objCtor == 'function' && objCtor instanceof objCtor && + typeof othCtor == 'function' && othCtor instanceof othCtor)) { + result = false; + } + } + stack['delete'](object); + stack['delete'](other); + return result; + } + + /** + * Creates an array of own enumerable property names and symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeys(object) { + return baseGetAllKeys(object, keys, getSymbols); + } + + /** + * Creates an array of own and inherited enumerable property names and + * symbols of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names and symbols. + */ + function getAllKeysIn(object) { + return baseGetAllKeys(object, keysIn, getSymbolsIn); + } + + /** + * Gets metadata for `func`. + * + * @private + * @param {Function} func The function to query. + * @returns {*} Returns the metadata for `func`. + */ + var getData = !metaMap ? noop : function(func) { + return metaMap.get(func); + }; + + /** + * Gets the name of `func`. + * + * @private + * @param {Function} func The function to query. + * @returns {string} Returns the function name. + */ + function getFuncName(func) { + var result = (func.name + ''), + array = realNames[result], + length = hasOwnProperty.call(realNames, result) ? array.length : 0; + + while (length--) { + var data = array[length], + otherFunc = data.func; + if (otherFunc == null || otherFunc == func) { + return data.name; + } + } + return result; + } + + /** + * Gets the argument placeholder value for `func`. + * + * @private + * @param {Function} func The function to inspect. + * @returns {*} Returns the placeholder value. + */ + function getHolder(func) { + var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func; + return object.placeholder; + } + + /** + * Gets the appropriate "iteratee" function. If `_.iteratee` is customized, + * this function returns the custom method, otherwise it returns `baseIteratee`. + * If arguments are provided, the chosen function is invoked with them and + * its result is returned. + * + * @private + * @param {*} [value] The value to convert to an iteratee. + * @param {number} [arity] The arity of the created iteratee. + * @returns {Function} Returns the chosen function or its result. + */ + function getIteratee() { + var result = lodash.iteratee || iteratee; + result = result === iteratee ? baseIteratee : result; + return arguments.length ? result(arguments[0], arguments[1]) : result; + } + + /** + * Gets the data for `map`. + * + * @private + * @param {Object} map The map to query. + * @param {string} key The reference key. + * @returns {*} Returns the map data. + */ + function getMapData(map, key) { + var data = map.__data__; + return isKeyable(key) + ? data[typeof key == 'string' ? 'string' : 'hash'] + : data.map; + } + + /** + * Gets the property names, values, and compare flags of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the match data of `object`. + */ + function getMatchData(object) { + var result = keys(object), + length = result.length; + + while (length--) { + var key = result[length], + value = object[key]; + + result[length] = [key, value, isStrictComparable(value)]; + } + return result; + } + + /** + * Gets the native function at `key` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {string} key The key of the method to get. + * @returns {*} Returns the function if it's native, else `undefined`. + */ + function getNative(object, key) { + var value = getValue(object, key); + return baseIsNative(value) ? value : undefined; + } + + /** + * Creates an array of the own enumerable symbol properties of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray; + + /** + * Creates an array of the own and inherited enumerable symbol properties + * of `object`. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of symbols. + */ + var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) { + var result = []; + while (object) { + arrayPush(result, getSymbols(object)); + object = getPrototype(object); + } + return result; + }; + + /** + * Gets the `toStringTag` of `value`. + * + * @private + * @param {*} value The value to query. + * @returns {string} Returns the `toStringTag`. + */ + var getTag = baseGetTag; + + // Fallback for data views, maps, sets, and weak maps in IE 11, + // for data views in Edge < 14, and promises in Node.js. + if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || + (Map && getTag(new Map) != mapTag) || + (Promise && getTag(Promise.resolve()) != promiseTag) || + (Set && getTag(new Set) != setTag) || + (WeakMap && getTag(new WeakMap) != weakMapTag)) { + getTag = function(value) { + var result = objectToString.call(value), + Ctor = result == objectTag ? value.constructor : undefined, + ctorString = Ctor ? toSource(Ctor) : undefined; + + if (ctorString) { + switch (ctorString) { + case dataViewCtorString: return dataViewTag; + case mapCtorString: return mapTag; + case promiseCtorString: return promiseTag; + case setCtorString: return setTag; + case weakMapCtorString: return weakMapTag; + } + } + return result; + }; + } + + /** + * Gets the view, applying any `transforms` to the `start` and `end` positions. + * + * @private + * @param {number} start The start of the view. + * @param {number} end The end of the view. + * @param {Array} transforms The transformations to apply to the view. + * @returns {Object} Returns an object containing the `start` and `end` + * positions of the view. + */ + function getView(start, end, transforms) { + var index = -1, + length = transforms.length; + + while (++index < length) { + var data = transforms[index], + size = data.size; + + switch (data.type) { + case 'drop': start += size; break; + case 'dropRight': end -= size; break; + case 'take': end = nativeMin(end, start + size); break; + case 'takeRight': start = nativeMax(start, end - size); break; + } + } + return { 'start': start, 'end': end }; + } + + /** + * Extracts wrapper details from the `source` body comment. + * + * @private + * @param {string} source The source to inspect. + * @returns {Array} Returns the wrapper details. + */ + function getWrapDetails(source) { + var match = source.match(reWrapDetails); + return match ? match[1].split(reSplitDetails) : []; + } + + /** + * Checks if `path` exists on `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @param {Function} hasFunc The function to check properties. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + */ + function hasPath(object, path, hasFunc) { + path = isKey(path, object) ? [path] : castPath(path); + + var result, + index = -1, + length = path.length; + + while (++index < length) { + var key = toKey(path[index]); + if (!(result = object != null && hasFunc(object, key))) { + break; + } + object = object[key]; + } + if (result) { + return result; + } + var length = object ? object.length : 0; + return !!length && isLength(length) && isIndex(key, length) && + (isArray(object) || isArguments(object)); + } + + /** + * Initializes an array clone. + * + * @private + * @param {Array} array The array to clone. + * @returns {Array} Returns the initialized clone. + */ + function initCloneArray(array) { + var length = array.length, + result = array.constructor(length); + + // Add properties assigned by `RegExp#exec`. + if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { + result.index = array.index; + result.input = array.input; + } + return result; + } + + /** + * Initializes an object clone. + * + * @private + * @param {Object} object The object to clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneObject(object) { + return (typeof object.constructor == 'function' && !isPrototype(object)) + ? baseCreate(getPrototype(object)) + : {}; + } + + /** + * Initializes an object clone based on its `toStringTag`. + * + * **Note:** This function only supports cloning values with tags of + * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. + * + * @private + * @param {Object} object The object to clone. + * @param {string} tag The `toStringTag` of the object to clone. + * @param {Function} cloneFunc The function to clone values. + * @param {boolean} [isDeep] Specify a deep clone. + * @returns {Object} Returns the initialized clone. + */ + function initCloneByTag(object, tag, cloneFunc, isDeep) { + var Ctor = object.constructor; + switch (tag) { + case arrayBufferTag: + return cloneArrayBuffer(object); + + case boolTag: + case dateTag: + return new Ctor(+object); + + case dataViewTag: + return cloneDataView(object, isDeep); + + case float32Tag: case float64Tag: + case int8Tag: case int16Tag: case int32Tag: + case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: + return cloneTypedArray(object, isDeep); + + case mapTag: + return cloneMap(object, isDeep, cloneFunc); + + case numberTag: + case stringTag: + return new Ctor(object); + + case regexpTag: + return cloneRegExp(object); + + case setTag: + return cloneSet(object, isDeep, cloneFunc); + + case symbolTag: + return cloneSymbol(object); + } + } + + /** + * Inserts wrapper `details` in a comment at the top of the `source` body. + * + * @private + * @param {string} source The source to modify. + * @returns {Array} details The details to insert. + * @returns {string} Returns the modified source. + */ + function insertWrapDetails(source, details) { + var length = details.length, + lastIndex = length - 1; + + details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex]; + details = details.join(length > 2 ? ', ' : ' '); + return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n'); + } + + /** + * Checks if `value` is a flattenable `arguments` object or array. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`. + */ + function isFlattenable(value) { + return isArray(value) || isArguments(value) || + !!(spreadableSymbol && value && value[spreadableSymbol]); + } + + /** + * Checks if `value` is a valid array-like index. + * + * @private + * @param {*} value The value to check. + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. + */ + function isIndex(value, length) { + length = length == null ? MAX_SAFE_INTEGER : length; + return !!length && + (typeof value == 'number' || reIsUint.test(value)) && + (value > -1 && value % 1 == 0 && value < length); + } + + /** + * Checks if the given arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, + * else `false`. + */ + function isIterateeCall(value, index, object) { + if (!isObject(object)) { + return false; + } + var type = typeof index; + if (type == 'number' + ? (isArrayLike(object) && isIndex(index, object.length)) + : (type == 'string' && index in object) + ) { + return eq(object[index], value); + } + return false; + } + + /** + * Checks if `value` is a property name and not a property path. + * + * @private + * @param {*} value The value to check. + * @param {Object} [object] The object to query keys on. + * @returns {boolean} Returns `true` if `value` is a property name, else `false`. + */ + function isKey(value, object) { + if (isArray(value)) { + return false; + } + var type = typeof value; + if (type == 'number' || type == 'symbol' || type == 'boolean' || + value == null || isSymbol(value)) { + return true; + } + return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || + (object != null && value in Object(object)); + } + + /** + * Checks if `value` is suitable for use as unique object key. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is suitable, else `false`. + */ + function isKeyable(value) { + var type = typeof value; + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') + ? (value !== '__proto__') + : (value === null); + } + + /** + * Checks if `func` has a lazy counterpart. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` has a lazy counterpart, + * else `false`. + */ + function isLaziable(func) { + var funcName = getFuncName(func), + other = lodash[funcName]; + + if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) { + return false; + } + if (func === other) { + return true; + } + var data = getData(other); + return !!data && func === data[0]; + } + + /** + * Checks if `func` has its source masked. + * + * @private + * @param {Function} func The function to check. + * @returns {boolean} Returns `true` if `func` is masked, else `false`. + */ + function isMasked(func) { + return !!maskSrcKey && (maskSrcKey in func); + } + + /** + * Checks if `func` is capable of being masked. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `func` is maskable, else `false`. + */ + var isMaskable = coreJsData ? isFunction : stubFalse; + + /** + * Checks if `value` is likely a prototype object. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. + */ + function isPrototype(value) { + var Ctor = value && value.constructor, + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; + + return value === proto; + } + + /** + * Checks if `value` is suitable for strict equality comparisons, i.e. `===`. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` if suitable for strict + * equality comparisons, else `false`. + */ + function isStrictComparable(value) { + return value === value && !isObject(value); + } + + /** + * A specialized version of `matchesProperty` for source values suitable + * for strict equality comparisons, i.e. `===`. + * + * @private + * @param {string} key The key of the property to get. + * @param {*} srcValue The value to match. + * @returns {Function} Returns the new spec function. + */ + function matchesStrictComparable(key, srcValue) { + return function(object) { + if (object == null) { + return false; + } + return object[key] === srcValue && + (srcValue !== undefined || (key in Object(object))); + }; + } + + /** + * Merges the function metadata of `source` into `data`. + * + * Merging metadata reduces the number of wrappers used to invoke a function. + * This is possible because methods like `_.bind`, `_.curry`, and `_.partial` + * may be applied regardless of execution order. Methods like `_.ary` and + * `_.rearg` modify function arguments, making the order in which they are + * executed important, preventing the merging of metadata. However, we make + * an exception for a safe combined case where curried functions have `_.ary` + * and or `_.rearg` applied. + * + * @private + * @param {Array} data The destination metadata. + * @param {Array} source The source metadata. + * @returns {Array} Returns `data`. + */ + function mergeData(data, source) { + var bitmask = data[1], + srcBitmask = source[1], + newBitmask = bitmask | srcBitmask, + isCommon = newBitmask < (BIND_FLAG | BIND_KEY_FLAG | ARY_FLAG); + + var isCombo = + ((srcBitmask == ARY_FLAG) && (bitmask == CURRY_FLAG)) || + ((srcBitmask == ARY_FLAG) && (bitmask == REARG_FLAG) && (data[7].length <= source[8])) || + ((srcBitmask == (ARY_FLAG | REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == CURRY_FLAG)); + + // Exit early if metadata can't be merged. + if (!(isCommon || isCombo)) { + return data; + } + // Use source `thisArg` if available. + if (srcBitmask & BIND_FLAG) { + data[2] = source[2]; + // Set when currying a bound function. + newBitmask |= bitmask & BIND_FLAG ? 0 : CURRY_BOUND_FLAG; + } + // Compose partial arguments. + var value = source[3]; + if (value) { + var partials = data[3]; + data[3] = partials ? composeArgs(partials, value, source[4]) : value; + data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4]; + } + // Compose partial right arguments. + value = source[5]; + if (value) { + partials = data[5]; + data[5] = partials ? composeArgsRight(partials, value, source[6]) : value; + data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6]; + } + // Use source `argPos` if available. + value = source[7]; + if (value) { + data[7] = value; + } + // Use source `ary` if it's smaller. + if (srcBitmask & ARY_FLAG) { + data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]); + } + // Use source `arity` if one is not provided. + if (data[9] == null) { + data[9] = source[9]; + } + // Use source `func` and merge bitmasks. + data[0] = source[0]; + data[1] = newBitmask; + + return data; + } + + /** + * Used by `_.defaultsDeep` to customize its `_.merge` use. + * + * @private + * @param {*} objValue The destination value. + * @param {*} srcValue The source value. + * @param {string} key The key of the property to merge. + * @param {Object} object The parent object of `objValue`. + * @param {Object} source The parent object of `srcValue`. + * @param {Object} [stack] Tracks traversed source values and their merged + * counterparts. + * @returns {*} Returns the value to assign. + */ + function mergeDefaults(objValue, srcValue, key, object, source, stack) { + if (isObject(objValue) && isObject(srcValue)) { + // Recursively merge objects and arrays (susceptible to call stack limits). + stack.set(srcValue, objValue); + baseMerge(objValue, srcValue, undefined, mergeDefaults, stack); + stack['delete'](srcValue); + } + return objValue; + } + + /** + * This function is like + * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * except that it includes inherited enumerable properties. + * + * @private + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + */ + function nativeKeysIn(object) { + var result = []; + if (object != null) { + for (var key in Object(object)) { + result.push(key); + } + } + return result; + } + + /** + * Gets the parent value at `path` of `object`. + * + * @private + * @param {Object} object The object to query. + * @param {Array} path The path to get the parent value of. + * @returns {*} Returns the parent value. + */ + function parent(object, path) { + return path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1)); + } + + /** + * Reorder `array` according to the specified indexes where the element at + * the first index is assigned as the first element, the element at + * the second index is assigned as the second element, and so on. + * + * @private + * @param {Array} array The array to reorder. + * @param {Array} indexes The arranged array indexes. + * @returns {Array} Returns `array`. + */ + function reorder(array, indexes) { + var arrLength = array.length, + length = nativeMin(indexes.length, arrLength), + oldArray = copyArray(array); + + while (length--) { + var index = indexes[length]; + array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined; + } + return array; + } + + /** + * Sets metadata for `func`. + * + * **Note:** If this function becomes hot, i.e. is invoked a lot in a short + * period of time, it will trip its breaker and transition to an identity + * function to avoid garbage collection pauses in V8. See + * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070) + * for more details. + * + * @private + * @param {Function} func The function to associate metadata with. + * @param {*} data The metadata. + * @returns {Function} Returns `func`. + */ + var setData = (function() { + var count = 0, + lastCalled = 0; + + return function(key, value) { + var stamp = now(), + remaining = HOT_SPAN - (stamp - lastCalled); + + lastCalled = stamp; + if (remaining > 0) { + if (++count >= HOT_COUNT) { + return key; + } + } else { + count = 0; + } + return baseSetData(key, value); + }; + }()); + + /** + * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout). + * + * @private + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @returns {number|Object} Returns the timer id or timeout object. + */ + var setTimeout = ctxSetTimeout || function(func, wait) { + return root.setTimeout(func, wait); + }; + + /** + * Sets the `toString` method of `wrapper` to mimic the source of `reference` + * with wrapper details in a comment at the top of the source body. + * + * @private + * @param {Function} wrapper The function to modify. + * @param {Function} reference The reference function. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @returns {Function} Returns `wrapper`. + */ + var setWrapToString = !defineProperty ? identity : function(wrapper, reference, bitmask) { + var source = (reference + ''); + return defineProperty(wrapper, 'toString', { + 'configurable': true, + 'enumerable': false, + 'value': constant(insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask))) + }); + }; + + /** + * Converts `string` to a property path array. + * + * @private + * @param {string} string The string to convert. + * @returns {Array} Returns the property path array. + */ + var stringToPath = memoize(function(string) { + string = toString(string); + + var result = []; + if (reLeadingDot.test(string)) { + result.push(''); + } + string.replace(rePropName, function(match, number, quote, string) { + result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match)); + }); + return result; + }); + + /** + * Converts `value` to a string key if it's not a string or symbol. + * + * @private + * @param {*} value The value to inspect. + * @returns {string|symbol} Returns the key. + */ + function toKey(value) { + if (typeof value == 'string' || isSymbol(value)) { + return value; + } + var result = (value + ''); + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; + } + + /** + * Converts `func` to its source code. + * + * @private + * @param {Function} func The function to process. + * @returns {string} Returns the source code. + */ + function toSource(func) { + if (func != null) { + try { + return funcToString.call(func); + } catch (e) {} + try { + return (func + ''); + } catch (e) {} + } + return ''; + } + + /** + * Updates wrapper `details` based on `bitmask` flags. + * + * @private + * @returns {Array} details The details to modify. + * @param {number} bitmask The bitmask flags. See `createWrap` for more details. + * @returns {Array} Returns `details`. + */ + function updateWrapDetails(details, bitmask) { + arrayEach(wrapFlags, function(pair) { + var value = '_.' + pair[0]; + if ((bitmask & pair[1]) && !arrayIncludes(details, value)) { + details.push(value); + } + }); + return details.sort(); + } + + /** + * Creates a clone of `wrapper`. + * + * @private + * @param {Object} wrapper The wrapper to clone. + * @returns {Object} Returns the cloned wrapper. + */ + function wrapperClone(wrapper) { + if (wrapper instanceof LazyWrapper) { + return wrapper.clone(); + } + var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__); + result.__actions__ = copyArray(wrapper.__actions__); + result.__index__ = wrapper.__index__; + result.__values__ = wrapper.__values__; + return result; + } + + /*------------------------------------------------------------------------*/ + + /** + * Creates an array of elements split into groups the length of `size`. + * If `array` can't be split evenly, the final chunk will be the remaining + * elements. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to process. + * @param {number} [size=1] The length of each chunk + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the new array of chunks. + * @example + * + * _.chunk(['a', 'b', 'c', 'd'], 2); + * // => [['a', 'b'], ['c', 'd']] + * + * _.chunk(['a', 'b', 'c', 'd'], 3); + * // => [['a', 'b', 'c'], ['d']] + */ + function chunk(array, size, guard) { + if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) { + size = 1; + } else { + size = nativeMax(toInteger(size), 0); + } + var length = array ? array.length : 0; + if (!length || size < 1) { + return []; + } + var index = 0, + resIndex = 0, + result = Array(nativeCeil(length / size)); + + while (index < length) { + result[resIndex++] = baseSlice(array, index, (index += size)); + } + return result; + } + + /** + * Creates an array with all falsey values removed. The values `false`, `null`, + * `0`, `""`, `undefined`, and `NaN` are falsey. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to compact. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.compact([0, 1, false, 2, '', 3]); + * // => [1, 2, 3] + */ + function compact(array) { + var index = -1, + length = array ? array.length : 0, + resIndex = 0, + result = []; + + while (++index < length) { + var value = array[index]; + if (value) { + result[resIndex++] = value; + } + } + return result; + } + + /** + * Creates a new array concatenating `array` with any additional arrays + * and/or values. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to concatenate. + * @param {...*} [values] The values to concatenate. + * @returns {Array} Returns the new concatenated array. + * @example + * + * var array = [1]; + * var other = _.concat(array, 2, [3], [[4]]); + * + * console.log(other); + * // => [1, 2, 3, [4]] + * + * console.log(array); + * // => [1] + */ + function concat() { + var length = arguments.length, + args = Array(length ? length - 1 : 0), + array = arguments[0], + index = length; + + while (index--) { + args[index - 1] = arguments[index]; + } + return length + ? arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1)) + : []; + } + + /** + * Creates an array of `array` values not included in the other given arrays + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. The order of result values is determined by the + * order they occur in the first array. + * + * **Note:** Unlike `_.pullAll`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @returns {Array} Returns the new array of filtered values. + * @see _.without, _.xor + * @example + * + * _.difference([2, 1], [2, 3]); + * // => [1] + */ + var difference = baseRest(function(array, values) { + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) + : []; + }); + + /** + * This method is like `_.difference` except that it accepts `iteratee` which + * is invoked for each element of `array` and `values` to generate the criterion + * by which they're compared. Result values are chosen from the first array. + * The iteratee is invoked with one argument: (value). + * + * **Note:** Unlike `_.pullAllBy`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [1.2] + * + * // The `_.property` iteratee shorthand. + * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x'); + * // => [{ 'x': 2 }] + */ + var differenceBy = baseRest(function(array, values) { + var iteratee = last(values); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) + : []; + }); + + /** + * This method is like `_.difference` except that it accepts `comparator` + * which is invoked to compare elements of `array` to `values`. Result values + * are chosen from the first array. The comparator is invoked with two arguments: + * (arrVal, othVal). + * + * **Note:** Unlike `_.pullAllWith`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...Array} [values] The values to exclude. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * + * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual); + * // => [{ 'x': 2, 'y': 1 }] + */ + var differenceWith = baseRest(function(array, values) { + var comparator = last(values); + if (isArrayLikeObject(comparator)) { + comparator = undefined; + } + return isArrayLikeObject(array) + ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator) + : []; + }); + + /** + * Creates a slice of `array` with `n` elements dropped from the beginning. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.drop([1, 2, 3]); + * // => [2, 3] + * + * _.drop([1, 2, 3], 2); + * // => [3] + * + * _.drop([1, 2, 3], 5); + * // => [] + * + * _.drop([1, 2, 3], 0); + * // => [1, 2, 3] + */ + function drop(array, n, guard) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + return baseSlice(array, n < 0 ? 0 : n, length); + } + + /** + * Creates a slice of `array` with `n` elements dropped from the end. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to drop. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.dropRight([1, 2, 3]); + * // => [1, 2] + * + * _.dropRight([1, 2, 3], 2); + * // => [1] + * + * _.dropRight([1, 2, 3], 5); + * // => [] + * + * _.dropRight([1, 2, 3], 0); + * // => [1, 2, 3] + */ + function dropRight(array, n, guard) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + n = length - n; + return baseSlice(array, 0, n < 0 ? 0 : n); + } + + /** + * Creates a slice of `array` excluding elements dropped from the end. + * Elements are dropped until `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.dropRightWhile(users, function(o) { return !o.active; }); + * // => objects for ['barney'] + * + * // The `_.matches` iteratee shorthand. + * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false }); + * // => objects for ['barney', 'fred'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.dropRightWhile(users, ['active', false]); + * // => objects for ['barney'] + * + * // The `_.property` iteratee shorthand. + * _.dropRightWhile(users, 'active'); + * // => objects for ['barney', 'fred', 'pebbles'] + */ + function dropRightWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), true, true) + : []; + } + + /** + * Creates a slice of `array` excluding elements dropped from the beginning. + * Elements are dropped until `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.dropWhile(users, function(o) { return !o.active; }); + * // => objects for ['pebbles'] + * + * // The `_.matches` iteratee shorthand. + * _.dropWhile(users, { 'user': 'barney', 'active': false }); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.dropWhile(users, ['active', false]); + * // => objects for ['pebbles'] + * + * // The `_.property` iteratee shorthand. + * _.dropWhile(users, 'active'); + * // => objects for ['barney', 'fred', 'pebbles'] + */ + function dropWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), true) + : []; + } + + /** + * Fills elements of `array` with `value` from `start` up to, but not + * including, `end`. + * + * **Note:** This method mutates `array`. + * + * @static + * @memberOf _ + * @since 3.2.0 + * @category Array + * @param {Array} array The array to fill. + * @param {*} value The value to fill `array` with. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns `array`. + * @example + * + * var array = [1, 2, 3]; + * + * _.fill(array, 'a'); + * console.log(array); + * // => ['a', 'a', 'a'] + * + * _.fill(Array(3), 2); + * // => [2, 2, 2] + * + * _.fill([4, 6, 8, 10], '*', 1, 3); + * // => [4, '*', '*', 10] + */ + function fill(array, value, start, end) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + if (start && typeof start != 'number' && isIterateeCall(array, value, start)) { + start = 0; + end = length; + } + return baseFill(array, value, start, end); + } + + /** + * This method is like `_.find` except that it returns the index of the first + * element `predicate` returns truthy for instead of the element itself. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @param {number} [fromIndex=0] The index to search from. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.findIndex(users, function(o) { return o.user == 'barney'; }); + * // => 0 + * + * // The `_.matches` iteratee shorthand. + * _.findIndex(users, { 'user': 'fred', 'active': false }); + * // => 1 + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findIndex(users, ['active', false]); + * // => 0 + * + * // The `_.property` iteratee shorthand. + * _.findIndex(users, 'active'); + * // => 2 + */ + function findIndex(array, predicate, fromIndex) { + var length = array ? array.length : 0; + if (!length) { + return -1; + } + var index = fromIndex == null ? 0 : toInteger(fromIndex); + if (index < 0) { + index = nativeMax(length + index, 0); + } + return baseFindIndex(array, getIteratee(predicate, 3), index); + } + + /** + * This method is like `_.findIndex` except that it iterates over elements + * of `collection` from right to left. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @param {number} [fromIndex=array.length-1] The index to search from. + * @returns {number} Returns the index of the found element, else `-1`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; }); + * // => 2 + * + * // The `_.matches` iteratee shorthand. + * _.findLastIndex(users, { 'user': 'barney', 'active': true }); + * // => 0 + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findLastIndex(users, ['active', false]); + * // => 2 + * + * // The `_.property` iteratee shorthand. + * _.findLastIndex(users, 'active'); + * // => 0 + */ + function findLastIndex(array, predicate, fromIndex) { + var length = array ? array.length : 0; + if (!length) { + return -1; + } + var index = length - 1; + if (fromIndex !== undefined) { + index = toInteger(fromIndex); + index = fromIndex < 0 + ? nativeMax(length + index, 0) + : nativeMin(index, length - 1); + } + return baseFindIndex(array, getIteratee(predicate, 3), index, true); + } + + /** + * Flattens `array` a single level deep. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to flatten. + * @returns {Array} Returns the new flattened array. + * @example + * + * _.flatten([1, [2, [3, [4]], 5]]); + * // => [1, 2, [3, [4]], 5] + */ + function flatten(array) { + var length = array ? array.length : 0; + return length ? baseFlatten(array, 1) : []; + } + + /** + * Recursively flattens `array`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to flatten. + * @returns {Array} Returns the new flattened array. + * @example + * + * _.flattenDeep([1, [2, [3, [4]], 5]]); + * // => [1, 2, 3, 4, 5] + */ + function flattenDeep(array) { + var length = array ? array.length : 0; + return length ? baseFlatten(array, INFINITY) : []; + } + + /** + * Recursively flatten `array` up to `depth` times. + * + * @static + * @memberOf _ + * @since 4.4.0 + * @category Array + * @param {Array} array The array to flatten. + * @param {number} [depth=1] The maximum recursion depth. + * @returns {Array} Returns the new flattened array. + * @example + * + * var array = [1, [2, [3, [4]], 5]]; + * + * _.flattenDepth(array, 1); + * // => [1, 2, [3, [4]], 5] + * + * _.flattenDepth(array, 2); + * // => [1, 2, 3, [4], 5] + */ + function flattenDepth(array, depth) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + depth = depth === undefined ? 1 : toInteger(depth); + return baseFlatten(array, depth); + } + + /** + * The inverse of `_.toPairs`; this method returns an object composed + * from key-value `pairs`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} pairs The key-value pairs. + * @returns {Object} Returns the new object. + * @example + * + * _.fromPairs([['a', 1], ['b', 2]]); + * // => { 'a': 1, 'b': 2 } + */ + function fromPairs(pairs) { + var index = -1, + length = pairs ? pairs.length : 0, + result = {}; + + while (++index < length) { + var pair = pairs[index]; + result[pair[0]] = pair[1]; + } + return result; + } + + /** + * Gets the first element of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @alias first + * @category Array + * @param {Array} array The array to query. + * @returns {*} Returns the first element of `array`. + * @example + * + * _.head([1, 2, 3]); + * // => 1 + * + * _.head([]); + * // => undefined + */ + function head(array) { + return (array && array.length) ? array[0] : undefined; + } + + /** + * Gets the index at which the first occurrence of `value` is found in `array` + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. If `fromIndex` is negative, it's used as the + * offset from the end of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} [fromIndex=0] The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.indexOf([1, 2, 1, 2], 2); + * // => 1 + * + * // Search from the `fromIndex`. + * _.indexOf([1, 2, 1, 2], 2, 2); + * // => 3 + */ + function indexOf(array, value, fromIndex) { + var length = array ? array.length : 0; + if (!length) { + return -1; + } + var index = fromIndex == null ? 0 : toInteger(fromIndex); + if (index < 0) { + index = nativeMax(length + index, 0); + } + return baseIndexOf(array, value, index); + } + + /** + * Gets all but the last element of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to query. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.initial([1, 2, 3]); + * // => [1, 2] + */ + function initial(array) { + var length = array ? array.length : 0; + return length ? baseSlice(array, 0, -1) : []; + } + + /** + * Creates an array of unique values that are included in all given arrays + * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. The order of result values is determined by the + * order they occur in the first array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of intersecting values. + * @example + * + * _.intersection([2, 1], [2, 3]); + * // => [2] + */ + var intersection = baseRest(function(arrays) { + var mapped = arrayMap(arrays, castArrayLikeObject); + return (mapped.length && mapped[0] === arrays[0]) + ? baseIntersection(mapped) + : []; + }); + + /** + * This method is like `_.intersection` except that it accepts `iteratee` + * which is invoked for each element of each `arrays` to generate the criterion + * by which they're compared. Result values are chosen from the first array. + * The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Array} Returns the new array of intersecting values. + * @example + * + * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [2.1] + * + * // The `_.property` iteratee shorthand. + * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }] + */ + var intersectionBy = baseRest(function(arrays) { + var iteratee = last(arrays), + mapped = arrayMap(arrays, castArrayLikeObject); + + if (iteratee === last(mapped)) { + iteratee = undefined; + } else { + mapped.pop(); + } + return (mapped.length && mapped[0] === arrays[0]) + ? baseIntersection(mapped, getIteratee(iteratee, 2)) + : []; + }); + + /** + * This method is like `_.intersection` except that it accepts `comparator` + * which is invoked to compare elements of `arrays`. Result values are chosen + * from the first array. The comparator is invoked with two arguments: + * (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of intersecting values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.intersectionWith(objects, others, _.isEqual); + * // => [{ 'x': 1, 'y': 2 }] + */ + var intersectionWith = baseRest(function(arrays) { + var comparator = last(arrays), + mapped = arrayMap(arrays, castArrayLikeObject); + + if (comparator === last(mapped)) { + comparator = undefined; + } else { + mapped.pop(); + } + return (mapped.length && mapped[0] === arrays[0]) + ? baseIntersection(mapped, undefined, comparator) + : []; + }); + + /** + * Converts all elements in `array` into a string separated by `separator`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to convert. + * @param {string} [separator=','] The element separator. + * @returns {string} Returns the joined string. + * @example + * + * _.join(['a', 'b', 'c'], '~'); + * // => 'a~b~c' + */ + function join(array, separator) { + return array ? nativeJoin.call(array, separator) : ''; + } + + /** + * Gets the last element of `array`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to query. + * @returns {*} Returns the last element of `array`. + * @example + * + * _.last([1, 2, 3]); + * // => 3 + */ + function last(array) { + var length = array ? array.length : 0; + return length ? array[length - 1] : undefined; + } + + /** + * This method is like `_.indexOf` except that it iterates over elements of + * `array` from right to left. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @param {number} [fromIndex=array.length-1] The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.lastIndexOf([1, 2, 1, 2], 2); + * // => 3 + * + * // Search from the `fromIndex`. + * _.lastIndexOf([1, 2, 1, 2], 2, 2); + * // => 1 + */ + function lastIndexOf(array, value, fromIndex) { + var length = array ? array.length : 0; + if (!length) { + return -1; + } + var index = length; + if (fromIndex !== undefined) { + index = toInteger(fromIndex); + index = ( + index < 0 + ? nativeMax(length + index, 0) + : nativeMin(index, length - 1) + ) + 1; + } + if (value !== value) { + return baseFindIndex(array, baseIsNaN, index - 1, true); + } + while (index--) { + if (array[index] === value) { + return index; + } + } + return -1; + } + + /** + * Gets the element at index `n` of `array`. If `n` is negative, the nth + * element from the end is returned. + * + * @static + * @memberOf _ + * @since 4.11.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=0] The index of the element to return. + * @returns {*} Returns the nth element of `array`. + * @example + * + * var array = ['a', 'b', 'c', 'd']; + * + * _.nth(array, 1); + * // => 'b' + * + * _.nth(array, -2); + * // => 'c'; + */ + function nth(array, n) { + return (array && array.length) ? baseNth(array, toInteger(n)) : undefined; + } + + /** + * Removes all given values from `array` using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove` + * to remove elements from an array by predicate. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {...*} [values] The values to remove. + * @returns {Array} Returns `array`. + * @example + * + * var array = ['a', 'b', 'c', 'a', 'b', 'c']; + * + * _.pull(array, 'a', 'c'); + * console.log(array); + * // => ['b', 'b'] + */ + var pull = baseRest(pullAll); + + /** + * This method is like `_.pull` except that it accepts an array of values to remove. + * + * **Note:** Unlike `_.difference`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @returns {Array} Returns `array`. + * @example + * + * var array = ['a', 'b', 'c', 'a', 'b', 'c']; + * + * _.pullAll(array, ['a', 'c']); + * console.log(array); + * // => ['b', 'b'] + */ + function pullAll(array, values) { + return (array && array.length && values && values.length) + ? basePullAll(array, values) + : array; + } + + /** + * This method is like `_.pullAll` except that it accepts `iteratee` which is + * invoked for each element of `array` and `values` to generate the criterion + * by which they're compared. The iteratee is invoked with one argument: (value). + * + * **Note:** Unlike `_.differenceBy`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @param {Function} [iteratee=_.identity] + * The iteratee invoked per element. + * @returns {Array} Returns `array`. + * @example + * + * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }]; + * + * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x'); + * console.log(array); + * // => [{ 'x': 2 }] + */ + function pullAllBy(array, values, iteratee) { + return (array && array.length && values && values.length) + ? basePullAll(array, values, getIteratee(iteratee, 2)) + : array; + } + + /** + * This method is like `_.pullAll` except that it accepts `comparator` which + * is invoked to compare elements of `array` to `values`. The comparator is + * invoked with two arguments: (arrVal, othVal). + * + * **Note:** Unlike `_.differenceWith`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Array} values The values to remove. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns `array`. + * @example + * + * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }]; + * + * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual); + * console.log(array); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }] + */ + function pullAllWith(array, values, comparator) { + return (array && array.length && values && values.length) + ? basePullAll(array, values, undefined, comparator) + : array; + } + + /** + * Removes elements from `array` corresponding to `indexes` and returns an + * array of removed elements. + * + * **Note:** Unlike `_.at`, this method mutates `array`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {...(number|number[])} [indexes] The indexes of elements to remove. + * @returns {Array} Returns the new array of removed elements. + * @example + * + * var array = ['a', 'b', 'c', 'd']; + * var pulled = _.pullAt(array, [1, 3]); + * + * console.log(array); + * // => ['a', 'c'] + * + * console.log(pulled); + * // => ['b', 'd'] + */ + var pullAt = baseRest(function(array, indexes) { + indexes = baseFlatten(indexes, 1); + + var length = array ? array.length : 0, + result = baseAt(array, indexes); + + basePullAt(array, arrayMap(indexes, function(index) { + return isIndex(index, length) ? +index : index; + }).sort(compareAscending)); + + return result; + }); + + /** + * Removes all elements from `array` that `predicate` returns truthy for + * and returns an array of the removed elements. The predicate is invoked + * with three arguments: (value, index, array). + * + * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull` + * to pull elements from an array by value. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Array + * @param {Array} array The array to modify. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the new array of removed elements. + * @example + * + * var array = [1, 2, 3, 4]; + * var evens = _.remove(array, function(n) { + * return n % 2 == 0; + * }); + * + * console.log(array); + * // => [1, 3] + * + * console.log(evens); + * // => [2, 4] + */ + function remove(array, predicate) { + var result = []; + if (!(array && array.length)) { + return result; + } + var index = -1, + indexes = [], + length = array.length; + + predicate = getIteratee(predicate, 3); + while (++index < length) { + var value = array[index]; + if (predicate(value, index, array)) { + result.push(value); + indexes.push(index); + } + } + basePullAt(array, indexes); + return result; + } + + /** + * Reverses `array` so that the first element becomes the last, the second + * element becomes the second to last, and so on. + * + * **Note:** This method mutates `array` and is based on + * [`Array#reverse`](https://mdn.io/Array/reverse). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to modify. + * @returns {Array} Returns `array`. + * @example + * + * var array = [1, 2, 3]; + * + * _.reverse(array); + * // => [3, 2, 1] + * + * console.log(array); + * // => [3, 2, 1] + */ + function reverse(array) { + return array ? nativeReverse.call(array) : array; + } + + /** + * Creates a slice of `array` from `start` up to, but not including, `end`. + * + * **Note:** This method is used instead of + * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are + * returned. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to slice. + * @param {number} [start=0] The start position. + * @param {number} [end=array.length] The end position. + * @returns {Array} Returns the slice of `array`. + */ + function slice(array, start, end) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + if (end && typeof end != 'number' && isIterateeCall(array, start, end)) { + start = 0; + end = length; + } + else { + start = start == null ? 0 : toInteger(start); + end = end === undefined ? length : toInteger(end); + } + return baseSlice(array, start, end); + } + + /** + * Uses a binary search to determine the lowest index at which `value` + * should be inserted into `array` in order to maintain its sort order. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * _.sortedIndex([30, 50], 40); + * // => 1 + */ + function sortedIndex(array, value) { + return baseSortedIndex(array, value); + } + + /** + * This method is like `_.sortedIndex` except that it accepts `iteratee` + * which is invoked for `value` and each element of `array` to compute their + * sort ranking. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} [iteratee=_.identity] + * The iteratee invoked per element. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * var objects = [{ 'x': 4 }, { 'x': 5 }]; + * + * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); + * // => 0 + * + * // The `_.property` iteratee shorthand. + * _.sortedIndexBy(objects, { 'x': 4 }, 'x'); + * // => 0 + */ + function sortedIndexBy(array, value, iteratee) { + return baseSortedIndexBy(array, value, getIteratee(iteratee, 2)); + } + + /** + * This method is like `_.indexOf` except that it performs a binary + * search on a sorted `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.sortedIndexOf([4, 5, 5, 5, 6], 5); + * // => 1 + */ + function sortedIndexOf(array, value) { + var length = array ? array.length : 0; + if (length) { + var index = baseSortedIndex(array, value); + if (index < length && eq(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * This method is like `_.sortedIndex` except that it returns the highest + * index at which `value` should be inserted into `array` in order to + * maintain its sort order. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * _.sortedLastIndex([4, 5, 5, 5, 6], 5); + * // => 4 + */ + function sortedLastIndex(array, value) { + return baseSortedIndex(array, value, true); + } + + /** + * This method is like `_.sortedLastIndex` except that it accepts `iteratee` + * which is invoked for `value` and each element of `array` to compute their + * sort ranking. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The sorted array to inspect. + * @param {*} value The value to evaluate. + * @param {Function} [iteratee=_.identity] + * The iteratee invoked per element. + * @returns {number} Returns the index at which `value` should be inserted + * into `array`. + * @example + * + * var objects = [{ 'x': 4 }, { 'x': 5 }]; + * + * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; }); + * // => 1 + * + * // The `_.property` iteratee shorthand. + * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x'); + * // => 1 + */ + function sortedLastIndexBy(array, value, iteratee) { + return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true); + } + + /** + * This method is like `_.lastIndexOf` except that it performs a binary + * search on a sorted `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {*} value The value to search for. + * @returns {number} Returns the index of the matched value, else `-1`. + * @example + * + * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5); + * // => 3 + */ + function sortedLastIndexOf(array, value) { + var length = array ? array.length : 0; + if (length) { + var index = baseSortedIndex(array, value, true) - 1; + if (eq(array[index], value)) { + return index; + } + } + return -1; + } + + /** + * This method is like `_.uniq` except that it's designed and optimized + * for sorted arrays. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.sortedUniq([1, 1, 2]); + * // => [1, 2] + */ + function sortedUniq(array) { + return (array && array.length) + ? baseSortedUniq(array) + : []; + } + + /** + * This method is like `_.uniqBy` except that it's designed and optimized + * for sorted arrays. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [iteratee] The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor); + * // => [1.1, 2.3] + */ + function sortedUniqBy(array, iteratee) { + return (array && array.length) + ? baseSortedUniq(array, getIteratee(iteratee, 2)) + : []; + } + + /** + * Gets all but the first element of `array`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to query. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.tail([1, 2, 3]); + * // => [2, 3] + */ + function tail(array) { + var length = array ? array.length : 0; + return length ? baseSlice(array, 1, length) : []; + } + + /** + * Creates a slice of `array` with `n` elements taken from the beginning. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to take. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.take([1, 2, 3]); + * // => [1] + * + * _.take([1, 2, 3], 2); + * // => [1, 2] + * + * _.take([1, 2, 3], 5); + * // => [1, 2, 3] + * + * _.take([1, 2, 3], 0); + * // => [] + */ + function take(array, n, guard) { + if (!(array && array.length)) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + return baseSlice(array, 0, n < 0 ? 0 : n); + } + + /** + * Creates a slice of `array` with `n` elements taken from the end. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {number} [n=1] The number of elements to take. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the slice of `array`. + * @example + * + * _.takeRight([1, 2, 3]); + * // => [3] + * + * _.takeRight([1, 2, 3], 2); + * // => [2, 3] + * + * _.takeRight([1, 2, 3], 5); + * // => [1, 2, 3] + * + * _.takeRight([1, 2, 3], 0); + * // => [] + */ + function takeRight(array, n, guard) { + var length = array ? array.length : 0; + if (!length) { + return []; + } + n = (guard || n === undefined) ? 1 : toInteger(n); + n = length - n; + return baseSlice(array, n < 0 ? 0 : n, length); + } + + /** + * Creates a slice of `array` with elements taken from the end. Elements are + * taken until `predicate` returns falsey. The predicate is invoked with + * three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false }, + * { 'user': 'pebbles', 'active': false } + * ]; + * + * _.takeRightWhile(users, function(o) { return !o.active; }); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.matches` iteratee shorthand. + * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false }); + * // => objects for ['pebbles'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.takeRightWhile(users, ['active', false]); + * // => objects for ['fred', 'pebbles'] + * + * // The `_.property` iteratee shorthand. + * _.takeRightWhile(users, 'active'); + * // => [] + */ + function takeRightWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3), false, true) + : []; + } + + /** + * Creates a slice of `array` with elements taken from the beginning. Elements + * are taken until `predicate` returns falsey. The predicate is invoked with + * three arguments: (value, index, array). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Array + * @param {Array} array The array to query. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the slice of `array`. + * @example + * + * var users = [ + * { 'user': 'barney', 'active': false }, + * { 'user': 'fred', 'active': false}, + * { 'user': 'pebbles', 'active': true } + * ]; + * + * _.takeWhile(users, function(o) { return !o.active; }); + * // => objects for ['barney', 'fred'] + * + * // The `_.matches` iteratee shorthand. + * _.takeWhile(users, { 'user': 'barney', 'active': false }); + * // => objects for ['barney'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.takeWhile(users, ['active', false]); + * // => objects for ['barney', 'fred'] + * + * // The `_.property` iteratee shorthand. + * _.takeWhile(users, 'active'); + * // => [] + */ + function takeWhile(array, predicate) { + return (array && array.length) + ? baseWhile(array, getIteratee(predicate, 3)) + : []; + } + + /** + * Creates an array of unique values, in order, from all given arrays using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of combined values. + * @example + * + * _.union([2], [1, 2]); + * // => [2, 1] + */ + var union = baseRest(function(arrays) { + return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true)); + }); + + /** + * This method is like `_.union` except that it accepts `iteratee` which is + * invoked for each element of each `arrays` to generate the criterion by + * which uniqueness is computed. Result values are chosen from the first + * array in which the value occurs. The iteratee is invoked with one argument: + * (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [iteratee=_.identity] + * The iteratee invoked per element. + * @returns {Array} Returns the new array of combined values. + * @example + * + * _.unionBy([2.1], [1.2, 2.3], Math.floor); + * // => [2.1, 1.2] + * + * // The `_.property` iteratee shorthand. + * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + var unionBy = baseRest(function(arrays) { + var iteratee = last(arrays); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)); + }); + + /** + * This method is like `_.union` except that it accepts `comparator` which + * is invoked to compare elements of `arrays`. Result values are chosen from + * the first array in which the value occurs. The comparator is invoked + * with two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of combined values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.unionWith(objects, others, _.isEqual); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ + var unionWith = baseRest(function(arrays) { + var comparator = last(arrays); + if (isArrayLikeObject(comparator)) { + comparator = undefined; + } + return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator); + }); + + /** + * Creates a duplicate-free version of an array, using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons, in which only the first occurrence of each + * element is kept. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.uniq([2, 1, 2]); + * // => [2, 1] + */ + function uniq(array) { + return (array && array.length) + ? baseUniq(array) + : []; + } + + /** + * This method is like `_.uniq` except that it accepts `iteratee` which is + * invoked for each element in `array` to generate the criterion by which + * uniqueness is computed. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [iteratee=_.identity] + * The iteratee invoked per element. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * _.uniqBy([2.1, 1.2, 2.3], Math.floor); + * // => [2.1, 1.2] + * + * // The `_.property` iteratee shorthand. + * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] + */ + function uniqBy(array, iteratee) { + return (array && array.length) + ? baseUniq(array, getIteratee(iteratee, 2)) + : []; + } + + /** + * This method is like `_.uniq` except that it accepts `comparator` which + * is invoked to compare elements of `array`. The comparator is invoked with + * two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new duplicate free array. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.uniqWith(objects, _.isEqual); + * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }] + */ + function uniqWith(array, comparator) { + return (array && array.length) + ? baseUniq(array, undefined, comparator) + : []; + } + + /** + * This method is like `_.zip` except that it accepts an array of grouped + * elements and creates an array regrouping the elements to their pre-zip + * configuration. + * + * @static + * @memberOf _ + * @since 1.2.0 + * @category Array + * @param {Array} array The array of grouped elements to process. + * @returns {Array} Returns the new array of regrouped elements. + * @example + * + * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]); + * // => [['a', 1, true], ['b', 2, false]] + * + * _.unzip(zipped); + * // => [['a', 'b'], [1, 2], [true, false]] + */ + function unzip(array) { + if (!(array && array.length)) { + return []; + } + var length = 0; + array = arrayFilter(array, function(group) { + if (isArrayLikeObject(group)) { + length = nativeMax(group.length, length); + return true; + } + }); + return baseTimes(length, function(index) { + return arrayMap(array, baseProperty(index)); + }); + } + + /** + * This method is like `_.unzip` except that it accepts `iteratee` to specify + * how regrouped values should be combined. The iteratee is invoked with the + * elements of each group: (...group). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Array + * @param {Array} array The array of grouped elements to process. + * @param {Function} [iteratee=_.identity] The function to combine + * regrouped values. + * @returns {Array} Returns the new array of regrouped elements. + * @example + * + * var zipped = _.zip([1, 2], [10, 20], [100, 200]); + * // => [[1, 10, 100], [2, 20, 200]] + * + * _.unzipWith(zipped, _.add); + * // => [3, 30, 300] + */ + function unzipWith(array, iteratee) { + if (!(array && array.length)) { + return []; + } + var result = unzip(array); + if (iteratee == null) { + return result; + } + return arrayMap(result, function(group) { + return apply(iteratee, undefined, group); + }); + } + + /** + * Creates an array excluding all given values using + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * for equality comparisons. + * + * **Note:** Unlike `_.pull`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {Array} array The array to inspect. + * @param {...*} [values] The values to exclude. + * @returns {Array} Returns the new array of filtered values. + * @see _.difference, _.xor + * @example + * + * _.without([2, 1, 2, 3], 1, 2); + * // => [3] + */ + var without = baseRest(function(array, values) { + return isArrayLikeObject(array) + ? baseDifference(array, values) + : []; + }); + + /** + * Creates an array of unique values that is the + * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference) + * of the given arrays. The order of result values is determined by the order + * they occur in the arrays. + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @returns {Array} Returns the new array of filtered values. + * @see _.difference, _.without + * @example + * + * _.xor([2, 1], [2, 3]); + * // => [1, 3] + */ + var xor = baseRest(function(arrays) { + return baseXor(arrayFilter(arrays, isArrayLikeObject)); + }); + + /** + * This method is like `_.xor` except that it accepts `iteratee` which is + * invoked for each element of each `arrays` to generate the criterion by + * which by which they're compared. The iteratee is invoked with one argument: + * (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [iteratee=_.identity] + * The iteratee invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor); + * // => [1.2, 3.4] + * + * // The `_.property` iteratee shorthand. + * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 2 }] + */ + var xorBy = baseRest(function(arrays) { + var iteratee = last(arrays); + if (isArrayLikeObject(iteratee)) { + iteratee = undefined; + } + return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2)); + }); + + /** + * This method is like `_.xor` except that it accepts `comparator` which is + * invoked to compare elements of `arrays`. The comparator is invoked with + * two arguments: (arrVal, othVal). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Array + * @param {...Array} [arrays] The arrays to inspect. + * @param {Function} [comparator] The comparator invoked per element. + * @returns {Array} Returns the new array of filtered values. + * @example + * + * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]; + * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }]; + * + * _.xorWith(objects, others, _.isEqual); + * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }] + */ + var xorWith = baseRest(function(arrays) { + var comparator = last(arrays); + if (isArrayLikeObject(comparator)) { + comparator = undefined; + } + return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator); + }); + + /** + * Creates an array of grouped elements, the first of which contains the + * first elements of the given arrays, the second of which contains the + * second elements of the given arrays, and so on. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Array + * @param {...Array} [arrays] The arrays to process. + * @returns {Array} Returns the new array of grouped elements. + * @example + * + * _.zip(['a', 'b'], [1, 2], [true, false]); + * // => [['a', 1, true], ['b', 2, false]] + */ + var zip = baseRest(unzip); + + /** + * This method is like `_.fromPairs` except that it accepts two arrays, + * one of property identifiers and one of corresponding values. + * + * @static + * @memberOf _ + * @since 0.4.0 + * @category Array + * @param {Array} [props=[]] The property identifiers. + * @param {Array} [values=[]] The property values. + * @returns {Object} Returns the new object. + * @example + * + * _.zipObject(['a', 'b'], [1, 2]); + * // => { 'a': 1, 'b': 2 } + */ + function zipObject(props, values) { + return baseZipObject(props || [], values || [], assignValue); + } + + /** + * This method is like `_.zipObject` except that it supports property paths. + * + * @static + * @memberOf _ + * @since 4.1.0 + * @category Array + * @param {Array} [props=[]] The property identifiers. + * @param {Array} [values=[]] The property values. + * @returns {Object} Returns the new object. + * @example + * + * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]); + * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } + */ + function zipObjectDeep(props, values) { + return baseZipObject(props || [], values || [], baseSet); + } + + /** + * This method is like `_.zip` except that it accepts `iteratee` to specify + * how grouped values should be combined. The iteratee is invoked with the + * elements of each group: (...group). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Array + * @param {...Array} [arrays] The arrays to process. + * @param {Function} [iteratee=_.identity] The function to combine grouped values. + * @returns {Array} Returns the new array of grouped elements. + * @example + * + * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) { + * return a + b + c; + * }); + * // => [111, 222] + */ + var zipWith = baseRest(function(arrays) { + var length = arrays.length, + iteratee = length > 1 ? arrays[length - 1] : undefined; + + iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined; + return unzipWith(arrays, iteratee); + }); + + /*------------------------------------------------------------------------*/ + + /** + * Creates a `lodash` wrapper instance that wraps `value` with explicit method + * chain sequences enabled. The result of such sequences must be unwrapped + * with `_#value`. + * + * @static + * @memberOf _ + * @since 1.3.0 + * @category Seq + * @param {*} value The value to wrap. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 }, + * { 'user': 'pebbles', 'age': 1 } + * ]; + * + * var youngest = _ + * .chain(users) + * .sortBy('age') + * .map(function(o) { + * return o.user + ' is ' + o.age; + * }) + * .head() + * .value(); + * // => 'pebbles is 1' + */ + function chain(value) { + var result = lodash(value); + result.__chain__ = true; + return result; + } + + /** + * This method invokes `interceptor` and returns `value`. The interceptor + * is invoked with one argument; (value). The purpose of this method is to + * "tap into" a method chain sequence in order to modify intermediate results. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Seq + * @param {*} value The value to provide to `interceptor`. + * @param {Function} interceptor The function to invoke. + * @returns {*} Returns `value`. + * @example + * + * _([1, 2, 3]) + * .tap(function(array) { + * // Mutate input array. + * array.pop(); + * }) + * .reverse() + * .value(); + * // => [2, 1] + */ + function tap(value, interceptor) { + interceptor(value); + return value; + } + + /** + * This method is like `_.tap` except that it returns the result of `interceptor`. + * The purpose of this method is to "pass thru" values replacing intermediate + * results in a method chain sequence. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Seq + * @param {*} value The value to provide to `interceptor`. + * @param {Function} interceptor The function to invoke. + * @returns {*} Returns the result of `interceptor`. + * @example + * + * _(' abc ') + * .chain() + * .trim() + * .thru(function(value) { + * return [value]; + * }) + * .value(); + * // => ['abc'] + */ + function thru(value, interceptor) { + return interceptor(value); + } + + /** + * This method is the wrapper version of `_.at`. + * + * @name at + * @memberOf _ + * @since 1.0.0 + * @category Seq + * @param {...(string|string[])} [paths] The property paths of elements to pick. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + * + * _(object).at(['a[0].b.c', 'a[1]']).value(); + * // => [3, 4] + */ + var wrapperAt = baseRest(function(paths) { + paths = baseFlatten(paths, 1); + var length = paths.length, + start = length ? paths[0] : 0, + value = this.__wrapped__, + interceptor = function(object) { return baseAt(object, paths); }; + + if (length > 1 || this.__actions__.length || + !(value instanceof LazyWrapper) || !isIndex(start)) { + return this.thru(interceptor); + } + value = value.slice(start, +start + (length ? 1 : 0)); + value.__actions__.push({ + 'func': thru, + 'args': [interceptor], + 'thisArg': undefined + }); + return new LodashWrapper(value, this.__chain__).thru(function(array) { + if (length && !array.length) { + array.push(undefined); + } + return array; + }); + }); + + /** + * Creates a `lodash` wrapper instance with explicit method chain sequences enabled. + * + * @name chain + * @memberOf _ + * @since 0.1.0 + * @category Seq + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 } + * ]; + * + * // A sequence without explicit chaining. + * _(users).head(); + * // => { 'user': 'barney', 'age': 36 } + * + * // A sequence with explicit chaining. + * _(users) + * .chain() + * .head() + * .pick('user') + * .value(); + * // => { 'user': 'barney' } + */ + function wrapperChain() { + return chain(this); + } + + /** + * Executes the chain sequence and returns the wrapped result. + * + * @name commit + * @memberOf _ + * @since 3.2.0 + * @category Seq + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var array = [1, 2]; + * var wrapped = _(array).push(3); + * + * console.log(array); + * // => [1, 2] + * + * wrapped = wrapped.commit(); + * console.log(array); + * // => [1, 2, 3] + * + * wrapped.last(); + * // => 3 + * + * console.log(array); + * // => [1, 2, 3] + */ + function wrapperCommit() { + return new LodashWrapper(this.value(), this.__chain__); + } + + /** + * Gets the next value on a wrapped object following the + * [iterator protocol](https://mdn.io/iteration_protocols#iterator). + * + * @name next + * @memberOf _ + * @since 4.0.0 + * @category Seq + * @returns {Object} Returns the next iterator value. + * @example + * + * var wrapped = _([1, 2]); + * + * wrapped.next(); + * // => { 'done': false, 'value': 1 } + * + * wrapped.next(); + * // => { 'done': false, 'value': 2 } + * + * wrapped.next(); + * // => { 'done': true, 'value': undefined } + */ + function wrapperNext() { + if (this.__values__ === undefined) { + this.__values__ = toArray(this.value()); + } + var done = this.__index__ >= this.__values__.length, + value = done ? undefined : this.__values__[this.__index__++]; + + return { 'done': done, 'value': value }; + } + + /** + * Enables the wrapper to be iterable. + * + * @name Symbol.iterator + * @memberOf _ + * @since 4.0.0 + * @category Seq + * @returns {Object} Returns the wrapper object. + * @example + * + * var wrapped = _([1, 2]); + * + * wrapped[Symbol.iterator]() === wrapped; + * // => true + * + * Array.from(wrapped); + * // => [1, 2] + */ + function wrapperToIterator() { + return this; + } + + /** + * Creates a clone of the chain sequence planting `value` as the wrapped value. + * + * @name plant + * @memberOf _ + * @since 3.2.0 + * @category Seq + * @param {*} value The value to plant. + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * function square(n) { + * return n * n; + * } + * + * var wrapped = _([1, 2]).map(square); + * var other = wrapped.plant([3, 4]); + * + * other.value(); + * // => [9, 16] + * + * wrapped.value(); + * // => [1, 4] + */ + function wrapperPlant(value) { + var result, + parent = this; + + while (parent instanceof baseLodash) { + var clone = wrapperClone(parent); + clone.__index__ = 0; + clone.__values__ = undefined; + if (result) { + previous.__wrapped__ = clone; + } else { + result = clone; + } + var previous = clone; + parent = parent.__wrapped__; + } + previous.__wrapped__ = value; + return result; + } + + /** + * This method is the wrapper version of `_.reverse`. + * + * **Note:** This method mutates the wrapped array. + * + * @name reverse + * @memberOf _ + * @since 0.1.0 + * @category Seq + * @returns {Object} Returns the new `lodash` wrapper instance. + * @example + * + * var array = [1, 2, 3]; + * + * _(array).reverse().value() + * // => [3, 2, 1] + * + * console.log(array); + * // => [3, 2, 1] + */ + function wrapperReverse() { + var value = this.__wrapped__; + if (value instanceof LazyWrapper) { + var wrapped = value; + if (this.__actions__.length) { + wrapped = new LazyWrapper(this); + } + wrapped = wrapped.reverse(); + wrapped.__actions__.push({ + 'func': thru, + 'args': [reverse], + 'thisArg': undefined + }); + return new LodashWrapper(wrapped, this.__chain__); + } + return this.thru(reverse); + } + + /** + * Executes the chain sequence to resolve the unwrapped value. + * + * @name value + * @memberOf _ + * @since 0.1.0 + * @alias toJSON, valueOf + * @category Seq + * @returns {*} Returns the resolved unwrapped value. + * @example + * + * _([1, 2, 3]).value(); + * // => [1, 2, 3] + */ + function wrapperValue() { + return baseWrapperValue(this.__wrapped__, this.__actions__); + } + + /*------------------------------------------------------------------------*/ + + /** + * Creates an object composed of keys generated from the results of running + * each element of `collection` thru `iteratee`. The corresponding value of + * each key is the number of times the key was returned by `iteratee`. The + * iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] + * The iteratee to transform keys. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.countBy([6.1, 4.2, 6.3], Math.floor); + * // => { '4': 1, '6': 2 } + * + * // The `_.property` iteratee shorthand. + * _.countBy(['one', 'two', 'three'], 'length'); + * // => { '3': 2, '5': 1 } + */ + var countBy = createAggregator(function(result, value, key) { + hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1); + }); + + /** + * Checks if `predicate` returns truthy for **all** elements of `collection`. + * Iteration is stopped once `predicate` returns falsey. The predicate is + * invoked with three arguments: (value, index|key, collection). + * + * **Note:** This method returns `true` for + * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because + * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of + * elements of empty collections. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {boolean} Returns `true` if all elements pass the predicate check, + * else `false`. + * @example + * + * _.every([true, 1, null, 'yes'], Boolean); + * // => false + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': false }, + * { 'user': 'fred', 'age': 40, 'active': false } + * ]; + * + * // The `_.matches` iteratee shorthand. + * _.every(users, { 'user': 'barney', 'active': false }); + * // => false + * + * // The `_.matchesProperty` iteratee shorthand. + * _.every(users, ['active', false]); + * // => true + * + * // The `_.property` iteratee shorthand. + * _.every(users, 'active'); + * // => false + */ + function every(collection, predicate, guard) { + var func = isArray(collection) ? arrayEvery : baseEvery; + if (guard && isIterateeCall(collection, predicate, guard)) { + predicate = undefined; + } + return func(collection, getIteratee(predicate, 3)); + } + + /** + * Iterates over elements of `collection`, returning an array of all elements + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index|key, collection). + * + * **Note:** Unlike `_.remove`, this method returns a new array. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @see _.reject + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': true }, + * { 'user': 'fred', 'age': 40, 'active': false } + * ]; + * + * _.filter(users, function(o) { return !o.active; }); + * // => objects for ['fred'] + * + * // The `_.matches` iteratee shorthand. + * _.filter(users, { 'age': 36, 'active': true }); + * // => objects for ['barney'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.filter(users, ['active', false]); + * // => objects for ['fred'] + * + * // The `_.property` iteratee shorthand. + * _.filter(users, 'active'); + * // => objects for ['barney'] + */ + function filter(collection, predicate) { + var func = isArray(collection) ? arrayFilter : baseFilter; + return func(collection, getIteratee(predicate, 3)); + } + + /** + * Iterates over elements of `collection`, returning the first element + * `predicate` returns truthy for. The predicate is invoked with three + * arguments: (value, index|key, collection). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to inspect. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @param {number} [fromIndex=0] The index to search from. + * @returns {*} Returns the matched element, else `undefined`. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': true }, + * { 'user': 'fred', 'age': 40, 'active': false }, + * { 'user': 'pebbles', 'age': 1, 'active': true } + * ]; + * + * _.find(users, function(o) { return o.age < 40; }); + * // => object for 'barney' + * + * // The `_.matches` iteratee shorthand. + * _.find(users, { 'age': 1, 'active': true }); + * // => object for 'pebbles' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.find(users, ['active', false]); + * // => object for 'fred' + * + * // The `_.property` iteratee shorthand. + * _.find(users, 'active'); + * // => object for 'barney' + */ + var find = createFind(findIndex); + + /** + * This method is like `_.find` except that it iterates over elements of + * `collection` from right to left. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Collection + * @param {Array|Object} collection The collection to inspect. + * @param {Function} [predicate=_.identity] + * The function invoked per iteration. + * @param {number} [fromIndex=collection.length-1] The index to search from. + * @returns {*} Returns the matched element, else `undefined`. + * @example + * + * _.findLast([1, 2, 3, 4], function(n) { + * return n % 2 == 1; + * }); + * // => 3 + */ + var findLast = createFind(findLastIndex); + + /** + * Creates a flattened array of values by running each element in `collection` + * thru `iteratee` and flattening the mapped results. The iteratee is invoked + * with three arguments: (value, index|key, collection). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the new flattened array. + * @example + * + * function duplicate(n) { + * return [n, n]; + * } + * + * _.flatMap([1, 2], duplicate); + * // => [1, 1, 2, 2] + */ + function flatMap(collection, iteratee) { + return baseFlatten(map(collection, iteratee), 1); + } + + /** + * This method is like `_.flatMap` except that it recursively flattens the + * mapped results. + * + * @static + * @memberOf _ + * @since 4.7.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] + * The function invoked per iteration. + * @returns {Array} Returns the new flattened array. + * @example + * + * function duplicate(n) { + * return [[[n, n]]]; + * } + * + * _.flatMapDeep([1, 2], duplicate); + * // => [1, 1, 2, 2] + */ + function flatMapDeep(collection, iteratee) { + return baseFlatten(map(collection, iteratee), INFINITY); + } + + /** + * This method is like `_.flatMap` except that it recursively flattens the + * mapped results up to `depth` times. + * + * @static + * @memberOf _ + * @since 4.7.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] + * The function invoked per iteration. + * @param {number} [depth=1] The maximum recursion depth. + * @returns {Array} Returns the new flattened array. + * @example + * + * function duplicate(n) { + * return [[[n, n]]]; + * } + * + * _.flatMapDepth([1, 2], duplicate, 2); + * // => [[1, 1], [2, 2]] + */ + function flatMapDepth(collection, iteratee, depth) { + depth = depth === undefined ? 1 : toInteger(depth); + return baseFlatten(map(collection, iteratee), depth); + } + + /** + * Iterates over elements of `collection` and invokes `iteratee` for each element. + * The iteratee is invoked with three arguments: (value, index|key, collection). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * **Note:** As with other "Collections" methods, objects with a "length" + * property are iterated like arrays. To avoid this behavior use `_.forIn` + * or `_.forOwn` for object iteration. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @alias each + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @see _.forEachRight + * @example + * + * _([1, 2]).forEach(function(value) { + * console.log(value); + * }); + * // => Logs `1` then `2`. + * + * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forEach(collection, iteratee) { + var func = isArray(collection) ? arrayEach : baseEach; + return func(collection, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.forEach` except that it iterates over elements of + * `collection` from right to left. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @alias eachRight + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array|Object} Returns `collection`. + * @see _.forEach + * @example + * + * _.forEachRight([1, 2], function(value) { + * console.log(value); + * }); + * // => Logs `2` then `1`. + */ + function forEachRight(collection, iteratee) { + var func = isArray(collection) ? arrayEachRight : baseEachRight; + return func(collection, getIteratee(iteratee, 3)); + } + + /** + * Creates an object composed of keys generated from the results of running + * each element of `collection` thru `iteratee`. The order of grouped values + * is determined by the order they occur in `collection`. The corresponding + * value of each key is an array of elements responsible for generating the + * key. The iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] + * The iteratee to transform keys. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * _.groupBy([6.1, 4.2, 6.3], Math.floor); + * // => { '4': [4.2], '6': [6.1, 6.3] } + * + * // The `_.property` iteratee shorthand. + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } + */ + var groupBy = createAggregator(function(result, value, key) { + if (hasOwnProperty.call(result, key)) { + result[key].push(value); + } else { + result[key] = [value]; + } + }); + + /** + * Checks if `value` is in `collection`. If `collection` is a string, it's + * checked for a substring of `value`, otherwise + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * is used for equality comparisons. If `fromIndex` is negative, it's used as + * the offset from the end of `collection`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object|string} collection The collection to inspect. + * @param {*} value The value to search for. + * @param {number} [fromIndex=0] The index to search from. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. + * @returns {boolean} Returns `true` if `value` is found, else `false`. + * @example + * + * _.includes([1, 2, 3], 1); + * // => true + * + * _.includes([1, 2, 3], 1, 2); + * // => false + * + * _.includes({ 'a': 1, 'b': 2 }, 1); + * // => true + * + * _.includes('abcd', 'bc'); + * // => true + */ + function includes(collection, value, fromIndex, guard) { + collection = isArrayLike(collection) ? collection : values(collection); + fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0; + + var length = collection.length; + if (fromIndex < 0) { + fromIndex = nativeMax(length + fromIndex, 0); + } + return isString(collection) + ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1) + : (!!length && baseIndexOf(collection, value, fromIndex) > -1); + } + + /** + * Invokes the method at `path` of each element in `collection`, returning + * an array of the results of each invoked method. Any additional arguments + * are provided to each invoked method. If `path` is a function, it's invoked + * for, and `this` bound to, each element in `collection`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Function|string} path The path of the method to invoke or + * the function invoked per iteration. + * @param {...*} [args] The arguments to invoke each method with. + * @returns {Array} Returns the array of results. + * @example + * + * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invokeMap([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ + var invokeMap = baseRest(function(collection, path, args) { + var index = -1, + isFunc = typeof path == 'function', + isProp = isKey(path), + result = isArrayLike(collection) ? Array(collection.length) : []; + + baseEach(collection, function(value) { + var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined); + result[++index] = func ? apply(func, value, args) : baseInvoke(value, path, args); + }); + return result; + }); + + /** + * Creates an object composed of keys generated from the results of running + * each element of `collection` thru `iteratee`. The corresponding value of + * each key is the last element responsible for generating the key. The + * iteratee is invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] + * The iteratee to transform keys. + * @returns {Object} Returns the composed aggregate object. + * @example + * + * var array = [ + * { 'dir': 'left', 'code': 97 }, + * { 'dir': 'right', 'code': 100 } + * ]; + * + * _.keyBy(array, function(o) { + * return String.fromCharCode(o.code); + * }); + * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } } + * + * _.keyBy(array, 'dir'); + * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } } + */ + var keyBy = createAggregator(function(result, value, key) { + result[key] = value; + }); + + /** + * Creates an array of values by running each element in `collection` thru + * `iteratee`. The iteratee is invoked with three arguments: + * (value, index|key, collection). + * + * Many lodash methods are guarded to work as iteratees for methods like + * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`. + * + * The guarded methods are: + * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`, + * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`, + * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`, + * `template`, `trim`, `trimEnd`, `trimStart`, and `words` + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new mapped array. + * @example + * + * function square(n) { + * return n * n; + * } + * + * _.map([4, 8], square); + * // => [16, 64] + * + * _.map({ 'a': 4, 'b': 8 }, square); + * // => [16, 64] (iteration order is not guaranteed) + * + * var users = [ + * { 'user': 'barney' }, + * { 'user': 'fred' } + * ]; + * + * // The `_.property` iteratee shorthand. + * _.map(users, 'user'); + * // => ['barney', 'fred'] + */ + function map(collection, iteratee) { + var func = isArray(collection) ? arrayMap : baseMap; + return func(collection, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.sortBy` except that it allows specifying the sort + * orders of the iteratees to sort by. If `orders` is unspecified, all values + * are sorted in ascending order. Otherwise, specify an order of "desc" for + * descending or "asc" for ascending sort order of corresponding values. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]] + * The iteratees to sort by. + * @param {string[]} [orders] The sort orders of `iteratees`. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 34 }, + * { 'user': 'fred', 'age': 40 }, + * { 'user': 'barney', 'age': 36 } + * ]; + * + * // Sort by `user` in ascending order and by `age` in descending order. + * _.orderBy(users, ['user', 'age'], ['asc', 'desc']); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] + */ + function orderBy(collection, iteratees, orders, guard) { + if (collection == null) { + return []; + } + if (!isArray(iteratees)) { + iteratees = iteratees == null ? [] : [iteratees]; + } + orders = guard ? undefined : orders; + if (!isArray(orders)) { + orders = orders == null ? [] : [orders]; + } + return baseOrderBy(collection, iteratees, orders); + } + + /** + * Creates an array of elements split into two groups, the first of which + * contains elements `predicate` returns truthy for, the second of which + * contains elements `predicate` returns falsey for. The predicate is + * invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the array of grouped elements. + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': false }, + * { 'user': 'fred', 'age': 40, 'active': true }, + * { 'user': 'pebbles', 'age': 1, 'active': false } + * ]; + * + * _.partition(users, function(o) { return o.active; }); + * // => objects for [['fred'], ['barney', 'pebbles']] + * + * // The `_.matches` iteratee shorthand. + * _.partition(users, { 'age': 1, 'active': false }); + * // => objects for [['pebbles'], ['barney', 'fred']] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.partition(users, ['active', false]); + * // => objects for [['barney', 'pebbles'], ['fred']] + * + * // The `_.property` iteratee shorthand. + * _.partition(users, 'active'); + * // => objects for [['fred'], ['barney', 'pebbles']] + */ + var partition = createAggregator(function(result, value, key) { + result[key ? 0 : 1].push(value); + }, function() { return [[], []]; }); + + /** + * Reduces `collection` to a value which is the accumulated result of running + * each element in `collection` thru `iteratee`, where each successive + * invocation is supplied the return value of the previous. If `accumulator` + * is not given, the first element of `collection` is used as the initial + * value. The iteratee is invoked with four arguments: + * (accumulator, value, index|key, collection). + * + * Many lodash methods are guarded to work as iteratees for methods like + * `_.reduce`, `_.reduceRight`, and `_.transform`. + * + * The guarded methods are: + * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`, + * and `sortBy` + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @returns {*} Returns the accumulated value. + * @see _.reduceRight + * @example + * + * _.reduce([1, 2], function(sum, n) { + * return sum + n; + * }, 0); + * // => 3 + * + * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { + * (result[value] || (result[value] = [])).push(key); + * return result; + * }, {}); + * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed) + */ + function reduce(collection, iteratee, accumulator) { + var func = isArray(collection) ? arrayReduce : baseReduce, + initAccum = arguments.length < 3; + + return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach); + } + + /** + * This method is like `_.reduce` except that it iterates over elements of + * `collection` from right to left. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The initial value. + * @returns {*} Returns the accumulated value. + * @see _.reduce + * @example + * + * var array = [[0, 1], [2, 3], [4, 5]]; + * + * _.reduceRight(array, function(flattened, other) { + * return flattened.concat(other); + * }, []); + * // => [4, 5, 2, 3, 0, 1] + */ + function reduceRight(collection, iteratee, accumulator) { + var func = isArray(collection) ? arrayReduceRight : baseReduce, + initAccum = arguments.length < 3; + + return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight); + } + + /** + * The opposite of `_.filter`; this method returns the elements of `collection` + * that `predicate` does **not** return truthy for. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {Array} Returns the new filtered array. + * @see _.filter + * @example + * + * var users = [ + * { 'user': 'barney', 'age': 36, 'active': false }, + * { 'user': 'fred', 'age': 40, 'active': true } + * ]; + * + * _.reject(users, function(o) { return !o.active; }); + * // => objects for ['fred'] + * + * // The `_.matches` iteratee shorthand. + * _.reject(users, { 'age': 40, 'active': true }); + * // => objects for ['barney'] + * + * // The `_.matchesProperty` iteratee shorthand. + * _.reject(users, ['active', false]); + * // => objects for ['fred'] + * + * // The `_.property` iteratee shorthand. + * _.reject(users, 'active'); + * // => objects for ['barney'] + */ + function reject(collection, predicate) { + var func = isArray(collection) ? arrayFilter : baseFilter; + return func(collection, negate(getIteratee(predicate, 3))); + } + + /** + * Gets a random element from `collection`. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Collection + * @param {Array|Object} collection The collection to sample. + * @returns {*} Returns the random element. + * @example + * + * _.sample([1, 2, 3, 4]); + * // => 2 + */ + function sample(collection) { + var array = isArrayLike(collection) ? collection : values(collection), + length = array.length; + + return length > 0 ? array[baseRandom(0, length - 1)] : undefined; + } + + /** + * Gets `n` random elements at unique keys from `collection` up to the + * size of `collection`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Collection + * @param {Array|Object} collection The collection to sample. + * @param {number} [n=1] The number of elements to sample. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Array} Returns the random elements. + * @example + * + * _.sampleSize([1, 2, 3], 2); + * // => [3, 1] + * + * _.sampleSize([1, 2, 3], 4); + * // => [2, 3, 1] + */ + function sampleSize(collection, n, guard) { + var index = -1, + result = toArray(collection), + length = result.length, + lastIndex = length - 1; + + if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) { + n = 1; + } else { + n = baseClamp(toInteger(n), 0, length); + } + while (++index < n) { + var rand = baseRandom(index, lastIndex), + value = result[rand]; + + result[rand] = result[index]; + result[index] = value; + } + result.length = n; + return result; + } + + /** + * Creates an array of shuffled values, using a version of the + * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to shuffle. + * @returns {Array} Returns the new shuffled array. + * @example + * + * _.shuffle([1, 2, 3, 4]); + * // => [4, 1, 3, 2] + */ + function shuffle(collection) { + return sampleSize(collection, MAX_ARRAY_LENGTH); + } + + /** + * Gets the size of `collection` by returning its length for array-like + * values or the number of own enumerable string keyed properties for objects. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object|string} collection The collection to inspect. + * @returns {number} Returns the collection size. + * @example + * + * _.size([1, 2, 3]); + * // => 3 + * + * _.size({ 'a': 1, 'b': 2 }); + * // => 2 + * + * _.size('pebbles'); + * // => 7 + */ + function size(collection) { + if (collection == null) { + return 0; + } + if (isArrayLike(collection)) { + return isString(collection) ? stringSize(collection) : collection.length; + } + var tag = getTag(collection); + if (tag == mapTag || tag == setTag) { + return collection.size; + } + return baseKeys(collection).length; + } + + /** + * Checks if `predicate` returns truthy for **any** element of `collection`. + * Iteration is stopped once `predicate` returns truthy. The predicate is + * invoked with three arguments: (value, index|key, collection). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {boolean} Returns `true` if any element passes the predicate check, + * else `false`. + * @example + * + * _.some([null, 0, 'yes', false], Boolean); + * // => true + * + * var users = [ + * { 'user': 'barney', 'active': true }, + * { 'user': 'fred', 'active': false } + * ]; + * + * // The `_.matches` iteratee shorthand. + * _.some(users, { 'user': 'barney', 'active': false }); + * // => false + * + * // The `_.matchesProperty` iteratee shorthand. + * _.some(users, ['active', false]); + * // => true + * + * // The `_.property` iteratee shorthand. + * _.some(users, 'active'); + * // => true + */ + function some(collection, predicate, guard) { + var func = isArray(collection) ? arraySome : baseSome; + if (guard && isIterateeCall(collection, predicate, guard)) { + predicate = undefined; + } + return func(collection, getIteratee(predicate, 3)); + } + + /** + * Creates an array of elements, sorted in ascending order by the results of + * running each element in a collection thru each iteratee. This method + * performs a stable sort, that is, it preserves the original sort order of + * equal elements. The iteratees are invoked with one argument: (value). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Collection + * @param {Array|Object} collection The collection to iterate over. + * @param {...(Function|Function[])} [iteratees=[_.identity]] + * The iteratees to sort by. + * @returns {Array} Returns the new sorted array. + * @example + * + * var users = [ + * { 'user': 'fred', 'age': 48 }, + * { 'user': 'barney', 'age': 36 }, + * { 'user': 'fred', 'age': 40 }, + * { 'user': 'barney', 'age': 34 } + * ]; + * + * _.sortBy(users, function(o) { return o.user; }); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] + * + * _.sortBy(users, ['user', 'age']); + * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] + * + * _.sortBy(users, 'user', function(o) { + * return Math.floor(o.age / 10); + * }); + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] + */ + var sortBy = baseRest(function(collection, iteratees) { + if (collection == null) { + return []; + } + var length = iteratees.length; + if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) { + iteratees = []; + } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) { + iteratees = [iteratees[0]]; + } + return baseOrderBy(collection, baseFlatten(iteratees, 1), []); + }); + + /*------------------------------------------------------------------------*/ + + /** + * Gets the timestamp of the number of milliseconds that have elapsed since + * the Unix epoch (1 January 1970 00:00:00 UTC). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Date + * @returns {number} Returns the timestamp. + * @example + * + * _.defer(function(stamp) { + * console.log(_.now() - stamp); + * }, _.now()); + * // => Logs the number of milliseconds it took for the deferred invocation. + */ + var now = ctxNow || function() { + return root.Date.now(); + }; + + /*------------------------------------------------------------------------*/ + + /** + * The opposite of `_.before`; this method creates a function that invokes + * `func` once it's called `n` or more times. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {number} n The number of calls before `func` is invoked. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var saves = ['profile', 'settings']; + * + * var done = _.after(saves.length, function() { + * console.log('done saving!'); + * }); + * + * _.forEach(saves, function(type) { + * asyncSave({ 'type': type, 'complete': done }); + * }); + * // => Logs 'done saving!' after the two async saves have completed. + */ + function after(n, func) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + n = toInteger(n); + return function() { + if (--n < 1) { + return func.apply(this, arguments); + } + }; + } + + /** + * Creates a function that invokes `func`, with up to `n` arguments, + * ignoring any additional arguments. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} func The function to cap arguments for. + * @param {number} [n=func.length] The arity cap. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the new capped function. + * @example + * + * _.map(['6', '8', '10'], _.ary(parseInt, 1)); + * // => [6, 8, 10] + */ + function ary(func, n, guard) { + n = guard ? undefined : n; + n = (func && n == null) ? func.length : n; + return createWrap(func, ARY_FLAG, undefined, undefined, undefined, undefined, n); + } + + /** + * Creates a function that invokes `func`, with the `this` binding and arguments + * of the created function, while it's called less than `n` times. Subsequent + * calls to the created function return the result of the last `func` invocation. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {number} n The number of calls at which `func` is no longer invoked. + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * jQuery(element).on('click', _.before(5, addContactToList)); + * // => Allows adding up to 4 contacts to the list. + */ + function before(n, func) { + var result; + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + n = toInteger(n); + return function() { + if (--n > 0) { + result = func.apply(this, arguments); + } + if (n <= 1) { + func = undefined; + } + return result; + }; + } + + /** + * Creates a function that invokes `func` with the `this` binding of `thisArg` + * and `partials` prepended to the arguments it receives. + * + * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds, + * may be used as a placeholder for partially applied arguments. + * + * **Note:** Unlike native `Function#bind`, this method doesn't set the "length" + * property of bound functions. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * function greet(greeting, punctuation) { + * return greeting + ' ' + this.user + punctuation; + * } + * + * var object = { 'user': 'fred' }; + * + * var bound = _.bind(greet, object, 'hi'); + * bound('!'); + * // => 'hi fred!' + * + * // Bound with placeholders. + * var bound = _.bind(greet, object, _, '!'); + * bound('hi'); + * // => 'hi fred!' + */ + var bind = baseRest(function(func, thisArg, partials) { + var bitmask = BIND_FLAG; + if (partials.length) { + var holders = replaceHolders(partials, getHolder(bind)); + bitmask |= PARTIAL_FLAG; + } + return createWrap(func, bitmask, thisArg, partials, holders); + }); + + /** + * Creates a function that invokes the method at `object[key]` with `partials` + * prepended to the arguments it receives. + * + * This method differs from `_.bind` by allowing bound functions to reference + * methods that may be redefined or don't yet exist. See + * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern) + * for more details. + * + * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for partially applied arguments. + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Function + * @param {Object} object The object to invoke the method on. + * @param {string} key The key of the method. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new bound function. + * @example + * + * var object = { + * 'user': 'fred', + * 'greet': function(greeting, punctuation) { + * return greeting + ' ' + this.user + punctuation; + * } + * }; + * + * var bound = _.bindKey(object, 'greet', 'hi'); + * bound('!'); + * // => 'hi fred!' + * + * object.greet = function(greeting, punctuation) { + * return greeting + 'ya ' + this.user + punctuation; + * }; + * + * bound('!'); + * // => 'hiya fred!' + * + * // Bound with placeholders. + * var bound = _.bindKey(object, 'greet', _, '!'); + * bound('hi'); + * // => 'hiya fred!' + */ + var bindKey = baseRest(function(object, key, partials) { + var bitmask = BIND_FLAG | BIND_KEY_FLAG; + if (partials.length) { + var holders = replaceHolders(partials, getHolder(bindKey)); + bitmask |= PARTIAL_FLAG; + } + return createWrap(key, bitmask, object, partials, holders); + }); + + /** + * Creates a function that accepts arguments of `func` and either invokes + * `func` returning its result, if at least `arity` number of arguments have + * been provided, or returns a function that accepts the remaining `func` + * arguments, and so on. The arity of `func` may be specified if `func.length` + * is not sufficient. + * + * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds, + * may be used as a placeholder for provided arguments. + * + * **Note:** This method doesn't set the "length" property of curried functions. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Function + * @param {Function} func The function to curry. + * @param {number} [arity=func.length] The arity of `func`. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the new curried function. + * @example + * + * var abc = function(a, b, c) { + * return [a, b, c]; + * }; + * + * var curried = _.curry(abc); + * + * curried(1)(2)(3); + * // => [1, 2, 3] + * + * curried(1, 2)(3); + * // => [1, 2, 3] + * + * curried(1, 2, 3); + * // => [1, 2, 3] + * + * // Curried with placeholders. + * curried(1)(_, 3)(2); + * // => [1, 2, 3] + */ + function curry(func, arity, guard) { + arity = guard ? undefined : arity; + var result = createWrap(func, CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity); + result.placeholder = curry.placeholder; + return result; + } + + /** + * This method is like `_.curry` except that arguments are applied to `func` + * in the manner of `_.partialRight` instead of `_.partial`. + * + * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for provided arguments. + * + * **Note:** This method doesn't set the "length" property of curried functions. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} func The function to curry. + * @param {number} [arity=func.length] The arity of `func`. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the new curried function. + * @example + * + * var abc = function(a, b, c) { + * return [a, b, c]; + * }; + * + * var curried = _.curryRight(abc); + * + * curried(3)(2)(1); + * // => [1, 2, 3] + * + * curried(2, 3)(1); + * // => [1, 2, 3] + * + * curried(1, 2, 3); + * // => [1, 2, 3] + * + * // Curried with placeholders. + * curried(3)(1, _)(2); + * // => [1, 2, 3] + */ + function curryRight(func, arity, guard) { + arity = guard ? undefined : arity; + var result = createWrap(func, CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity); + result.placeholder = curryRight.placeholder; + return result; + } + + /** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked. The debounced function comes with a `cancel` method to cancel + * delayed `func` invocations and a `flush` method to immediately invoke them. + * Provide `options` to indicate whether `func` should be invoked on the + * leading and/or trailing edge of the `wait` timeout. The `func` is invoked + * with the last arguments provided to the debounced function. Subsequent + * calls to the debounced function return the result of the last `func` + * invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the debounced function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until to the next tick, similar to `setTimeout` with a timeout of `0`. + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `_.debounce` and `_.throttle`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] The number of milliseconds to delay. + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=false] + * Specify invoking on the leading edge of the timeout. + * @param {number} [options.maxWait] + * The maximum time `func` is allowed to be delayed before it's invoked. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // Avoid costly calculations while the window size is in flux. + * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); + * + * // Invoke `sendMail` when clicked, debouncing subsequent calls. + * jQuery(element).on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })); + * + * // Ensure `batchLog` is invoked once after 1 second of debounced calls. + * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); + * var source = new EventSource('/stream'); + * jQuery(source).on('message', debounced); + * + * // Cancel the trailing debounced invocation. + * jQuery(window).on('popstate', debounced.cancel); + */ + function debounce(func, wait, options) { + var lastArgs, + lastThis, + maxWait, + result, + timerId, + lastCallTime, + lastInvokeTime = 0, + leading = false, + maxing = false, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + wait = toNumber(wait) || 0; + if (isObject(options)) { + leading = !!options.leading; + maxing = 'maxWait' in options; + maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + + function invokeFunc(time) { + var args = lastArgs, + thisArg = lastThis; + + lastArgs = lastThis = undefined; + lastInvokeTime = time; + result = func.apply(thisArg, args); + return result; + } + + function leadingEdge(time) { + // Reset any `maxWait` timer. + lastInvokeTime = time; + // Start the timer for the trailing edge. + timerId = setTimeout(timerExpired, wait); + // Invoke the leading edge. + return leading ? invokeFunc(time) : result; + } + + function remainingWait(time) { + var timeSinceLastCall = time - lastCallTime, + timeSinceLastInvoke = time - lastInvokeTime, + result = wait - timeSinceLastCall; + + return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; + } + + function shouldInvoke(time) { + var timeSinceLastCall = time - lastCallTime, + timeSinceLastInvoke = time - lastInvokeTime; + + // Either this is the first call, activity has stopped and we're at the + // trailing edge, the system time has gone backwards and we're treating + // it as the trailing edge, or we've hit the `maxWait` limit. + return (lastCallTime === undefined || (timeSinceLastCall >= wait) || + (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); + } + + function timerExpired() { + var time = now(); + if (shouldInvoke(time)) { + return trailingEdge(time); + } + // Restart the timer. + timerId = setTimeout(timerExpired, remainingWait(time)); + } + + function trailingEdge(time) { + timerId = undefined; + + // Only invoke if we have `lastArgs` which means `func` has been + // debounced at least once. + if (trailing && lastArgs) { + return invokeFunc(time); + } + lastArgs = lastThis = undefined; + return result; + } + + function cancel() { + if (timerId !== undefined) { + clearTimeout(timerId); + } + lastInvokeTime = 0; + lastArgs = lastCallTime = lastThis = timerId = undefined; + } + + function flush() { + return timerId === undefined ? result : trailingEdge(now()); + } + + function debounced() { + var time = now(), + isInvoking = shouldInvoke(time); + + lastArgs = arguments; + lastThis = this; + lastCallTime = time; + + if (isInvoking) { + if (timerId === undefined) { + return leadingEdge(lastCallTime); + } + if (maxing) { + // Handle invocations in a tight loop. + timerId = setTimeout(timerExpired, wait); + return invokeFunc(lastCallTime); + } + } + if (timerId === undefined) { + timerId = setTimeout(timerExpired, wait); + } + return result; + } + debounced.cancel = cancel; + debounced.flush = flush; + return debounced; + } + + /** + * Defers invoking the `func` until the current call stack has cleared. Any + * additional arguments are provided to `func` when it's invoked. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to defer. + * @param {...*} [args] The arguments to invoke `func` with. + * @returns {number} Returns the timer id. + * @example + * + * _.defer(function(text) { + * console.log(text); + * }, 'deferred'); + * // => Logs 'deferred' after one or more milliseconds. + */ + var defer = baseRest(function(func, args) { + return baseDelay(func, 1, args); + }); + + /** + * Invokes `func` after `wait` milliseconds. Any additional arguments are + * provided to `func` when it's invoked. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to delay. + * @param {number} wait The number of milliseconds to delay invocation. + * @param {...*} [args] The arguments to invoke `func` with. + * @returns {number} Returns the timer id. + * @example + * + * _.delay(function(text) { + * console.log(text); + * }, 1000, 'later'); + * // => Logs 'later' after one second. + */ + var delay = baseRest(function(func, wait, args) { + return baseDelay(func, toNumber(wait) || 0, args); + }); + + /** + * Creates a function that invokes `func` with arguments reversed. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Function + * @param {Function} func The function to flip arguments for. + * @returns {Function} Returns the new flipped function. + * @example + * + * var flipped = _.flip(function() { + * return _.toArray(arguments); + * }); + * + * flipped('a', 'b', 'c', 'd'); + * // => ['d', 'c', 'b', 'a'] + */ + function flip(func) { + return createWrap(func, FLIP_FLAG); + } + + /** + * Creates a function that memoizes the result of `func`. If `resolver` is + * provided, it determines the cache key for storing the result based on the + * arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is used as the map cache key. The `func` + * is invoked with the `this` binding of the memoized function. + * + * **Note:** The cache is exposed as the `cache` property on the memoized + * function. Its creation may be customized by replacing the `_.memoize.Cache` + * constructor with one whose instances implement the + * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) + * method interface of `delete`, `get`, `has`, and `set`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to have its output memoized. + * @param {Function} [resolver] The function to resolve the cache key. + * @returns {Function} Returns the new memoized function. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * var other = { 'c': 3, 'd': 4 }; + * + * var values = _.memoize(_.values); + * values(object); + * // => [1, 2] + * + * values(other); + * // => [3, 4] + * + * object.a = 2; + * values(object); + * // => [1, 2] + * + * // Modify the result cache. + * values.cache.set(object, ['a', 'b']); + * values(object); + * // => ['a', 'b'] + * + * // Replace `_.memoize.Cache`. + * _.memoize.Cache = WeakMap; + */ + function memoize(func, resolver) { + if (typeof func != 'function' || (resolver && typeof resolver != 'function')) { + throw new TypeError(FUNC_ERROR_TEXT); + } + var memoized = function() { + var args = arguments, + key = resolver ? resolver.apply(this, args) : args[0], + cache = memoized.cache; + + if (cache.has(key)) { + return cache.get(key); + } + var result = func.apply(this, args); + memoized.cache = cache.set(key, result); + return result; + }; + memoized.cache = new (memoize.Cache || MapCache); + return memoized; + } + + // Assign cache to `_.memoize`. + memoize.Cache = MapCache; + + /** + * Creates a function that negates the result of the predicate `func`. The + * `func` predicate is invoked with the `this` binding and arguments of the + * created function. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} predicate The predicate to negate. + * @returns {Function} Returns the new negated function. + * @example + * + * function isEven(n) { + * return n % 2 == 0; + * } + * + * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven)); + * // => [1, 3, 5] + */ + function negate(predicate) { + if (typeof predicate != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + return function() { + var args = arguments; + switch (args.length) { + case 0: return !predicate.call(this); + case 1: return !predicate.call(this, args[0]); + case 2: return !predicate.call(this, args[0], args[1]); + case 3: return !predicate.call(this, args[0], args[1], args[2]); + } + return !predicate.apply(this, args); + }; + } + + /** + * Creates a function that is restricted to invoking `func` once. Repeat calls + * to the function return the value of the first invocation. The `func` is + * invoked with the `this` binding and arguments of the created function. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to restrict. + * @returns {Function} Returns the new restricted function. + * @example + * + * var initialize = _.once(createApplication); + * initialize(); + * initialize(); + * // => `createApplication` is invoked once + */ + function once(func) { + return before(2, func); + } + + /** + * Creates a function that invokes `func` with its arguments transformed. + * + * @static + * @since 4.0.0 + * @memberOf _ + * @category Function + * @param {Function} func The function to wrap. + * @param {...(Function|Function[])} [transforms=[_.identity]] + * The argument transforms. + * @returns {Function} Returns the new function. + * @example + * + * function doubled(n) { + * return n * 2; + * } + * + * function square(n) { + * return n * n; + * } + * + * var func = _.overArgs(function(x, y) { + * return [x, y]; + * }, [square, doubled]); + * + * func(9, 3); + * // => [81, 6] + * + * func(10, 5); + * // => [100, 10] + */ + var overArgs = baseRest(function(func, transforms) { + transforms = (transforms.length == 1 && isArray(transforms[0])) + ? arrayMap(transforms[0], baseUnary(getIteratee())) + : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee())); + + var funcsLength = transforms.length; + return baseRest(function(args) { + var index = -1, + length = nativeMin(args.length, funcsLength); + + while (++index < length) { + args[index] = transforms[index].call(this, args[index]); + } + return apply(func, this, args); + }); + }); + + /** + * Creates a function that invokes `func` with `partials` prepended to the + * arguments it receives. This method is like `_.bind` except it does **not** + * alter the `this` binding. + * + * The `_.partial.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for partially applied arguments. + * + * **Note:** This method doesn't set the "length" property of partially + * applied functions. + * + * @static + * @memberOf _ + * @since 0.2.0 + * @category Function + * @param {Function} func The function to partially apply arguments to. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * function greet(greeting, name) { + * return greeting + ' ' + name; + * } + * + * var sayHelloTo = _.partial(greet, 'hello'); + * sayHelloTo('fred'); + * // => 'hello fred' + * + * // Partially applied with placeholders. + * var greetFred = _.partial(greet, _, 'fred'); + * greetFred('hi'); + * // => 'hi fred' + */ + var partial = baseRest(function(func, partials) { + var holders = replaceHolders(partials, getHolder(partial)); + return createWrap(func, PARTIAL_FLAG, undefined, partials, holders); + }); + + /** + * This method is like `_.partial` except that partially applied arguments + * are appended to the arguments it receives. + * + * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic + * builds, may be used as a placeholder for partially applied arguments. + * + * **Note:** This method doesn't set the "length" property of partially + * applied functions. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Function + * @param {Function} func The function to partially apply arguments to. + * @param {...*} [partials] The arguments to be partially applied. + * @returns {Function} Returns the new partially applied function. + * @example + * + * function greet(greeting, name) { + * return greeting + ' ' + name; + * } + * + * var greetFred = _.partialRight(greet, 'fred'); + * greetFred('hi'); + * // => 'hi fred' + * + * // Partially applied with placeholders. + * var sayHelloTo = _.partialRight(greet, 'hello', _); + * sayHelloTo('fred'); + * // => 'hello fred' + */ + var partialRight = baseRest(function(func, partials) { + var holders = replaceHolders(partials, getHolder(partialRight)); + return createWrap(func, PARTIAL_RIGHT_FLAG, undefined, partials, holders); + }); + + /** + * Creates a function that invokes `func` with arguments arranged according + * to the specified `indexes` where the argument value at the first index is + * provided as the first argument, the argument value at the second index is + * provided as the second argument, and so on. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Function + * @param {Function} func The function to rearrange arguments for. + * @param {...(number|number[])} indexes The arranged argument indexes. + * @returns {Function} Returns the new function. + * @example + * + * var rearged = _.rearg(function(a, b, c) { + * return [a, b, c]; + * }, [2, 0, 1]); + * + * rearged('b', 'c', 'a') + * // => ['a', 'b', 'c'] + */ + var rearg = baseRest(function(func, indexes) { + return createWrap(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes, 1)); + }); + + /** + * Creates a function that invokes `func` with the `this` binding of the + * created function and arguments from `start` and beyond provided as + * an array. + * + * **Note:** This method is based on the + * [rest parameter](https://mdn.io/rest_parameters). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Function + * @param {Function} func The function to apply a rest parameter to. + * @param {number} [start=func.length-1] The start position of the rest parameter. + * @returns {Function} Returns the new function. + * @example + * + * var say = _.rest(function(what, names) { + * return what + ' ' + _.initial(names).join(', ') + + * (_.size(names) > 1 ? ', & ' : '') + _.last(names); + * }); + * + * say('hello', 'fred', 'barney', 'pebbles'); + * // => 'hello fred, barney, & pebbles' + */ + function rest(func, start) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + start = start === undefined ? start : toInteger(start); + return baseRest(func, start); + } + + /** + * Creates a function that invokes `func` with the `this` binding of the + * create function and an array of arguments much like + * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply). + * + * **Note:** This method is based on the + * [spread operator](https://mdn.io/spread_operator). + * + * @static + * @memberOf _ + * @since 3.2.0 + * @category Function + * @param {Function} func The function to spread arguments over. + * @param {number} [start=0] The start position of the spread. + * @returns {Function} Returns the new function. + * @example + * + * var say = _.spread(function(who, what) { + * return who + ' says ' + what; + * }); + * + * say(['fred', 'hello']); + * // => 'fred says hello' + * + * var numbers = Promise.all([ + * Promise.resolve(40), + * Promise.resolve(36) + * ]); + * + * numbers.then(_.spread(function(x, y) { + * return x + y; + * })); + * // => a Promise of 76 + */ + function spread(func, start) { + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + start = start === undefined ? 0 : nativeMax(toInteger(start), 0); + return baseRest(function(args) { + var array = args[start], + otherArgs = castSlice(args, 0, start); + + if (array) { + arrayPush(otherArgs, array); + } + return apply(func, this, otherArgs); + }); + } + + /** + * Creates a throttled function that only invokes `func` at most once per + * every `wait` milliseconds. The throttled function comes with a `cancel` + * method to cancel delayed `func` invocations and a `flush` method to + * immediately invoke them. Provide `options` to indicate whether `func` + * should be invoked on the leading and/or trailing edge of the `wait` + * timeout. The `func` is invoked with the last arguments provided to the + * throttled function. Subsequent calls to the throttled function return the + * result of the last `func` invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the throttled function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until to the next tick, similar to `setTimeout` with a timeout of `0`. + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `_.throttle` and `_.debounce`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {Function} func The function to throttle. + * @param {number} [wait=0] The number of milliseconds to throttle invocations to. + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=true] + * Specify invoking on the leading edge of the timeout. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new throttled function. + * @example + * + * // Avoid excessively updating the position while scrolling. + * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); + * + * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. + * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); + * jQuery(element).on('click', throttled); + * + * // Cancel the trailing throttled invocation. + * jQuery(window).on('popstate', throttled.cancel); + */ + function throttle(func, wait, options) { + var leading = true, + trailing = true; + + if (typeof func != 'function') { + throw new TypeError(FUNC_ERROR_TEXT); + } + if (isObject(options)) { + leading = 'leading' in options ? !!options.leading : leading; + trailing = 'trailing' in options ? !!options.trailing : trailing; + } + return debounce(func, wait, { + 'leading': leading, + 'maxWait': wait, + 'trailing': trailing + }); + } + + /** + * Creates a function that accepts up to one argument, ignoring any + * additional arguments. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Function + * @param {Function} func The function to cap arguments for. + * @returns {Function} Returns the new capped function. + * @example + * + * _.map(['6', '8', '10'], _.unary(parseInt)); + * // => [6, 8, 10] + */ + function unary(func) { + return ary(func, 1); + } + + /** + * Creates a function that provides `value` to `wrapper` as its first + * argument. Any additional arguments provided to the function are appended + * to those provided to the `wrapper`. The wrapper is invoked with the `this` + * binding of the created function. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Function + * @param {*} value The value to wrap. + * @param {Function} [wrapper=identity] The wrapper function. + * @returns {Function} Returns the new function. + * @example + * + * var p = _.wrap(_.escape, function(func, text) { + * return '

' + func(text) + '

'; + * }); + * + * p('fred, barney, & pebbles'); + * // => '

fred, barney, & pebbles

' + */ + function wrap(value, wrapper) { + wrapper = wrapper == null ? identity : wrapper; + return partial(wrapper, value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Casts `value` as an array if it's not one. + * + * @static + * @memberOf _ + * @since 4.4.0 + * @category Lang + * @param {*} value The value to inspect. + * @returns {Array} Returns the cast array. + * @example + * + * _.castArray(1); + * // => [1] + * + * _.castArray({ 'a': 1 }); + * // => [{ 'a': 1 }] + * + * _.castArray('abc'); + * // => ['abc'] + * + * _.castArray(null); + * // => [null] + * + * _.castArray(undefined); + * // => [undefined] + * + * _.castArray(); + * // => [] + * + * var array = [1, 2, 3]; + * console.log(_.castArray(array) === array); + * // => true + */ + function castArray() { + if (!arguments.length) { + return []; + } + var value = arguments[0]; + return isArray(value) ? value : [value]; + } + + /** + * Creates a shallow clone of `value`. + * + * **Note:** This method is loosely based on the + * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm) + * and supports cloning arrays, array buffers, booleans, date objects, maps, + * numbers, `Object` objects, regexes, sets, strings, symbols, and typed + * arrays. The own enumerable properties of `arguments` objects are cloned + * as plain objects. An empty object is returned for uncloneable values such + * as error objects, functions, DOM nodes, and WeakMaps. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to clone. + * @returns {*} Returns the cloned value. + * @see _.cloneDeep + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var shallow = _.clone(objects); + * console.log(shallow[0] === objects[0]); + * // => true + */ + function clone(value) { + return baseClone(value, false, true); + } + + /** + * This method is like `_.clone` except that it accepts `customizer` which + * is invoked to produce the cloned value. If `customizer` returns `undefined`, + * cloning is handled by the method instead. The `customizer` is invoked with + * up to four arguments; (value [, index|key, object, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the cloned value. + * @see _.cloneDeepWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(false); + * } + * } + * + * var el = _.cloneWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 0 + */ + function cloneWith(value, customizer) { + return baseClone(value, false, true, customizer); + } + + /** + * This method is like `_.clone` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @returns {*} Returns the deep cloned value. + * @see _.clone + * @example + * + * var objects = [{ 'a': 1 }, { 'b': 2 }]; + * + * var deep = _.cloneDeep(objects); + * console.log(deep[0] === objects[0]); + * // => false + */ + function cloneDeep(value) { + return baseClone(value, true, true); + } + + /** + * This method is like `_.cloneWith` except that it recursively clones `value`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to recursively clone. + * @param {Function} [customizer] The function to customize cloning. + * @returns {*} Returns the deep cloned value. + * @see _.cloneWith + * @example + * + * function customizer(value) { + * if (_.isElement(value)) { + * return value.cloneNode(true); + * } + * } + * + * var el = _.cloneDeepWith(document.body, customizer); + * + * console.log(el === document.body); + * // => false + * console.log(el.nodeName); + * // => 'BODY' + * console.log(el.childNodes.length); + * // => 20 + */ + function cloneDeepWith(value, customizer) { + return baseClone(value, true, true, customizer); + } + + /** + * Checks if `object` conforms to `source` by invoking the predicate + * properties of `source` with the corresponding property values of `object`. + * + * **Note:** This method is equivalent to `_.conforms` when `source` is + * partially applied. + * + * @static + * @memberOf _ + * @since 4.14.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property predicates to conform to. + * @returns {boolean} Returns `true` if `object` conforms, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.conformsTo(object, { 'b': function(n) { return n > 1; } }); + * // => true + * + * _.conformsTo(object, { 'b': function(n) { return n > 2; } }); + * // => false + */ + function conformsTo(object, source) { + return source == null || baseConformsTo(object, source, keys(source)); + } + + /** + * Performs a + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) + * comparison between two values to determine if they are equivalent. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.eq(object, object); + * // => true + * + * _.eq(object, other); + * // => false + * + * _.eq('a', 'a'); + * // => true + * + * _.eq('a', Object('a')); + * // => false + * + * _.eq(NaN, NaN); + * // => true + */ + function eq(value, other) { + return value === other || (value !== value && other !== other); + } + + /** + * Checks if `value` is greater than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than `other`, + * else `false`. + * @see _.lt + * @example + * + * _.gt(3, 1); + * // => true + * + * _.gt(3, 3); + * // => false + * + * _.gt(1, 3); + * // => false + */ + var gt = createRelationalOperation(baseGt); + + /** + * Checks if `value` is greater than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is greater than or equal to + * `other`, else `false`. + * @see _.lte + * @example + * + * _.gte(3, 1); + * // => true + * + * _.gte(3, 3); + * // => true + * + * _.gte(1, 3); + * // => false + */ + var gte = createRelationalOperation(function(value, other) { + return value >= other; + }); + + /** + * Checks if `value` is likely an `arguments` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an `arguments` object, + * else `false`. + * @example + * + * _.isArguments(function() { return arguments; }()); + * // => true + * + * _.isArguments([1, 2, 3]); + * // => false + */ + function isArguments(value) { + // Safari 8.1 makes `arguments.callee` enumerable in strict mode. + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && + (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); + } + + /** + * Checks if `value` is classified as an `Array` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array, else `false`. + * @example + * + * _.isArray([1, 2, 3]); + * // => true + * + * _.isArray(document.body.children); + * // => false + * + * _.isArray('abc'); + * // => false + * + * _.isArray(_.noop); + * // => false + */ + var isArray = Array.isArray; + + /** + * Checks if `value` is classified as an `ArrayBuffer` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`. + * @example + * + * _.isArrayBuffer(new ArrayBuffer(2)); + * // => true + * + * _.isArrayBuffer(new Array(2)); + * // => false + */ + var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer; + + /** + * Checks if `value` is array-like. A value is considered array-like if it's + * not a function and has a `value.length` that's an integer greater than or + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is array-like, else `false`. + * @example + * + * _.isArrayLike([1, 2, 3]); + * // => true + * + * _.isArrayLike(document.body.children); + * // => true + * + * _.isArrayLike('abc'); + * // => true + * + * _.isArrayLike(_.noop); + * // => false + */ + function isArrayLike(value) { + return value != null && isLength(value.length) && !isFunction(value); + } + + /** + * This method is like `_.isArrayLike` except that it also checks if `value` + * is an object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an array-like object, + * else `false`. + * @example + * + * _.isArrayLikeObject([1, 2, 3]); + * // => true + * + * _.isArrayLikeObject(document.body.children); + * // => true + * + * _.isArrayLikeObject('abc'); + * // => false + * + * _.isArrayLikeObject(_.noop); + * // => false + */ + function isArrayLikeObject(value) { + return isObjectLike(value) && isArrayLike(value); + } + + /** + * Checks if `value` is classified as a boolean primitive or object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a boolean, else `false`. + * @example + * + * _.isBoolean(false); + * // => true + * + * _.isBoolean(null); + * // => false + */ + function isBoolean(value) { + return value === true || value === false || + (isObjectLike(value) && objectToString.call(value) == boolTag); + } + + /** + * Checks if `value` is a buffer. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. + * @example + * + * _.isBuffer(new Buffer(2)); + * // => true + * + * _.isBuffer(new Uint8Array(2)); + * // => false + */ + var isBuffer = nativeIsBuffer || stubFalse; + + /** + * Checks if `value` is classified as a `Date` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a date object, else `false`. + * @example + * + * _.isDate(new Date); + * // => true + * + * _.isDate('Mon April 23 2012'); + * // => false + */ + var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate; + + /** + * Checks if `value` is likely a DOM element. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`. + * @example + * + * _.isElement(document.body); + * // => true + * + * _.isElement(''); + * // => false + */ + function isElement(value) { + return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value); + } + + /** + * Checks if `value` is an empty object, collection, map, or set. + * + * Objects are considered empty if they have no own enumerable string keyed + * properties. + * + * Array-like values such as `arguments` objects, arrays, buffers, strings, or + * jQuery-like collections are considered empty if they have a `length` of `0`. + * Similarly, maps and sets are considered empty if they have a `size` of `0`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is empty, else `false`. + * @example + * + * _.isEmpty(null); + * // => true + * + * _.isEmpty(true); + * // => true + * + * _.isEmpty(1); + * // => true + * + * _.isEmpty([1, 2, 3]); + * // => false + * + * _.isEmpty({ 'a': 1 }); + * // => false + */ + function isEmpty(value) { + if (isArrayLike(value) && + (isArray(value) || typeof value == 'string' || + typeof value.splice == 'function' || isBuffer(value) || isArguments(value))) { + return !value.length; + } + var tag = getTag(value); + if (tag == mapTag || tag == setTag) { + return !value.size; + } + if (nonEnumShadows || isPrototype(value)) { + return !nativeKeys(value).length; + } + for (var key in value) { + if (hasOwnProperty.call(value, key)) { + return false; + } + } + return true; + } + + /** + * Performs a deep comparison between two values to determine if they are + * equivalent. + * + * **Note:** This method supports comparing arrays, array buffers, booleans, + * date objects, error objects, maps, numbers, `Object` objects, regexes, + * sets, strings, symbols, and typed arrays. `Object` objects are compared + * by their own, not inherited, enumerable properties. Functions and DOM + * nodes are **not** supported. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'a': 1 }; + * var other = { 'a': 1 }; + * + * _.isEqual(object, other); + * // => true + * + * object === other; + * // => false + */ + function isEqual(value, other) { + return baseIsEqual(value, other); + } + + /** + * This method is like `_.isEqual` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with up to + * six arguments: (objValue, othValue [, index|key, object, other, stack]). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, othValue) { + * if (isGreeting(objValue) && isGreeting(othValue)) { + * return true; + * } + * } + * + * var array = ['hello', 'goodbye']; + * var other = ['hi', 'goodbye']; + * + * _.isEqualWith(array, other, customizer); + * // => true + */ + function isEqualWith(value, other, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + var result = customizer ? customizer(value, other) : undefined; + return result === undefined ? baseIsEqual(value, other, customizer) : !!result; + } + + /** + * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, + * `SyntaxError`, `TypeError`, or `URIError` object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an error object, else `false`. + * @example + * + * _.isError(new Error); + * // => true + * + * _.isError(Error); + * // => false + */ + function isError(value) { + if (!isObjectLike(value)) { + return false; + } + return (objectToString.call(value) == errorTag) || + (typeof value.message == 'string' && typeof value.name == 'string'); + } + + /** + * Checks if `value` is a finite primitive number. + * + * **Note:** This method is based on + * [`Number.isFinite`](https://mdn.io/Number/isFinite). + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a finite number, else `false`. + * @example + * + * _.isFinite(3); + * // => true + * + * _.isFinite(Number.MIN_VALUE); + * // => true + * + * _.isFinite(Infinity); + * // => false + * + * _.isFinite('3'); + * // => false + */ + function isFinite(value) { + return typeof value == 'number' && nativeIsFinite(value); + } + + /** + * Checks if `value` is classified as a `Function` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a function, else `false`. + * @example + * + * _.isFunction(_); + * // => true + * + * _.isFunction(/abc/); + * // => false + */ + function isFunction(value) { + // The use of `Object#toString` avoids issues with the `typeof` operator + // in Safari 8-9 which returns 'object' for typed array and other constructors. + var tag = isObject(value) ? objectToString.call(value) : ''; + return tag == funcTag || tag == genTag; + } + + /** + * Checks if `value` is an integer. + * + * **Note:** This method is based on + * [`Number.isInteger`](https://mdn.io/Number/isInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an integer, else `false`. + * @example + * + * _.isInteger(3); + * // => true + * + * _.isInteger(Number.MIN_VALUE); + * // => false + * + * _.isInteger(Infinity); + * // => false + * + * _.isInteger('3'); + * // => false + */ + function isInteger(value) { + return typeof value == 'number' && value == toInteger(value); + } + + /** + * Checks if `value` is a valid array-like length. + * + * **Note:** This method is loosely based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + * @example + * + * _.isLength(3); + * // => true + * + * _.isLength(Number.MIN_VALUE); + * // => false + * + * _.isLength(Infinity); + * // => false + * + * _.isLength('3'); + * // => false + */ + function isLength(value) { + return typeof value == 'number' && + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); + * // => true + * + * _.isObject(_.noop); + * // => true + * + * _.isObject(null); + * // => false + */ + function isObject(value) { + var type = typeof value; + return !!value && (type == 'object' || type == 'function'); + } + + /** + * Checks if `value` is object-like. A value is object-like if it's not `null` + * and has a `typeof` result of "object". + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is object-like, else `false`. + * @example + * + * _.isObjectLike({}); + * // => true + * + * _.isObjectLike([1, 2, 3]); + * // => true + * + * _.isObjectLike(_.noop); + * // => false + * + * _.isObjectLike(null); + * // => false + */ + function isObjectLike(value) { + return !!value && typeof value == 'object'; + } + + /** + * Checks if `value` is classified as a `Map` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a map, else `false`. + * @example + * + * _.isMap(new Map); + * // => true + * + * _.isMap(new WeakMap); + * // => false + */ + var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap; + + /** + * Performs a partial deep comparison between `object` and `source` to + * determine if `object` contains equivalent property values. + * + * **Note:** This method is equivalent to `_.matches` when `source` is + * partially applied. + * + * Partial comparisons will match empty array and empty object `source` + * values against any array or object value, respectively. See `_.isEqual` + * for a list of supported value comparisons. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * var object = { 'a': 1, 'b': 2 }; + * + * _.isMatch(object, { 'b': 2 }); + * // => true + * + * _.isMatch(object, { 'b': 1 }); + * // => false + */ + function isMatch(object, source) { + return object === source || baseIsMatch(object, source, getMatchData(source)); + } + + /** + * This method is like `_.isMatch` except that it accepts `customizer` which + * is invoked to compare values. If `customizer` returns `undefined`, comparisons + * are handled by the method instead. The `customizer` is invoked with five + * arguments: (objValue, srcValue, index|key, object, source). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {Object} object The object to inspect. + * @param {Object} source The object of property values to match. + * @param {Function} [customizer] The function to customize comparisons. + * @returns {boolean} Returns `true` if `object` is a match, else `false`. + * @example + * + * function isGreeting(value) { + * return /^h(?:i|ello)$/.test(value); + * } + * + * function customizer(objValue, srcValue) { + * if (isGreeting(objValue) && isGreeting(srcValue)) { + * return true; + * } + * } + * + * var object = { 'greeting': 'hello' }; + * var source = { 'greeting': 'hi' }; + * + * _.isMatchWith(object, source, customizer); + * // => true + */ + function isMatchWith(object, source, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return baseIsMatch(object, source, getMatchData(source), customizer); + } + + /** + * Checks if `value` is `NaN`. + * + * **Note:** This method is based on + * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as + * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for + * `undefined` and other non-number values. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`. + * @example + * + * _.isNaN(NaN); + * // => true + * + * _.isNaN(new Number(NaN)); + * // => true + * + * isNaN(undefined); + * // => true + * + * _.isNaN(undefined); + * // => false + */ + function isNaN(value) { + // An `NaN` primitive is the only value that is not equal to itself. + // Perform the `toStringTag` check first to avoid errors with some + // ActiveX objects in IE. + return isNumber(value) && value != +value; + } + + /** + * Checks if `value` is a pristine native function. + * + * **Note:** This method can't reliably detect native functions in the presence + * of the core-js package because core-js circumvents this kind of detection. + * Despite multiple requests, the core-js maintainer has made it clear: any + * attempt to fix the detection will be obstructed. As a result, we're left + * with little choice but to throw an error. Unfortunately, this also affects + * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill), + * which rely on core-js. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a native function, + * else `false`. + * @example + * + * _.isNative(Array.prototype.push); + * // => true + * + * _.isNative(_); + * // => false + */ + function isNative(value) { + if (isMaskable(value)) { + throw new Error('This method is not supported with core-js. Try https://github.com/es-shims.'); + } + return baseIsNative(value); + } + + /** + * Checks if `value` is `null`. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `null`, else `false`. + * @example + * + * _.isNull(null); + * // => true + * + * _.isNull(void 0); + * // => false + */ + function isNull(value) { + return value === null; + } + + /** + * Checks if `value` is `null` or `undefined`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is nullish, else `false`. + * @example + * + * _.isNil(null); + * // => true + * + * _.isNil(void 0); + * // => true + * + * _.isNil(NaN); + * // => false + */ + function isNil(value) { + return value == null; + } + + /** + * Checks if `value` is classified as a `Number` primitive or object. + * + * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are + * classified as numbers, use the `_.isFinite` method. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a number, else `false`. + * @example + * + * _.isNumber(3); + * // => true + * + * _.isNumber(Number.MIN_VALUE); + * // => true + * + * _.isNumber(Infinity); + * // => true + * + * _.isNumber('3'); + * // => false + */ + function isNumber(value) { + return typeof value == 'number' || + (isObjectLike(value) && objectToString.call(value) == numberTag); + } + + /** + * Checks if `value` is a plain object, that is, an object created by the + * `Object` constructor or one with a `[[Prototype]]` of `null`. + * + * @static + * @memberOf _ + * @since 0.8.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * _.isPlainObject(new Foo); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true + * + * _.isPlainObject(Object.create(null)); + * // => true + */ + function isPlainObject(value) { + if (!isObjectLike(value) || + objectToString.call(value) != objectTag || isHostObject(value)) { + return false; + } + var proto = getPrototype(value); + if (proto === null) { + return true; + } + var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; + return (typeof Ctor == 'function' && + Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString); + } + + /** + * Checks if `value` is classified as a `RegExp` object. + * + * @static + * @memberOf _ + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a regexp, else `false`. + * @example + * + * _.isRegExp(/abc/); + * // => true + * + * _.isRegExp('/abc/'); + * // => false + */ + var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp; + + /** + * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754 + * double precision number which isn't the result of a rounded unsafe integer. + * + * **Note:** This method is based on + * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`. + * @example + * + * _.isSafeInteger(3); + * // => true + * + * _.isSafeInteger(Number.MIN_VALUE); + * // => false + * + * _.isSafeInteger(Infinity); + * // => false + * + * _.isSafeInteger('3'); + * // => false + */ + function isSafeInteger(value) { + return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER; + } + + /** + * Checks if `value` is classified as a `Set` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a set, else `false`. + * @example + * + * _.isSet(new Set); + * // => true + * + * _.isSet(new WeakSet); + * // => false + */ + var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet; + + /** + * Checks if `value` is classified as a `String` primitive or object. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a string, else `false`. + * @example + * + * _.isString('abc'); + * // => true + * + * _.isString(1); + * // => false + */ + function isString(value) { + return typeof value == 'string' || + (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag); + } + + /** + * Checks if `value` is classified as a `Symbol` primitive or object. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. + * @example + * + * _.isSymbol(Symbol.iterator); + * // => true + * + * _.isSymbol('abc'); + * // => false + */ + function isSymbol(value) { + return typeof value == 'symbol' || + (isObjectLike(value) && objectToString.call(value) == symbolTag); + } + + /** + * Checks if `value` is classified as a typed array. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. + * @example + * + * _.isTypedArray(new Uint8Array); + * // => true + * + * _.isTypedArray([]); + * // => false + */ + var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; + + /** + * Checks if `value` is `undefined`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`. + * @example + * + * _.isUndefined(void 0); + * // => true + * + * _.isUndefined(null); + * // => false + */ + function isUndefined(value) { + return value === undefined; + } + + /** + * Checks if `value` is classified as a `WeakMap` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak map, else `false`. + * @example + * + * _.isWeakMap(new WeakMap); + * // => true + * + * _.isWeakMap(new Map); + * // => false + */ + function isWeakMap(value) { + return isObjectLike(value) && getTag(value) == weakMapTag; + } + + /** + * Checks if `value` is classified as a `WeakSet` object. + * + * @static + * @memberOf _ + * @since 4.3.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a weak set, else `false`. + * @example + * + * _.isWeakSet(new WeakSet); + * // => true + * + * _.isWeakSet(new Set); + * // => false + */ + function isWeakSet(value) { + return isObjectLike(value) && objectToString.call(value) == weakSetTag; + } + + /** + * Checks if `value` is less than `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than `other`, + * else `false`. + * @see _.gt + * @example + * + * _.lt(1, 3); + * // => true + * + * _.lt(3, 3); + * // => false + * + * _.lt(3, 1); + * // => false + */ + var lt = createRelationalOperation(baseLt); + + /** + * Checks if `value` is less than or equal to `other`. + * + * @static + * @memberOf _ + * @since 3.9.0 + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @returns {boolean} Returns `true` if `value` is less than or equal to + * `other`, else `false`. + * @see _.gte + * @example + * + * _.lte(1, 3); + * // => true + * + * _.lte(3, 3); + * // => true + * + * _.lte(3, 1); + * // => false + */ + var lte = createRelationalOperation(function(value, other) { + return value <= other; + }); + + /** + * Converts `value` to an array. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Lang + * @param {*} value The value to convert. + * @returns {Array} Returns the converted array. + * @example + * + * _.toArray({ 'a': 1, 'b': 2 }); + * // => [1, 2] + * + * _.toArray('abc'); + * // => ['a', 'b', 'c'] + * + * _.toArray(1); + * // => [] + * + * _.toArray(null); + * // => [] + */ + function toArray(value) { + if (!value) { + return []; + } + if (isArrayLike(value)) { + return isString(value) ? stringToArray(value) : copyArray(value); + } + if (iteratorSymbol && value[iteratorSymbol]) { + return iteratorToArray(value[iteratorSymbol]()); + } + var tag = getTag(value), + func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values); + + return func(value); + } + + /** + * Converts `value` to a finite number. + * + * @static + * @memberOf _ + * @since 4.12.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted number. + * @example + * + * _.toFinite(3.2); + * // => 3.2 + * + * _.toFinite(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toFinite(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toFinite('3.2'); + * // => 3.2 + */ + function toFinite(value) { + if (!value) { + return value === 0 ? value : 0; + } + value = toNumber(value); + if (value === INFINITY || value === -INFINITY) { + var sign = (value < 0 ? -1 : 1); + return sign * MAX_INTEGER; + } + return value === value ? value : 0; + } + + /** + * Converts `value` to an integer. + * + * **Note:** This method is loosely based on + * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toInteger(3.2); + * // => 3 + * + * _.toInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toInteger(Infinity); + * // => 1.7976931348623157e+308 + * + * _.toInteger('3.2'); + * // => 3 + */ + function toInteger(value) { + var result = toFinite(value), + remainder = result % 1; + + return result === result ? (remainder ? result - remainder : result) : 0; + } + + /** + * Converts `value` to an integer suitable for use as the length of an + * array-like object. + * + * **Note:** This method is based on + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toLength(3.2); + * // => 3 + * + * _.toLength(Number.MIN_VALUE); + * // => 0 + * + * _.toLength(Infinity); + * // => 4294967295 + * + * _.toLength('3.2'); + * // => 3 + */ + function toLength(value) { + return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0; + } + + /** + * Converts `value` to a number. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {number} Returns the number. + * @example + * + * _.toNumber(3.2); + * // => 3.2 + * + * _.toNumber(Number.MIN_VALUE); + * // => 5e-324 + * + * _.toNumber(Infinity); + * // => Infinity + * + * _.toNumber('3.2'); + * // => 3.2 + */ + function toNumber(value) { + if (typeof value == 'number') { + return value; + } + if (isSymbol(value)) { + return NAN; + } + if (isObject(value)) { + var other = typeof value.valueOf == 'function' ? value.valueOf() : value; + value = isObject(other) ? (other + '') : other; + } + if (typeof value != 'string') { + return value === 0 ? value : +value; + } + value = value.replace(reTrim, ''); + var isBinary = reIsBinary.test(value); + return (isBinary || reIsOctal.test(value)) + ? freeParseInt(value.slice(2), isBinary ? 2 : 8) + : (reIsBadHex.test(value) ? NAN : +value); + } + + /** + * Converts `value` to a plain object flattening inherited enumerable string + * keyed properties of `value` to own properties of the plain object. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {Object} Returns the converted plain object. + * @example + * + * function Foo() { + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.assign({ 'a': 1 }, new Foo); + * // => { 'a': 1, 'b': 2 } + * + * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); + * // => { 'a': 1, 'b': 2, 'c': 3 } + */ + function toPlainObject(value) { + return copyObject(value, keysIn(value)); + } + + /** + * Converts `value` to a safe integer. A safe integer can be compared and + * represented correctly. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to convert. + * @returns {number} Returns the converted integer. + * @example + * + * _.toSafeInteger(3.2); + * // => 3 + * + * _.toSafeInteger(Number.MIN_VALUE); + * // => 0 + * + * _.toSafeInteger(Infinity); + * // => 9007199254740991 + * + * _.toSafeInteger('3.2'); + * // => 3 + */ + function toSafeInteger(value) { + return baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER); + } + + /** + * Converts `value` to a string. An empty string is returned for `null` + * and `undefined` values. The sign of `-0` is preserved. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Lang + * @param {*} value The value to process. + * @returns {string} Returns the string. + * @example + * + * _.toString(null); + * // => '' + * + * _.toString(-0); + * // => '-0' + * + * _.toString([1, 2, 3]); + * // => '1,2,3' + */ + function toString(value) { + return value == null ? '' : baseToString(value); + } + + /*------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable string keyed properties of source objects to the + * destination object. Source objects are applied from left to right. + * Subsequent sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object` and is loosely based on + * [`Object.assign`](https://mdn.io/Object/assign). + * + * @static + * @memberOf _ + * @since 0.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assignIn + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assign({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'c': 3 } + */ + var assign = createAssigner(function(object, source) { + if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) { + copyObject(source, keys(source), object); + return; + } + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + assignValue(object, key, source[key]); + } + } + }); + + /** + * This method is like `_.assign` except that it iterates over own and + * inherited source properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extend + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.assign + * @example + * + * function Foo() { + * this.a = 1; + * } + * + * function Bar() { + * this.c = 3; + * } + * + * Foo.prototype.b = 2; + * Bar.prototype.d = 4; + * + * _.assignIn({ 'a': 0 }, new Foo, new Bar); + * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 } + */ + var assignIn = createAssigner(function(object, source) { + copyObject(source, keysIn(source), object); + }); + + /** + * This method is like `_.assignIn` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias extendWith + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignInWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keysIn(source), object, customizer); + }); + + /** + * This method is like `_.assign` except that it accepts `customizer` + * which is invoked to produce the assigned values. If `customizer` returns + * `undefined`, assignment is handled by the method instead. The `customizer` + * is invoked with five arguments: (objValue, srcValue, key, object, source). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @see _.assignInWith + * @example + * + * function customizer(objValue, srcValue) { + * return _.isUndefined(objValue) ? srcValue : objValue; + * } + * + * var defaults = _.partialRight(_.assignWith, customizer); + * + * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var assignWith = createAssigner(function(object, source, srcIndex, customizer) { + copyObject(source, keys(source), object, customizer); + }); + + /** + * Creates an array of values corresponding to `paths` of `object`. + * + * @static + * @memberOf _ + * @since 1.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {...(string|string[])} [paths] The property paths of elements to pick. + * @returns {Array} Returns the picked values. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; + * + * _.at(object, ['a[0].b.c', 'a[1]']); + * // => [3, 4] + */ + var at = baseRest(function(object, paths) { + return baseAt(object, baseFlatten(paths, 1)); + }); + + /** + * Creates an object that inherits from the `prototype` object. If a + * `properties` object is given, its own enumerable string keyed properties + * are assigned to the created object. + * + * @static + * @memberOf _ + * @since 2.3.0 + * @category Object + * @param {Object} prototype The object to inherit from. + * @param {Object} [properties] The properties to assign to the object. + * @returns {Object} Returns the new object. + * @example + * + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * function Circle() { + * Shape.call(this); + * } + * + * Circle.prototype = _.create(Shape.prototype, { + * 'constructor': Circle + * }); + * + * var circle = new Circle; + * circle instanceof Circle; + * // => true + * + * circle instanceof Shape; + * // => true + */ + function create(prototype, properties) { + var result = baseCreate(prototype); + return properties ? baseAssign(result, properties) : result; + } + + /** + * Assigns own and inherited enumerable string keyed properties of source + * objects to the destination object for all destination properties that + * resolve to `undefined`. Source objects are applied from left to right. + * Once a property is set, additional values of the same property are ignored. + * + * **Note:** This method mutates `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaultsDeep + * @example + * + * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); + * // => { 'a': 1, 'b': 2 } + */ + var defaults = baseRest(function(args) { + args.push(undefined, assignInDefaults); + return apply(assignInWith, undefined, args); + }); + + /** + * This method is like `_.defaults` except that it recursively assigns + * default properties. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.10.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @see _.defaults + * @example + * + * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } }); + * // => { 'a': { 'b': 2, 'c': 3 } } + */ + var defaultsDeep = baseRest(function(args) { + args.push(undefined, mergeDefaults); + return apply(mergeWith, undefined, args); + }); + + /** + * This method is like `_.find` except that it returns the key of the first + * element `predicate` returns truthy for instead of the element itself. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findKey(users, function(o) { return o.age < 40; }); + * // => 'barney' (iteration order is not guaranteed) + * + * // The `_.matches` iteratee shorthand. + * _.findKey(users, { 'age': 1, 'active': true }); + * // => 'pebbles' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findKey(users, 'active'); + * // => 'barney' + */ + function findKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwn); + } + + /** + * This method is like `_.findKey` except that it iterates over elements of + * a collection in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @param {Function} [predicate=_.identity] The function invoked per iteration. + * @returns {string|undefined} Returns the key of the matched element, + * else `undefined`. + * @example + * + * var users = { + * 'barney': { 'age': 36, 'active': true }, + * 'fred': { 'age': 40, 'active': false }, + * 'pebbles': { 'age': 1, 'active': true } + * }; + * + * _.findLastKey(users, function(o) { return o.age < 40; }); + * // => returns 'pebbles' assuming `_.findKey` returns 'barney' + * + * // The `_.matches` iteratee shorthand. + * _.findLastKey(users, { 'age': 36, 'active': true }); + * // => 'barney' + * + * // The `_.matchesProperty` iteratee shorthand. + * _.findLastKey(users, ['active', false]); + * // => 'fred' + * + * // The `_.property` iteratee shorthand. + * _.findLastKey(users, 'active'); + * // => 'pebbles' + */ + function findLastKey(object, predicate) { + return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight); + } + + /** + * Iterates over own and inherited enumerable string keyed properties of an + * object and invokes `iteratee` for each property. The iteratee is invoked + * with three arguments: (value, key, object). Iteratee functions may exit + * iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forInRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forIn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed). + */ + function forIn(object, iteratee) { + return object == null + ? object + : baseFor(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * This method is like `_.forIn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forIn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forInRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'. + */ + function forInRight(object, iteratee) { + return object == null + ? object + : baseForRight(object, getIteratee(iteratee, 3), keysIn); + } + + /** + * Iterates over own enumerable string keyed properties of an object and + * invokes `iteratee` for each property. The iteratee is invoked with three + * arguments: (value, key, object). Iteratee functions may exit iteration + * early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 0.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwnRight + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwn(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'a' then 'b' (iteration order is not guaranteed). + */ + function forOwn(object, iteratee) { + return object && baseForOwn(object, getIteratee(iteratee, 3)); + } + + /** + * This method is like `_.forOwn` except that it iterates over properties of + * `object` in the opposite order. + * + * @static + * @memberOf _ + * @since 2.0.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns `object`. + * @see _.forOwn + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.forOwnRight(new Foo, function(value, key) { + * console.log(key); + * }); + * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'. + */ + function forOwnRight(object, iteratee) { + return object && baseForOwnRight(object, getIteratee(iteratee, 3)); + } + + /** + * Creates an array of function property names from own enumerable properties + * of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functionsIn + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functions(new Foo); + * // => ['a', 'b'] + */ + function functions(object) { + return object == null ? [] : baseFunctions(object, keys(object)); + } + + /** + * Creates an array of function property names from own and inherited + * enumerable properties of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to inspect. + * @returns {Array} Returns the function names. + * @see _.functions + * @example + * + * function Foo() { + * this.a = _.constant('a'); + * this.b = _.constant('b'); + * } + * + * Foo.prototype.c = _.constant('c'); + * + * _.functionsIn(new Foo); + * // => ['a', 'b', 'c'] + */ + function functionsIn(object) { + return object == null ? [] : baseFunctions(object, keysIn(object)); + } + + /** + * Gets the value at `path` of `object`. If the resolved value is + * `undefined`, the `defaultValue` is returned in its place. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to get. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.get(object, 'a[0].b.c'); + * // => 3 + * + * _.get(object, ['a', '0', 'b', 'c']); + * // => 3 + * + * _.get(object, 'a.b.c', 'default'); + * // => 'default' + */ + function get(object, path, defaultValue) { + var result = object == null ? undefined : baseGet(object, path); + return result === undefined ? defaultValue : result; + } + + /** + * Checks if `path` is a direct property of `object`. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = { 'a': { 'b': 2 } }; + * var other = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.has(object, 'a'); + * // => true + * + * _.has(object, 'a.b'); + * // => true + * + * _.has(object, ['a', 'b']); + * // => true + * + * _.has(other, 'a'); + * // => false + */ + function has(object, path) { + return object != null && hasPath(object, path, baseHas); + } + + /** + * Checks if `path` is a direct or inherited property of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path to check. + * @returns {boolean} Returns `true` if `path` exists, else `false`. + * @example + * + * var object = _.create({ 'a': _.create({ 'b': 2 }) }); + * + * _.hasIn(object, 'a'); + * // => true + * + * _.hasIn(object, 'a.b'); + * // => true + * + * _.hasIn(object, ['a', 'b']); + * // => true + * + * _.hasIn(object, 'b'); + * // => false + */ + function hasIn(object, path) { + return object != null && hasPath(object, path, baseHasIn); + } + + /** + * Creates an object composed of the inverted keys and values of `object`. + * If `object` contains duplicate values, subsequent values overwrite + * property assignments of previous values. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Object + * @param {Object} object The object to invert. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invert(object); + * // => { '1': 'c', '2': 'b' } + */ + var invert = createInverter(function(result, value, key) { + result[value] = key; + }, constant(identity)); + + /** + * This method is like `_.invert` except that the inverted object is generated + * from the results of running each element of `object` thru `iteratee`. The + * corresponding inverted value of each inverted key is an array of keys + * responsible for generating the inverted value. The iteratee is invoked + * with one argument: (value). + * + * @static + * @memberOf _ + * @since 4.1.0 + * @category Object + * @param {Object} object The object to invert. + * @param {Function} [iteratee=_.identity] The iteratee invoked per element. + * @returns {Object} Returns the new inverted object. + * @example + * + * var object = { 'a': 1, 'b': 2, 'c': 1 }; + * + * _.invertBy(object); + * // => { '1': ['a', 'c'], '2': ['b'] } + * + * _.invertBy(object, function(value) { + * return 'group' + value; + * }); + * // => { 'group1': ['a', 'c'], 'group2': ['b'] } + */ + var invertBy = createInverter(function(result, value, key) { + if (hasOwnProperty.call(result, value)) { + result[value].push(key); + } else { + result[value] = [key]; + } + }, getIteratee); + + /** + * Invokes the method at `path` of `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the method to invoke. + * @param {...*} [args] The arguments to invoke the method with. + * @returns {*} Returns the result of the invoked method. + * @example + * + * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] }; + * + * _.invoke(object, 'a[0].b.c.slice', 1, 3); + * // => [2, 3] + */ + var invoke = baseRest(baseInvoke); + + /** + * Creates an array of the own enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. See the + * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) + * for more details. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keys(new Foo); + * // => ['a', 'b'] (iteration order is not guaranteed) + * + * _.keys('hi'); + * // => ['0', '1'] + */ + function keys(object) { + return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); + } + + /** + * Creates an array of the own and inherited enumerable property names of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property names. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.keysIn(new Foo); + * // => ['a', 'b', 'c'] (iteration order is not guaranteed) + */ + function keysIn(object) { + return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); + } + + /** + * The opposite of `_.mapValues`; this method creates an object with the + * same values as `object` and keys generated by running each own enumerable + * string keyed property of `object` thru `iteratee`. The iteratee is invoked + * with three arguments: (value, key, object). + * + * @static + * @memberOf _ + * @since 3.8.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapValues + * @example + * + * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) { + * return key + value; + * }); + * // => { 'a1': 1, 'b2': 2 } + */ + function mapKeys(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + + baseForOwn(object, function(value, key, object) { + result[iteratee(value, key, object)] = value; + }); + return result; + } + + /** + * Creates an object with the same keys as `object` and values generated + * by running each own enumerable string keyed property of `object` thru + * `iteratee`. The iteratee is invoked with three arguments: + * (value, key, object). + * + * @static + * @memberOf _ + * @since 2.4.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @returns {Object} Returns the new mapped object. + * @see _.mapKeys + * @example + * + * var users = { + * 'fred': { 'user': 'fred', 'age': 40 }, + * 'pebbles': { 'user': 'pebbles', 'age': 1 } + * }; + * + * _.mapValues(users, function(o) { return o.age; }); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + * + * // The `_.property` iteratee shorthand. + * _.mapValues(users, 'age'); + * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed) + */ + function mapValues(object, iteratee) { + var result = {}; + iteratee = getIteratee(iteratee, 3); + + baseForOwn(object, function(value, key, object) { + result[key] = iteratee(value, key, object); + }); + return result; + } + + /** + * This method is like `_.assign` except that it recursively merges own and + * inherited enumerable string keyed properties of source objects into the + * destination object. Source properties that resolve to `undefined` are + * skipped if a destination value exists. Array and plain object properties + * are merged recursively. Other objects and value types are overridden by + * assignment. Source objects are applied from left to right. Subsequent + * sources overwrite property assignments of previous sources. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 0.5.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} [sources] The source objects. + * @returns {Object} Returns `object`. + * @example + * + * var object = { + * 'a': [{ 'b': 2 }, { 'd': 4 }] + * }; + * + * var other = { + * 'a': [{ 'c': 3 }, { 'e': 5 }] + * }; + * + * _.merge(object, other); + * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] } + */ + var merge = createAssigner(function(object, source, srcIndex) { + baseMerge(object, source, srcIndex); + }); + + /** + * This method is like `_.merge` except that it accepts `customizer` which + * is invoked to produce the merged values of the destination and source + * properties. If `customizer` returns `undefined`, merging is handled by the + * method instead. The `customizer` is invoked with seven arguments: + * (objValue, srcValue, key, object, source, stack). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The destination object. + * @param {...Object} sources The source objects. + * @param {Function} customizer The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * function customizer(objValue, srcValue) { + * if (_.isArray(objValue)) { + * return objValue.concat(srcValue); + * } + * } + * + * var object = { 'a': [1], 'b': [2] }; + * var other = { 'a': [3], 'b': [4] }; + * + * _.mergeWith(object, other, customizer); + * // => { 'a': [1, 3], 'b': [2, 4] } + */ + var mergeWith = createAssigner(function(object, source, srcIndex, customizer) { + baseMerge(object, source, srcIndex, customizer); + }); + + /** + * The opposite of `_.pick`; this method creates an object composed of the + * own and inherited enumerable string keyed properties of `object` that are + * not omitted. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [props] The property identifiers to omit. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omit(object, ['a', 'c']); + * // => { 'b': '2' } + */ + var omit = baseRest(function(object, props) { + if (object == null) { + return {}; + } + props = arrayMap(baseFlatten(props, 1), toKey); + return basePick(object, baseDifference(getAllKeysIn(object), props)); + }); + + /** + * The opposite of `_.pickBy`; this method creates an object composed of + * the own and inherited enumerable string keyed properties of `object` that + * `predicate` doesn't return truthy for. The predicate is invoked with two + * arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.omitBy(object, _.isNumber); + * // => { 'b': '2' } + */ + function omitBy(object, predicate) { + return pickBy(object, negate(getIteratee(predicate))); + } + + /** + * Creates an object composed of the picked `object` properties. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The source object. + * @param {...(string|string[])} [props] The property identifiers to pick. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pick(object, ['a', 'c']); + * // => { 'a': 1, 'c': 3 } + */ + var pick = baseRest(function(object, props) { + return object == null ? {} : basePick(object, arrayMap(baseFlatten(props, 1), toKey)); + }); + + /** + * Creates an object composed of the `object` properties `predicate` returns + * truthy for. The predicate is invoked with two arguments: (value, key). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The source object. + * @param {Function} [predicate=_.identity] The function invoked per property. + * @returns {Object} Returns the new object. + * @example + * + * var object = { 'a': 1, 'b': '2', 'c': 3 }; + * + * _.pickBy(object, _.isNumber); + * // => { 'a': 1, 'c': 3 } + */ + function pickBy(object, predicate) { + return object == null ? {} : basePickBy(object, getAllKeysIn(object), getIteratee(predicate)); + } + + /** + * This method is like `_.get` except that if the resolved value is a + * function it's invoked with the `this` binding of its parent object and + * its result is returned. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @param {Array|string} path The path of the property to resolve. + * @param {*} [defaultValue] The value returned for `undefined` resolved values. + * @returns {*} Returns the resolved value. + * @example + * + * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] }; + * + * _.result(object, 'a[0].b.c1'); + * // => 3 + * + * _.result(object, 'a[0].b.c2'); + * // => 4 + * + * _.result(object, 'a[0].b.c3', 'default'); + * // => 'default' + * + * _.result(object, 'a[0].b.c3', _.constant('default')); + * // => 'default' + */ + function result(object, path, defaultValue) { + path = isKey(path, object) ? [path] : castPath(path); + + var index = -1, + length = path.length; + + // Ensure the loop is entered when path is empty. + if (!length) { + object = undefined; + length = 1; + } + while (++index < length) { + var value = object == null ? undefined : object[toKey(path[index])]; + if (value === undefined) { + index = length; + value = defaultValue; + } + object = isFunction(value) ? value.call(object) : value; + } + return object; + } + + /** + * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, + * it's created. Arrays are created for missing index properties while objects + * are created for all other missing properties. Use `_.setWith` to customize + * `path` creation. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 3.7.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.set(object, 'a[0].b.c', 4); + * console.log(object.a[0].b.c); + * // => 4 + * + * _.set(object, ['x', '0', 'y', 'z'], 5); + * console.log(object.x[0].y.z); + * // => 5 + */ + function set(object, path, value) { + return object == null ? object : baseSet(object, path, value); + } + + /** + * This method is like `_.set` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {*} value The value to set. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.setWith(object, '[0][1]', 'a', Object); + * // => { '0': { '1': 'a' } } + */ + function setWith(object, path, value, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseSet(object, path, value, customizer); + } + + /** + * Creates an array of own enumerable string keyed-value pairs for `object` + * which can be consumed by `_.fromPairs`. If `object` is a map or set, its + * entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entries + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairs(new Foo); + * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed) + */ + var toPairs = createToPairs(keys); + + /** + * Creates an array of own and inherited enumerable string keyed-value pairs + * for `object` which can be consumed by `_.fromPairs`. If `object` is a map + * or set, its entries are returned. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @alias entriesIn + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the key-value pairs. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.toPairsIn(new Foo); + * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed) + */ + var toPairsIn = createToPairs(keysIn); + + /** + * An alternative to `_.reduce`; this method transforms `object` to a new + * `accumulator` object which is the result of running each of its own + * enumerable string keyed properties thru `iteratee`, with each invocation + * potentially mutating the `accumulator` object. If `accumulator` is not + * provided, a new object with the same `[[Prototype]]` will be used. The + * iteratee is invoked with four arguments: (accumulator, value, key, object). + * Iteratee functions may exit iteration early by explicitly returning `false`. + * + * @static + * @memberOf _ + * @since 1.3.0 + * @category Object + * @param {Object} object The object to iterate over. + * @param {Function} [iteratee=_.identity] The function invoked per iteration. + * @param {*} [accumulator] The custom accumulator value. + * @returns {*} Returns the accumulated value. + * @example + * + * _.transform([2, 3, 4], function(result, n) { + * result.push(n *= n); + * return n % 2 == 0; + * }, []); + * // => [4, 9] + * + * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) { + * (result[value] || (result[value] = [])).push(key); + * }, {}); + * // => { '1': ['a', 'c'], '2': ['b'] } + */ + function transform(object, iteratee, accumulator) { + var isArr = isArray(object) || isTypedArray(object); + iteratee = getIteratee(iteratee, 4); + + if (accumulator == null) { + if (isArr || isObject(object)) { + var Ctor = object.constructor; + if (isArr) { + accumulator = isArray(object) ? new Ctor : []; + } else { + accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {}; + } + } else { + accumulator = {}; + } + } + (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) { + return iteratee(accumulator, value, index, object); + }); + return accumulator; + } + + /** + * Removes the property at `path` of `object`. + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to unset. + * @returns {boolean} Returns `true` if the property is deleted, else `false`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 7 } }] }; + * _.unset(object, 'a[0].b.c'); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + * + * _.unset(object, ['a', '0', 'b', 'c']); + * // => true + * + * console.log(object); + * // => { 'a': [{ 'b': {} }] }; + */ + function unset(object, path) { + return object == null ? true : baseUnset(object, path); + } + + /** + * This method is like `_.set` except that accepts `updater` to produce the + * value to set. Use `_.updateWith` to customize `path` creation. The `updater` + * is invoked with one argument: (value). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @returns {Object} Returns `object`. + * @example + * + * var object = { 'a': [{ 'b': { 'c': 3 } }] }; + * + * _.update(object, 'a[0].b.c', function(n) { return n * n; }); + * console.log(object.a[0].b.c); + * // => 9 + * + * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; }); + * console.log(object.x[0].y.z); + * // => 0 + */ + function update(object, path, updater) { + return object == null ? object : baseUpdate(object, path, castFunction(updater)); + } + + /** + * This method is like `_.update` except that it accepts `customizer` which is + * invoked to produce the objects of `path`. If `customizer` returns `undefined` + * path creation is handled by the method instead. The `customizer` is invoked + * with three arguments: (nsValue, key, nsObject). + * + * **Note:** This method mutates `object`. + * + * @static + * @memberOf _ + * @since 4.6.0 + * @category Object + * @param {Object} object The object to modify. + * @param {Array|string} path The path of the property to set. + * @param {Function} updater The function to produce the updated value. + * @param {Function} [customizer] The function to customize assigned values. + * @returns {Object} Returns `object`. + * @example + * + * var object = {}; + * + * _.updateWith(object, '[0][1]', _.constant('a'), Object); + * // => { '0': { '1': 'a' } } + */ + function updateWith(object, path, updater, customizer) { + customizer = typeof customizer == 'function' ? customizer : undefined; + return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer); + } + + /** + * Creates an array of the own enumerable string keyed property values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.values(new Foo); + * // => [1, 2] (iteration order is not guaranteed) + * + * _.values('hi'); + * // => ['h', 'i'] + */ + function values(object) { + return object ? baseValues(object, keys(object)) : []; + } + + /** + * Creates an array of the own and inherited enumerable string keyed property + * values of `object`. + * + * **Note:** Non-object values are coerced to objects. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category Object + * @param {Object} object The object to query. + * @returns {Array} Returns the array of property values. + * @example + * + * function Foo() { + * this.a = 1; + * this.b = 2; + * } + * + * Foo.prototype.c = 3; + * + * _.valuesIn(new Foo); + * // => [1, 2, 3] (iteration order is not guaranteed) + */ + function valuesIn(object) { + return object == null ? [] : baseValues(object, keysIn(object)); + } + + /*------------------------------------------------------------------------*/ + + /** + * Clamps `number` within the inclusive `lower` and `upper` bounds. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category Number + * @param {number} number The number to clamp. + * @param {number} [lower] The lower bound. + * @param {number} upper The upper bound. + * @returns {number} Returns the clamped number. + * @example + * + * _.clamp(-10, -5, 5); + * // => -5 + * + * _.clamp(10, -5, 5); + * // => 5 + */ + function clamp(number, lower, upper) { + if (upper === undefined) { + upper = lower; + lower = undefined; + } + if (upper !== undefined) { + upper = toNumber(upper); + upper = upper === upper ? upper : 0; + } + if (lower !== undefined) { + lower = toNumber(lower); + lower = lower === lower ? lower : 0; + } + return baseClamp(toNumber(number), lower, upper); + } + + /** + * Checks if `n` is between `start` and up to, but not including, `end`. If + * `end` is not specified, it's set to `start` with `start` then set to `0`. + * If `start` is greater than `end` the params are swapped to support + * negative ranges. + * + * @static + * @memberOf _ + * @since 3.3.0 + * @category Number + * @param {number} number The number to check. + * @param {number} [start=0] The start of the range. + * @param {number} end The end of the range. + * @returns {boolean} Returns `true` if `number` is in the range, else `false`. + * @see _.range, _.rangeRight + * @example + * + * _.inRange(3, 2, 4); + * // => true + * + * _.inRange(4, 8); + * // => true + * + * _.inRange(4, 2); + * // => false + * + * _.inRange(2, 2); + * // => false + * + * _.inRange(1.2, 2); + * // => true + * + * _.inRange(5.2, 4); + * // => false + * + * _.inRange(-3, -2, -6); + * // => true + */ + function inRange(number, start, end) { + start = toFinite(start); + if (end === undefined) { + end = start; + start = 0; + } else { + end = toFinite(end); + } + number = toNumber(number); + return baseInRange(number, start, end); + } + + /** + * Produces a random number between the inclusive `lower` and `upper` bounds. + * If only one argument is provided a number between `0` and the given number + * is returned. If `floating` is `true`, or either `lower` or `upper` are + * floats, a floating-point number is returned instead of an integer. + * + * **Note:** JavaScript follows the IEEE-754 standard for resolving + * floating-point values which can produce unexpected results. + * + * @static + * @memberOf _ + * @since 0.7.0 + * @category Number + * @param {number} [lower=0] The lower bound. + * @param {number} [upper=1] The upper bound. + * @param {boolean} [floating] Specify returning a floating-point number. + * @returns {number} Returns the random number. + * @example + * + * _.random(0, 5); + * // => an integer between 0 and 5 + * + * _.random(5); + * // => also an integer between 0 and 5 + * + * _.random(5, true); + * // => a floating-point number between 0 and 5 + * + * _.random(1.2, 5.2); + * // => a floating-point number between 1.2 and 5.2 + */ + function random(lower, upper, floating) { + if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) { + upper = floating = undefined; + } + if (floating === undefined) { + if (typeof upper == 'boolean') { + floating = upper; + upper = undefined; + } + else if (typeof lower == 'boolean') { + floating = lower; + lower = undefined; + } + } + if (lower === undefined && upper === undefined) { + lower = 0; + upper = 1; + } + else { + lower = toFinite(lower); + if (upper === undefined) { + upper = lower; + lower = 0; + } else { + upper = toFinite(upper); + } + } + if (lower > upper) { + var temp = lower; + lower = upper; + upper = temp; + } + if (floating || lower % 1 || upper % 1) { + var rand = nativeRandom(); + return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper); + } + return baseRandom(lower, upper); + } + + /*------------------------------------------------------------------------*/ + + /** + * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the camel cased string. + * @example + * + * _.camelCase('Foo Bar'); + * // => 'fooBar' + * + * _.camelCase('--foo-bar--'); + * // => 'fooBar' + * + * _.camelCase('__FOO_BAR__'); + * // => 'fooBar' + */ + var camelCase = createCompounder(function(result, word, index) { + word = word.toLowerCase(); + return result + (index ? capitalize(word) : word); + }); + + /** + * Converts the first character of `string` to upper case and the remaining + * to lower case. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to capitalize. + * @returns {string} Returns the capitalized string. + * @example + * + * _.capitalize('FRED'); + * // => 'Fred' + */ + function capitalize(string) { + return upperFirst(toString(string).toLowerCase()); + } + + /** + * Deburrs `string` by converting + * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table) + * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A) + * letters to basic Latin letters and removing + * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to deburr. + * @returns {string} Returns the deburred string. + * @example + * + * _.deburr('déjà vu'); + * // => 'deja vu' + */ + function deburr(string) { + string = toString(string); + return string && string.replace(reLatin, deburrLetter).replace(reComboMark, ''); + } + + /** + * Checks if `string` ends with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=string.length] The position to search up to. + * @returns {boolean} Returns `true` if `string` ends with `target`, + * else `false`. + * @example + * + * _.endsWith('abc', 'c'); + * // => true + * + * _.endsWith('abc', 'b'); + * // => false + * + * _.endsWith('abc', 'b', 2); + * // => true + */ + function endsWith(string, target, position) { + string = toString(string); + target = baseToString(target); + + var length = string.length; + position = position === undefined + ? length + : baseClamp(toInteger(position), 0, length); + + var end = position; + position -= target.length; + return position >= 0 && string.slice(position, end) == target; + } + + /** + * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to + * their corresponding HTML entities. + * + * **Note:** No other characters are escaped. To escape additional + * characters use a third-party library like [_he_](https://mths.be/he). + * + * Though the ">" character is escaped for symmetry, characters like + * ">" and "/" don't need escaping in HTML and have no special meaning + * unless they're part of a tag or unquoted attribute value. See + * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands) + * (under "semi-related fun fact") for more details. + * + * Backticks are escaped because in IE < 9, they can break out of + * attribute values or HTML comments. See [#59](https://html5sec.org/#59), + * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and + * [#133](https://html5sec.org/#133) of the + * [HTML5 Security Cheatsheet](https://html5sec.org/) for more details. + * + * When working with HTML you should always + * [quote attribute values](http://wonko.com/post/html-escaping) to reduce + * XSS vectors. + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escape('fred, barney, & pebbles'); + * // => 'fred, barney, & pebbles' + */ + function escape(string) { + string = toString(string); + return (string && reHasUnescapedHtml.test(string)) + ? string.replace(reUnescapedHtml, escapeHtmlChar) + : string; + } + + /** + * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", + * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to escape. + * @returns {string} Returns the escaped string. + * @example + * + * _.escapeRegExp('[lodash](https://lodash.com/)'); + * // => '\[lodash\]\(https://lodash\.com/\)' + */ + function escapeRegExp(string) { + string = toString(string); + return (string && reHasRegExpChar.test(string)) + ? string.replace(reRegExpChar, '\\$&') + : string; + } + + /** + * Converts `string` to + * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the kebab cased string. + * @example + * + * _.kebabCase('Foo Bar'); + * // => 'foo-bar' + * + * _.kebabCase('fooBar'); + * // => 'foo-bar' + * + * _.kebabCase('__FOO_BAR__'); + * // => 'foo-bar' + */ + var kebabCase = createCompounder(function(result, word, index) { + return result + (index ? '-' : '') + word.toLowerCase(); + }); + + /** + * Converts `string`, as space separated words, to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the lower cased string. + * @example + * + * _.lowerCase('--Foo-Bar--'); + * // => 'foo bar' + * + * _.lowerCase('fooBar'); + * // => 'foo bar' + * + * _.lowerCase('__FOO_BAR__'); + * // => 'foo bar' + */ + var lowerCase = createCompounder(function(result, word, index) { + return result + (index ? ' ' : '') + word.toLowerCase(); + }); + + /** + * Converts the first character of `string` to lower case. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the converted string. + * @example + * + * _.lowerFirst('Fred'); + * // => 'fred' + * + * _.lowerFirst('FRED'); + * // => 'fRED' + */ + var lowerFirst = createCaseFirst('toLowerCase'); + + /** + * Pads `string` on the left and right sides if it's shorter than `length`. + * Padding characters are truncated if they can't be evenly divided by `length`. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.pad('abc', 8); + * // => ' abc ' + * + * _.pad('abc', 8, '_-'); + * // => '_-abc_-_' + * + * _.pad('abc', 3); + * // => 'abc' + */ + function pad(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + if (!length || strLength >= length) { + return string; + } + var mid = (length - strLength) / 2; + return ( + createPadding(nativeFloor(mid), chars) + + string + + createPadding(nativeCeil(mid), chars) + ); + } + + /** + * Pads `string` on the right side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padEnd('abc', 6); + * // => 'abc ' + * + * _.padEnd('abc', 6, '_-'); + * // => 'abc_-_' + * + * _.padEnd('abc', 3); + * // => 'abc' + */ + function padEnd(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + return (length && strLength < length) + ? (string + createPadding(length - strLength, chars)) + : string; + } + + /** + * Pads `string` on the left side if it's shorter than `length`. Padding + * characters are truncated if they exceed `length`. + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to pad. + * @param {number} [length=0] The padding length. + * @param {string} [chars=' '] The string used as padding. + * @returns {string} Returns the padded string. + * @example + * + * _.padStart('abc', 6); + * // => ' abc' + * + * _.padStart('abc', 6, '_-'); + * // => '_-_abc' + * + * _.padStart('abc', 3); + * // => 'abc' + */ + function padStart(string, length, chars) { + string = toString(string); + length = toInteger(length); + + var strLength = length ? stringSize(string) : 0; + return (length && strLength < length) + ? (createPadding(length - strLength, chars) + string) + : string; + } + + /** + * Converts `string` to an integer of the specified radix. If `radix` is + * `undefined` or `0`, a `radix` of `10` is used unless `value` is a + * hexadecimal, in which case a `radix` of `16` is used. + * + * **Note:** This method aligns with the + * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`. + * + * @static + * @memberOf _ + * @since 1.1.0 + * @category String + * @param {string} string The string to convert. + * @param {number} [radix=10] The radix to interpret `value` by. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {number} Returns the converted integer. + * @example + * + * _.parseInt('08'); + * // => 8 + * + * _.map(['6', '08', '10'], _.parseInt); + * // => [6, 8, 10] + */ + function parseInt(string, radix, guard) { + // Chrome fails to trim leading whitespace characters. + // See https://bugs.chromium.org/p/v8/issues/detail?id=3109 for more details. + if (guard || radix == null) { + radix = 0; + } else if (radix) { + radix = +radix; + } + string = toString(string).replace(reTrim, ''); + return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10)); + } + + /** + * Repeats the given string `n` times. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to repeat. + * @param {number} [n=1] The number of times to repeat the string. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {string} Returns the repeated string. + * @example + * + * _.repeat('*', 3); + * // => '***' + * + * _.repeat('abc', 2); + * // => 'abcabc' + * + * _.repeat('abc', 0); + * // => '' + */ + function repeat(string, n, guard) { + if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) { + n = 1; + } else { + n = toInteger(n); + } + return baseRepeat(toString(string), n); + } + + /** + * Replaces matches for `pattern` in `string` with `replacement`. + * + * **Note:** This method is based on + * [`String#replace`](https://mdn.io/String/replace). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to modify. + * @param {RegExp|string} pattern The pattern to replace. + * @param {Function|string} replacement The match replacement. + * @returns {string} Returns the modified string. + * @example + * + * _.replace('Hi Fred', 'Fred', 'Barney'); + * // => 'Hi Barney' + */ + function replace() { + var args = arguments, + string = toString(args[0]); + + return args.length < 3 ? string : string.replace(args[1], args[2]); + } + + /** + * Converts `string` to + * [snake case](https://en.wikipedia.org/wiki/Snake_case). + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the snake cased string. + * @example + * + * _.snakeCase('Foo Bar'); + * // => 'foo_bar' + * + * _.snakeCase('fooBar'); + * // => 'foo_bar' + * + * _.snakeCase('--FOO-BAR--'); + * // => 'foo_bar' + */ + var snakeCase = createCompounder(function(result, word, index) { + return result + (index ? '_' : '') + word.toLowerCase(); + }); + + /** + * Splits `string` by `separator`. + * + * **Note:** This method is based on + * [`String#split`](https://mdn.io/String/split). + * + * @static + * @memberOf _ + * @since 4.0.0 + * @category String + * @param {string} [string=''] The string to split. + * @param {RegExp|string} separator The separator pattern to split by. + * @param {number} [limit] The length to truncate results to. + * @returns {Array} Returns the string segments. + * @example + * + * _.split('a-b-c', '-', 2); + * // => ['a', 'b'] + */ + function split(string, separator, limit) { + if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) { + separator = limit = undefined; + } + limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0; + if (!limit) { + return []; + } + string = toString(string); + if (string && ( + typeof separator == 'string' || + (separator != null && !isRegExp(separator)) + )) { + separator = baseToString(separator); + if (!separator && hasUnicode(string)) { + return castSlice(stringToArray(string), 0, limit); + } + } + return string.split(separator, limit); + } + + /** + * Converts `string` to + * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage). + * + * @static + * @memberOf _ + * @since 3.1.0 + * @category String + * @param {string} [string=''] The string to convert. + * @returns {string} Returns the start cased string. + * @example + * + * _.startCase('--foo-bar--'); + * // => 'Foo Bar' + * + * _.startCase('fooBar'); + * // => 'Foo Bar' + * + * _.startCase('__FOO_BAR__'); + * // => 'FOO BAR' + */ + var startCase = createCompounder(function(result, word, index) { + return result + (index ? ' ' : '') + upperFirst(word); + }); + + /** + * Checks if `string` starts with the given target string. + * + * @static + * @memberOf _ + * @since 3.0.0 + * @category String + * @param {string} [string=''] The string to inspect. + * @param {string} [target] The string to search for. + * @param {number} [position=0] The position to search from. + * @returns {boolean} Returns `true` if `string` starts with `target`, + * else `false`. + * @example + * + * _.startsWith('abc', 'a'); + * // => true + * + * _.startsWith('abc', 'b'); + * // => false + * + * _.startsWith('abc', 'b', 1); + * // => true + */ + function startsWith(string, target, position) { + string = toString(string); + position = baseClamp(toInteger(position), 0, string.length); + target = baseToString(target); + return string.slice(position, position + target.length) == target; + } + + /** + * Creates a compiled template function that can interpolate data properties + * in "interpolate" delimiters, HTML-escape interpolated data properties in + * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data + * properties may be accessed as free variables in the template. If a setting + * object is given, it takes precedence over `_.templateSettings` values. + * + * **Note:** In the development build `_.template` utilizes + * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) + * for easier debugging. + * + * For more information on precompiling templates see + * [lodash's custom builds documentation](https://lodash.com/custom-builds). + * + * For more information on Chrome extension sandboxes see + * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). + * + * @static + * @since 0.1.0 + * @memberOf _ + * @category String + * @param {string} [string=''] The template string. + * @param {Object} [options={}] The options object. + * @param {RegExp} [options.escape=_.templateSettings.escape] + * The HTML "escape" delimiter. + * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] + * The "evaluate" delimiter. + * @param {Object} [options.imports=_.templateSettings.imports] + * An object to import into the template as free variables. + * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] + * The "interpolate" delimiter. + * @param {string} [options.sourceURL='lodash.templateSources[n]'] + * The sourceURL of the compiled template. + * @param {string} [options.variable='obj'] + * The data object variable name. + * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. + * @returns {Function} Returns the compiled template function. + * @example + * + * // Use the "interpolate" delimiter to create a compiled template. + * var compiled = _.template('hello <%= user %>!'); + * compiled({ 'user': 'fred' }); + * // => 'hello fred!' + * + * // Use the HTML "escape" delimiter to escape data property values. + * var compiled = _.template('<%- value %>'); + * compiled({ 'value': ' + + + + + + + + + + + diff --git a/public/vendor/lodash/perf/perf.js b/public/vendor/lodash/perf/perf.js new file mode 100644 index 00000000000..3588c7dc3aa --- /dev/null +++ b/public/vendor/lodash/perf/perf.js @@ -0,0 +1,1978 @@ +;(function() { + 'use strict'; + + /** Used to access the Firebug Lite panel (set by `run`). */ + var fbPanel; + + /** Used as a safe reference for `undefined` in pre ES5 environments. */ + var undefined; + + /** Used as a reference to the global object. */ + var root = typeof global == 'object' && global || this; + + /** Method and object shortcuts. */ + var phantom = root.phantom, + amd = root.define && define.amd, + argv = root.process && process.argv, + document = !phantom && root.document, + noop = function() {}, + params = root.arguments, + system = root.system; + + /** Add `console.log()` support for Rhino and RingoJS. */ + var console = root.console || (root.console = { 'log': root.print }); + + /** The file path of the lodash file to test. */ + var filePath = (function() { + var min = 0, + result = []; + + if (phantom) { + result = params = phantom.args; + } else if (system) { + min = 1; + result = params = system.args; + } else if (argv) { + min = 2; + result = params = argv; + } else if (params) { + result = params; + } + var last = result[result.length - 1]; + result = (result.length > min && !/perf(?:\.js)?$/.test(last)) ? last : '../lodash.js'; + + if (!amd) { + try { + result = require('fs').realpathSync(result); + } catch (e) {} + + try { + result = require.resolve(result); + } catch (e) {} + } + return result; + }()); + + /** Used to match path separators. */ + var rePathSeparator = /[\/\\]/; + + /** Used to detect primitive types. */ + var rePrimitive = /^(?:boolean|number|string|undefined)$/; + + /** Used to match RegExp special characters. */ + var reSpecialChars = /[.*+?^=!:${}()|[\]\/\\]/g; + + /** The `ui` object. */ + var ui = root.ui || (root.ui = { + 'buildPath': basename(filePath, '.js'), + 'otherPath': 'underscore' + }); + + /** The lodash build basename. */ + var buildName = root.buildName = basename(ui.buildPath, '.js'); + + /** The other library basename. */ + var otherName = root.otherName = (function() { + var result = basename(ui.otherPath, '.js'); + return result + (result == buildName ? ' (2)' : ''); + }()); + + /** Used to score performance. */ + var score = { 'a': [], 'b': [] }; + + /** Used to queue benchmark suites. */ + var suites = []; + + /** Use a single "load" function. */ + var load = (typeof require == 'function' && !amd) + ? require + : noop; + + /** Load lodash. */ + var lodash = root.lodash || (root.lodash = ( + lodash = load(filePath) || root._, + lodash = lodash._ || lodash, + (lodash.runInContext ? lodash.runInContext(root) : lodash), + lodash.noConflict() + )); + + /** Load Underscore. */ + var _ = root.underscore || (root.underscore = ( + _ = load('../vendor/underscore/underscore.js') || root._, + _._ || _ + )); + + /** Load Benchmark.js. */ + var Benchmark = root.Benchmark || (root.Benchmark = ( + Benchmark = load('../node_modules/benchmark/benchmark.js') || root.Benchmark, + Benchmark = Benchmark.Benchmark || Benchmark, + Benchmark.runInContext(lodash.extend({}, root, { '_': lodash })) + )); + + /*--------------------------------------------------------------------------*/ + + /** + * Gets the basename of the given `filePath`. If the file `extension` is passed, + * it will be removed from the basename. + * + * @private + * @param {string} path The file path to inspect. + * @param {string} extension The extension to remove. + * @returns {string} Returns the basename. + */ + function basename(filePath, extension) { + var result = (filePath || '').split(rePathSeparator).pop(); + return (arguments.length < 2) + ? result + : result.replace(RegExp(extension.replace(reSpecialChars, '\\$&') + '$'), ''); + } + + /** + * Computes the geometric mean (log-average) of an array of values. + * See http://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_arithmetic_mean_of_logarithms. + * + * @private + * @param {Array} array The array of values. + * @returns {number} The geometric mean. + */ + function getGeometricMean(array) { + return Math.pow(Math.E, lodash.reduce(array, function(sum, x) { + return sum + Math.log(x); + }, 0) / array.length) || 0; + } + + /** + * Gets the Hz, i.e. operations per second, of `bench` adjusted for the + * margin of error. + * + * @private + * @param {Object} bench The benchmark object. + * @returns {number} Returns the adjusted Hz. + */ + function getHz(bench) { + var result = 1 / (bench.stats.mean + bench.stats.moe); + return isFinite(result) ? result : 0; + } + + /** + * Host objects can return type values that are different from their actual + * data type. The objects we are concerned with usually return non-primitive + * types of "object", "function", or "unknown". + * + * @private + * @param {*} object The owner of the property. + * @param {string} property The property to check. + * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`. + */ + function isHostType(object, property) { + if (object == null) { + return false; + } + var type = typeof object[property]; + return !rePrimitive.test(type) && (type != 'object' || !!object[property]); + } + + /** + * Logs text to the console. + * + * @private + * @param {string} text The text to log. + */ + function log(text) { + console.log(text + ''); + if (fbPanel) { + // Scroll the Firebug Lite panel down. + fbPanel.scrollTop = fbPanel.scrollHeight; + } + } + + /** + * Runs all benchmark suites. + * + * @private (@public in the browser) + */ + function run() { + fbPanel = (fbPanel = root.document && document.getElementById('FirebugUI')) && + (fbPanel = (fbPanel = fbPanel.contentWindow || fbPanel.contentDocument).document || fbPanel) && + fbPanel.getElementById('fbPanel1'); + + log('\nSit back and relax, this may take a while.'); + suites[0].run({ 'async': true }); + } + + /*--------------------------------------------------------------------------*/ + + lodash.extend(Benchmark.Suite.options, { + 'onStart': function() { + log('\n' + this.name + ':'); + }, + 'onCycle': function(event) { + log(event.target); + }, + 'onComplete': function() { + for (var index = 0, length = this.length; index < length; index++) { + var bench = this[index]; + if (bench.error) { + var errored = true; + } + } + if (errored) { + log('There was a problem, skipping...'); + } + else { + var formatNumber = Benchmark.formatNumber, + fastest = this.filter('fastest'), + fastestHz = getHz(fastest[0]), + slowest = this.filter('slowest'), + slowestHz = getHz(slowest[0]), + aHz = getHz(this[0]), + bHz = getHz(this[1]); + + if (fastest.length > 1) { + log('It\'s too close to call.'); + aHz = bHz = slowestHz; + } + else { + var percent = ((fastestHz / slowestHz) - 1) * 100; + + log( + fastest[0].name + ' is ' + + formatNumber(percent < 1 ? percent.toFixed(2) : Math.round(percent)) + + '% faster.' + ); + } + // Add score adjusted for margin of error. + score.a.push(aHz); + score.b.push(bHz); + } + // Remove current suite from queue. + suites.shift(); + + if (suites.length) { + // Run next suite. + suites[0].run({ 'async': true }); + } + else { + var aMeanHz = getGeometricMean(score.a), + bMeanHz = getGeometricMean(score.b), + fastestMeanHz = Math.max(aMeanHz, bMeanHz), + slowestMeanHz = Math.min(aMeanHz, bMeanHz), + xFaster = fastestMeanHz / slowestMeanHz, + percentFaster = formatNumber(Math.round((xFaster - 1) * 100)), + message = 'is ' + percentFaster + '% ' + (xFaster == 1 ? '' : '(' + formatNumber(xFaster.toFixed(2)) + 'x) ') + 'faster than'; + + // Report results. + if (aMeanHz >= bMeanHz) { + log('\n' + buildName + ' ' + message + ' ' + otherName + '.'); + } else { + log('\n' + otherName + ' ' + message + ' ' + buildName + '.'); + } + } + } + }); + + /*--------------------------------------------------------------------------*/ + + lodash.extend(Benchmark.options, { + 'async': true, + 'setup': '\ + var _ = global.underscore,\ + lodash = global.lodash,\ + belt = this.name == buildName ? lodash : _;\ + \ + var date = new Date,\ + limit = 50,\ + regexp = /x/,\ + object = {},\ + objects = Array(limit),\ + numbers = Array(limit),\ + fourNumbers = [5, 25, 10, 30],\ + nestedNumbers = [1, [2], [3, [[4]]]],\ + nestedObjects = [{}, [{}], [{}, [[{}]]]],\ + twoNumbers = [12, 23];\ + \ + for (var index = 0; index < limit; index++) {\ + numbers[index] = index;\ + object["key" + index] = index;\ + objects[index] = { "num": index };\ + }\ + var strNumbers = numbers + "";\ + \ + if (typeof assign != "undefined") {\ + var _assign = _.assign || _.extend,\ + lodashAssign = lodash.assign;\ + }\ + if (typeof bind != "undefined") {\ + var thisArg = { "name": "fred" };\ + \ + var func = function(greeting, punctuation) {\ + return (greeting || "hi") + " " + this.name + (punctuation || ".");\ + };\ + \ + var _boundNormal = _.bind(func, thisArg),\ + _boundMultiple = _boundNormal,\ + _boundPartial = _.bind(func, thisArg, "hi");\ + \ + var lodashBoundNormal = lodash.bind(func, thisArg),\ + lodashBoundMultiple = lodashBoundNormal,\ + lodashBoundPartial = lodash.bind(func, thisArg, "hi");\ + \ + for (index = 0; index < 10; index++) {\ + _boundMultiple = _.bind(_boundMultiple, { "name": "fred" + index });\ + lodashBoundMultiple = lodash.bind(lodashBoundMultiple, { "name": "fred" + index });\ + }\ + }\ + if (typeof bindAll != "undefined") {\ + var bindAllCount = -1,\ + bindAllObjects = Array(this.count);\ + \ + var funcNames = belt.reject(belt.functions(belt).slice(0, 40), function(funcName) {\ + return /^_/.test(funcName);\ + });\ + \ + // Potentially expensive.\n\ + for (index = 0; index < this.count; index++) {\ + bindAllObjects[index] = belt.reduce(funcNames, function(object, funcName) {\ + object[funcName] = belt[funcName];\ + return object;\ + }, {});\ + }\ + }\ + if (typeof chaining != "undefined") {\ + var even = function(v) { return v % 2 == 0; },\ + square = function(v) { return v * v; };\ + \ + var largeArray = belt.range(10000),\ + _chaining = _(largeArray).chain(),\ + lodashChaining = lodash(largeArray).chain();\ + }\ + if (typeof compact != "undefined") {\ + var uncompacted = numbers.slice();\ + uncompacted[2] = false;\ + uncompacted[6] = null;\ + uncompacted[18] = "";\ + }\ + if (typeof flowRight != "undefined") {\ + var compAddOne = function(n) { return n + 1; },\ + compAddTwo = function(n) { return n + 2; },\ + compAddThree = function(n) { return n + 3; };\ + \ + var _composed = _.flowRight && _.flowRight(compAddThree, compAddTwo, compAddOne),\ + lodashComposed = lodash.flowRight && lodash.flowRight(compAddThree, compAddTwo, compAddOne);\ + }\ + if (typeof countBy != "undefined" || typeof omit != "undefined") {\ + var wordToNumber = {\ + "one": 1,\ + "two": 2,\ + "three": 3,\ + "four": 4,\ + "five": 5,\ + "six": 6,\ + "seven": 7,\ + "eight": 8,\ + "nine": 9,\ + "ten": 10,\ + "eleven": 11,\ + "twelve": 12,\ + "thirteen": 13,\ + "fourteen": 14,\ + "fifteen": 15,\ + "sixteen": 16,\ + "seventeen": 17,\ + "eighteen": 18,\ + "nineteen": 19,\ + "twenty": 20,\ + "twenty-one": 21,\ + "twenty-two": 22,\ + "twenty-three": 23,\ + "twenty-four": 24,\ + "twenty-five": 25,\ + "twenty-six": 26,\ + "twenty-seven": 27,\ + "twenty-eight": 28,\ + "twenty-nine": 29,\ + "thirty": 30,\ + "thirty-one": 31,\ + "thirty-two": 32,\ + "thirty-three": 33,\ + "thirty-four": 34,\ + "thirty-five": 35,\ + "thirty-six": 36,\ + "thirty-seven": 37,\ + "thirty-eight": 38,\ + "thirty-nine": 39,\ + "forty": 40\ + };\ + \ + var words = belt.keys(wordToNumber).slice(0, limit);\ + }\ + if (typeof flatten != "undefined") {\ + var _flattenDeep = _.flatten([[1]])[0] !== 1,\ + lodashFlattenDeep = lodash.flatten([[1]])[0] !== 1;\ + }\ + if (typeof isEqual != "undefined") {\ + var objectOfPrimitives = {\ + "boolean": true,\ + "number": 1,\ + "string": "a"\ + };\ + \ + var objectOfObjects = {\ + "boolean": new Boolean(true),\ + "number": new Number(1),\ + "string": new String("a")\ + };\ + \ + var objectOfObjects2 = {\ + "boolean": new Boolean(true),\ + "number": new Number(1),\ + "string": new String("A")\ + };\ + \ + var object2 = {},\ + object3 = {},\ + objects2 = Array(limit),\ + objects3 = Array(limit),\ + numbers2 = Array(limit),\ + numbers3 = Array(limit),\ + nestedNumbers2 = [1, [2], [3, [[4]]]],\ + nestedNumbers3 = [1, [2], [3, [[6]]]];\ + \ + for (index = 0; index < limit; index++) {\ + object2["key" + index] = index;\ + object3["key" + index] = index;\ + objects2[index] = { "num": index };\ + objects3[index] = { "num": index };\ + numbers2[index] = index;\ + numbers3[index] = index;\ + }\ + object3["key" + (limit - 1)] = -1;\ + objects3[limit - 1].num = -1;\ + numbers3[limit - 1] = -1;\ + }\ + if (typeof matches != "undefined") {\ + var source = { "num": 9 };\ + \ + var _matcher = (_.matches || _.noop)(source),\ + lodashMatcher = (lodash.matches || lodash.noop)(source);\ + }\ + if (typeof multiArrays != "undefined") {\ + var twentyValues = belt.shuffle(belt.range(20)),\ + fortyValues = belt.shuffle(belt.range(40)),\ + hundredSortedValues = belt.range(100),\ + hundredValues = belt.shuffle(hundredSortedValues),\ + hundredValues2 = belt.shuffle(hundredValues),\ + hundredTwentyValues = belt.shuffle(belt.range(120)),\ + hundredTwentyValues2 = belt.shuffle(hundredTwentyValues),\ + twoHundredValues = belt.shuffle(belt.range(200)),\ + twoHundredValues2 = belt.shuffle(twoHundredValues);\ + }\ + if (typeof partial != "undefined") {\ + var func = function(greeting, punctuation) {\ + return greeting + " fred" + (punctuation || ".");\ + };\ + \ + var _partial = _.partial(func, "hi"),\ + lodashPartial = lodash.partial(func, "hi");\ + }\ + if (typeof template != "undefined") {\ + var tplData = {\ + "header1": "Header1",\ + "header2": "Header2",\ + "header3": "Header3",\ + "header4": "Header4",\ + "header5": "Header5",\ + "header6": "Header6",\ + "list": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]\ + };\ + \ + var tpl =\ + "
" +\ + "

<%= header1 %>

" +\ + "

<%= header2 %>

" +\ + "

<%= header3 %>

" +\ + "

<%= header4 %>

" +\ + "
<%= header5 %>
" +\ + "
<%= header6 %>
" +\ + "
    " +\ + "<% for (var index = 0, length = list.length; index < length; index++) { %>" +\ + "
  • <%= list[index] %>
  • " +\ + "<% } %>" +\ + "
" +\ + "
";\ + \ + var tplVerbose =\ + "
" +\ + "

<%= data.header1 %>

" +\ + "

<%= data.header2 %>

" +\ + "

<%= data.header3 %>

" +\ + "

<%= data.header4 %>

" +\ + "
<%= data.header5 %>
" +\ + "
<%= data.header6 %>
" +\ + "
    " +\ + "<% for (var index = 0, length = data.list.length; index < length; index++) { %>" +\ + "
  • <%= data.list[index] %>
  • " +\ + "<% } %>" +\ + "
" +\ + "
";\ + \ + var settingsObject = { "variable": "data" };\ + \ + var _tpl = _.template(tpl),\ + _tplVerbose = _.template(tplVerbose, null, settingsObject);\ + \ + var lodashTpl = lodash.template(tpl),\ + lodashTplVerbose = lodash.template(tplVerbose, null, settingsObject);\ + }\ + if (typeof wrap != "undefined") {\ + var add = function(a, b) {\ + return a + b;\ + };\ + \ + var average = function(func, a, b) {\ + return (func(a, b) / 2).toFixed(2);\ + };\ + \ + var _wrapped = _.wrap(add, average);\ + lodashWrapped = lodash.wrap(add, average);\ + }\ + if (typeof zip != "undefined") {\ + var unzipped = [["a", "b", "c"], [1, 2, 3], [true, false, true]];\ + }' + }); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_(...).map(...).filter(...).take(...).value()`') + .add(buildName, { + 'fn': 'lodashChaining.map(square).filter(even).take(100).value()', + 'teardown': 'function chaining(){}' + }) + .add(otherName, { + 'fn': '_chaining.map(square).filter(even).take(100).value()', + 'teardown': 'function chaining(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.assign`') + .add(buildName, { + 'fn': 'lodashAssign({}, { "a": 1, "b": 2, "c": 3 })', + 'teardown': 'function assign(){}' + }) + .add(otherName, { + 'fn': '_assign({}, { "a": 1, "b": 2, "c": 3 })', + 'teardown': 'function assign(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.assign` with multiple sources') + .add(buildName, { + 'fn': 'lodashAssign({}, { "a": 1, "b": 2 }, { "c": 3, "d": 4 })', + 'teardown': 'function assign(){}' + }) + .add(otherName, { + 'fn': '_assign({}, { "a": 1, "b": 2 }, { "c": 3, "d": 4 })', + 'teardown': 'function assign(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.bind` (slow path)') + .add(buildName, { + 'fn': 'lodash.bind(function() { return this.name; }, { "name": "fred" })', + 'teardown': 'function bind(){}' + }) + .add(otherName, { + 'fn': '_.bind(function() { return this.name; }, { "name": "fred" })', + 'teardown': 'function bind(){}' + }) + ); + + suites.push( + Benchmark.Suite('bound call with arguments') + .add(buildName, { + 'fn': 'lodashBoundNormal("hi", "!")', + 'teardown': 'function bind(){}' + }) + .add(otherName, { + 'fn': '_boundNormal("hi", "!")', + 'teardown': 'function bind(){}' + }) + ); + + suites.push( + Benchmark.Suite('bound and partially applied call with arguments') + .add(buildName, { + 'fn': 'lodashBoundPartial("!")', + 'teardown': 'function bind(){}' + }) + .add(otherName, { + 'fn': '_boundPartial("!")', + 'teardown': 'function bind(){}' + }) + ); + + suites.push( + Benchmark.Suite('bound multiple times') + .add(buildName, { + 'fn': 'lodashBoundMultiple()', + 'teardown': 'function bind(){}' + }) + .add(otherName, { + 'fn': '_boundMultiple()', + 'teardown': 'function bind(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.bindAll`') + .add(buildName, { + 'fn': 'lodash.bindAll(bindAllObjects[++bindAllCount], funcNames)', + 'teardown': 'function bindAll(){}' + }) + .add(otherName, { + 'fn': '_.bindAll(bindAllObjects[++bindAllCount], funcNames)', + 'teardown': 'function bindAll(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.clone` with an array') + .add(buildName, '\ + lodash.clone(numbers)' + ) + .add(otherName, '\ + _.clone(numbers)' + ) + ); + + suites.push( + Benchmark.Suite('`_.clone` with an object') + .add(buildName, '\ + lodash.clone(object)' + ) + .add(otherName, '\ + _.clone(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.compact`') + .add(buildName, { + 'fn': 'lodash.compact(uncompacted)', + 'teardown': 'function compact(){}' + }) + .add(otherName, { + 'fn': '_.compact(uncompacted)', + 'teardown': 'function compact(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.countBy` with `callback` iterating an array') + .add(buildName, '\ + lodash.countBy(numbers, function(num) { return num >> 1; })' + ) + .add(otherName, '\ + _.countBy(numbers, function(num) { return num >> 1; })' + ) + ); + + suites.push( + Benchmark.Suite('`_.countBy` with `property` name iterating an array') + .add(buildName, { + 'fn': 'lodash.countBy(words, "length")', + 'teardown': 'function countBy(){}' + }) + .add(otherName, { + 'fn': '_.countBy(words, "length")', + 'teardown': 'function countBy(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.countBy` with `callback` iterating an object') + .add(buildName, { + 'fn': 'lodash.countBy(wordToNumber, function(num) { return num >> 1; })', + 'teardown': 'function countBy(){}' + }) + .add(otherName, { + 'fn': '_.countBy(wordToNumber, function(num) { return num >> 1; })', + 'teardown': 'function countBy(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.defaults`') + .add(buildName, '\ + lodash.defaults({ "key2": 2, "key6": 6, "key18": 18 }, object)' + ) + .add(otherName, '\ + _.defaults({ "key2": 2, "key6": 6, "key18": 18 }, object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.difference`') + .add(buildName, '\ + lodash.difference(numbers, twoNumbers, fourNumbers)' + ) + .add(otherName, '\ + _.difference(numbers, twoNumbers, fourNumbers)' + ) + ); + + suites.push( + Benchmark.Suite('`_.difference` iterating 20 and 40 elements') + .add(buildName, { + 'fn': 'lodash.difference(twentyValues, fortyValues)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.difference(twentyValues, fortyValues)', + 'teardown': 'function multiArrays(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.difference` iterating 200 elements') + .add(buildName, { + 'fn': 'lodash.difference(twoHundredValues, twoHundredValues2)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.difference(twoHundredValues, twoHundredValues2)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.each` iterating an array') + .add(buildName, '\ + var result = [];\ + lodash.each(numbers, function(num) {\ + result.push(num * 2);\ + })' + ) + .add(otherName, '\ + var result = [];\ + _.each(numbers, function(num) {\ + result.push(num * 2);\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.each` iterating an object') + .add(buildName, '\ + var result = [];\ + lodash.each(object, function(num) {\ + result.push(num * 2);\ + })' + ) + .add(otherName, '\ + var result = [];\ + _.each(object, function(num) {\ + result.push(num * 2);\ + })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.every` iterating an array') + .add(buildName, '\ + lodash.every(numbers, function(num) {\ + return num < limit;\ + })' + ) + .add(otherName, '\ + _.every(numbers, function(num) {\ + return num < limit;\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.every` iterating an object') + .add(buildName, '\ + lodash.every(object, function(num) {\ + return num < limit;\ + })' + ) + .add(otherName, '\ + _.every(object, function(num) {\ + return num < limit;\ + })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.filter` iterating an array') + .add(buildName, '\ + lodash.filter(numbers, function(num) {\ + return num % 2;\ + })' + ) + .add(otherName, '\ + _.filter(numbers, function(num) {\ + return num % 2;\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.filter` iterating an object') + .add(buildName, '\ + lodash.filter(object, function(num) {\ + return num % 2\ + })' + ) + .add(otherName, '\ + _.filter(object, function(num) {\ + return num % 2\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.filter` with `_.matches` shorthand') + .add(buildName, { + 'fn': 'lodash.filter(objects, source)', + 'teardown': 'function matches(){}' + }) + .add(otherName, { + 'fn': '_.filter(objects, source)', + 'teardown': 'function matches(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.filter` with `_.matches` predicate') + .add(buildName, { + 'fn': 'lodash.filter(objects, lodashMatcher)', + 'teardown': 'function matches(){}' + }) + .add(otherName, { + 'fn': '_.filter(objects, _matcher)', + 'teardown': 'function matches(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.find` iterating an array') + .add(buildName, '\ + lodash.find(numbers, function(num) {\ + return num === (limit - 1);\ + })' + ) + .add(otherName, '\ + _.find(numbers, function(num) {\ + return num === (limit - 1);\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.find` iterating an object') + .add(buildName, '\ + lodash.find(object, function(value, key) {\ + return /\D9$/.test(key);\ + })' + ) + .add(otherName, '\ + _.find(object, function(value, key) {\ + return /\D9$/.test(key);\ + })' + ) + ); + + // Avoid Underscore induced `OutOfMemoryError` in Rhino and Ringo. + suites.push( + Benchmark.Suite('`_.find` with `_.matches` shorthand') + .add(buildName, { + 'fn': 'lodash.find(objects, source)', + 'teardown': 'function matches(){}' + }) + .add(otherName, { + 'fn': '_.find(objects, source)', + 'teardown': 'function matches(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.flatten`') + .add(buildName, { + 'fn': 'lodash.flatten(nestedNumbers, !lodashFlattenDeep)', + 'teardown': 'function flatten(){}' + }) + .add(otherName, { + 'fn': '_.flatten(nestedNumbers, !_flattenDeep)', + 'teardown': 'function flatten(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.flattenDeep` nested arrays of numbers') + .add(buildName, { + 'fn': 'lodash.flattenDeep(nestedNumbers)', + 'teardown': 'function flatten(){}' + }) + .add(otherName, { + 'fn': '_.flattenDeep(nestedNumbers)', + 'teardown': 'function flatten(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.flattenDeep` nest arrays of objects') + .add(buildName, { + 'fn': 'lodash.flattenDeep(nestedObjects)', + 'teardown': 'function flatten(){}' + }) + .add(otherName, { + 'fn': '_.flattenDeep(nestedObjects)', + 'teardown': 'function flatten(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.flowRight`') + .add(buildName, { + 'fn': 'lodash.flowRight(compAddThree, compAddTwo, compAddOne)', + 'teardown': 'function flowRight(){}' + }) + .add(otherName, { + 'fn': '_.flowRight(compAddThree, compAddTwo, compAddOne)', + 'teardown': 'function flowRight(){}' + }) + ); + + suites.push( + Benchmark.Suite('composed call') + .add(buildName, { + 'fn': 'lodashComposed(0)', + 'teardown': 'function flowRight(){}' + }) + .add(otherName, { + 'fn': '_composed(0)', + 'teardown': 'function flowRight(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.functions`') + .add(buildName, '\ + lodash.functions(lodash)' + ) + .add(otherName, '\ + _.functions(lodash)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.groupBy` with `callback` iterating an array') + .add(buildName, '\ + lodash.groupBy(numbers, function(num) { return num >> 1; })' + ) + .add(otherName, '\ + _.groupBy(numbers, function(num) { return num >> 1; })' + ) + ); + + suites.push( + Benchmark.Suite('`_.groupBy` with `property` name iterating an array') + .add(buildName, { + 'fn': 'lodash.groupBy(words, "length")', + 'teardown': 'function countBy(){}' + }) + .add(otherName, { + 'fn': '_.groupBy(words, "length")', + 'teardown': 'function countBy(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.groupBy` with `callback` iterating an object') + .add(buildName, { + 'fn': 'lodash.groupBy(wordToNumber, function(num) { return num >> 1; })', + 'teardown': 'function countBy(){}' + }) + .add(otherName, { + 'fn': '_.groupBy(wordToNumber, function(num) { return num >> 1; })', + 'teardown': 'function countBy(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.includes` searching an array') + .add(buildName, '\ + lodash.includes(numbers, limit - 1)' + ) + .add(otherName, '\ + _.includes(numbers, limit - 1)' + ) + ); + + suites.push( + Benchmark.Suite('`_.includes` searching an object') + .add(buildName, '\ + lodash.includes(object, limit - 1)' + ) + .add(otherName, '\ + _.includes(object, limit - 1)' + ) + ); + + if (lodash.includes('ab', 'ab') && _.includes('ab', 'ab')) { + suites.push( + Benchmark.Suite('`_.includes` searching a string') + .add(buildName, '\ + lodash.includes(strNumbers, "," + (limit - 1))' + ) + .add(otherName, '\ + _.includes(strNumbers, "," + (limit - 1))' + ) + ); + } + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.indexOf`') + .add(buildName, { + 'fn': 'lodash.indexOf(hundredSortedValues, 99)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.indexOf(hundredSortedValues, 99)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.intersection`') + .add(buildName, '\ + lodash.intersection(numbers, twoNumbers, fourNumbers)' + ) + .add(otherName, '\ + _.intersection(numbers, twoNumbers, fourNumbers)' + ) + ); + + suites.push( + Benchmark.Suite('`_.intersection` iterating 120 elements') + .add(buildName, { + 'fn': 'lodash.intersection(hundredTwentyValues, hundredTwentyValues2)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.intersection(hundredTwentyValues, hundredTwentyValues2)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.invert`') + .add(buildName, '\ + lodash.invert(object)' + ) + .add(otherName, '\ + _.invert(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.invokeMap` iterating an array') + .add(buildName, '\ + lodash.invokeMap(numbers, "toFixed")' + ) + .add(otherName, '\ + _.invokeMap(numbers, "toFixed")' + ) + ); + + suites.push( + Benchmark.Suite('`_.invokeMap` with arguments iterating an array') + .add(buildName, '\ + lodash.invokeMap(numbers, "toFixed", 1)' + ) + .add(otherName, '\ + _.invokeMap(numbers, "toFixed", 1)' + ) + ); + + suites.push( + Benchmark.Suite('`_.invokeMap` with a function for `path` iterating an array') + .add(buildName, '\ + lodash.invokeMap(numbers, Number.prototype.toFixed, 1)' + ) + .add(otherName, '\ + _.invokeMap(numbers, Number.prototype.toFixed, 1)' + ) + ); + + suites.push( + Benchmark.Suite('`_.invokeMap` iterating an object') + .add(buildName, '\ + lodash.invokeMap(object, "toFixed", 1)' + ) + .add(otherName, '\ + _.invokeMap(object, "toFixed", 1)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.isEqual` comparing primitives') + .add(buildName, { + 'fn': '\ + lodash.isEqual(1, "1");\ + lodash.isEqual(1, 1)', + 'teardown': 'function isEqual(){}' + }) + .add(otherName, { + 'fn': '\ + _.isEqual(1, "1");\ + _.isEqual(1, 1);', + 'teardown': 'function isEqual(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.isEqual` comparing primitives and their object counterparts (edge case)') + .add(buildName, { + 'fn': '\ + lodash.isEqual(objectOfPrimitives, objectOfObjects);\ + lodash.isEqual(objectOfPrimitives, objectOfObjects2)', + 'teardown': 'function isEqual(){}' + }) + .add(otherName, { + 'fn': '\ + _.isEqual(objectOfPrimitives, objectOfObjects);\ + _.isEqual(objectOfPrimitives, objectOfObjects2)', + 'teardown': 'function isEqual(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.isEqual` comparing arrays') + .add(buildName, { + 'fn': '\ + lodash.isEqual(numbers, numbers2);\ + lodash.isEqual(numbers2, numbers3)', + 'teardown': 'function isEqual(){}' + }) + .add(otherName, { + 'fn': '\ + _.isEqual(numbers, numbers2);\ + _.isEqual(numbers2, numbers3)', + 'teardown': 'function isEqual(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.isEqual` comparing nested arrays') + .add(buildName, { + 'fn': '\ + lodash.isEqual(nestedNumbers, nestedNumbers2);\ + lodash.isEqual(nestedNumbers2, nestedNumbers3)', + 'teardown': 'function isEqual(){}' + }) + .add(otherName, { + 'fn': '\ + _.isEqual(nestedNumbers, nestedNumbers2);\ + _.isEqual(nestedNumbers2, nestedNumbers3)', + 'teardown': 'function isEqual(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.isEqual` comparing arrays of objects') + .add(buildName, { + 'fn': '\ + lodash.isEqual(objects, objects2);\ + lodash.isEqual(objects2, objects3)', + 'teardown': 'function isEqual(){}' + }) + .add(otherName, { + 'fn': '\ + _.isEqual(objects, objects2);\ + _.isEqual(objects2, objects3)', + 'teardown': 'function isEqual(){}' + }) + ); + + suites.push( + Benchmark.Suite('`_.isEqual` comparing objects') + .add(buildName, { + 'fn': '\ + lodash.isEqual(object, object2);\ + lodash.isEqual(object2, object3)', + 'teardown': 'function isEqual(){}' + }) + .add(otherName, { + 'fn': '\ + _.isEqual(object, object2);\ + _.isEqual(object2, object3)', + 'teardown': 'function isEqual(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.isArguments`, `_.isDate`, `_.isFunction`, `_.isNumber`, `_.isObject`, `_.isRegExp`') + .add(buildName, '\ + lodash.isArguments(arguments);\ + lodash.isArguments(object);\ + lodash.isDate(date);\ + lodash.isDate(object);\ + lodash.isFunction(lodash);\ + lodash.isFunction(object);\ + lodash.isNumber(1);\ + lodash.isNumber(object);\ + lodash.isObject(object);\ + lodash.isObject(1);\ + lodash.isRegExp(regexp);\ + lodash.isRegExp(object)' + ) + .add(otherName, '\ + _.isArguments(arguments);\ + _.isArguments(object);\ + _.isDate(date);\ + _.isDate(object);\ + _.isFunction(_);\ + _.isFunction(object);\ + _.isNumber(1);\ + _.isNumber(object);\ + _.isObject(object);\ + _.isObject(1);\ + _.isRegExp(regexp);\ + _.isRegExp(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.keys` (uses native `Object.keys` if available)') + .add(buildName, '\ + lodash.keys(object)' + ) + .add(otherName, '\ + _.keys(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.lastIndexOf`') + .add(buildName, { + 'fn': 'lodash.lastIndexOf(hundredSortedValues, 0)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.lastIndexOf(hundredSortedValues, 0)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.map` iterating an array') + .add(buildName, '\ + lodash.map(objects, function(value) {\ + return value.num;\ + })' + ) + .add(otherName, '\ + _.map(objects, function(value) {\ + return value.num;\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.map` iterating an object') + .add(buildName, '\ + lodash.map(object, function(value) {\ + return value;\ + })' + ) + .add(otherName, '\ + _.map(object, function(value) {\ + return value;\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.map` with `_.property` shorthand') + .add(buildName, '\ + lodash.map(objects, "num")' + ) + .add(otherName, '\ + _.map(objects, "num")' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.max`') + .add(buildName, '\ + lodash.max(numbers)' + ) + .add(otherName, '\ + _.max(numbers)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.min`') + .add(buildName, '\ + lodash.min(numbers)' + ) + .add(otherName, '\ + _.min(numbers)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.omit` iterating 20 properties, omitting 2 keys') + .add(buildName, '\ + lodash.omit(object, "key6", "key13")' + ) + .add(otherName, '\ + _.omit(object, "key6", "key13")' + ) + ); + + suites.push( + Benchmark.Suite('`_.omit` iterating 40 properties, omitting 20 keys') + .add(buildName, { + 'fn': 'lodash.omit(wordToNumber, words)', + 'teardown': 'function omit(){}' + }) + .add(otherName, { + 'fn': '_.omit(wordToNumber, words)', + 'teardown': 'function omit(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.partial` (slow path)') + .add(buildName, { + 'fn': 'lodash.partial(function(greeting) { return greeting + " " + this.name; }, "hi")', + 'teardown': 'function partial(){}' + }) + .add(otherName, { + 'fn': '_.partial(function(greeting) { return greeting + " " + this.name; }, "hi")', + 'teardown': 'function partial(){}' + }) + ); + + suites.push( + Benchmark.Suite('partially applied call with arguments') + .add(buildName, { + 'fn': 'lodashPartial("!")', + 'teardown': 'function partial(){}' + }) + .add(otherName, { + 'fn': '_partial("!")', + 'teardown': 'function partial(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.partition` iterating an array') + .add(buildName, '\ + lodash.partition(numbers, function(num) {\ + return num % 2;\ + })' + ) + .add(otherName, '\ + _.partition(numbers, function(num) {\ + return num % 2;\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.partition` iterating an object') + .add(buildName, '\ + lodash.partition(object, function(num) {\ + return num % 2;\ + })' + ) + .add(otherName, '\ + _.partition(object, function(num) {\ + return num % 2;\ + })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.pick`') + .add(buildName, '\ + lodash.pick(object, "key6", "key13")' + ) + .add(otherName, '\ + _.pick(object, "key6", "key13")' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.reduce` iterating an array') + .add(buildName, '\ + lodash.reduce(numbers, function(result, value, index) {\ + result[index] = value;\ + return result;\ + }, {})' + ) + .add(otherName, '\ + _.reduce(numbers, function(result, value, index) {\ + result[index] = value;\ + return result;\ + }, {})' + ) + ); + + suites.push( + Benchmark.Suite('`_.reduce` iterating an object') + .add(buildName, '\ + lodash.reduce(object, function(result, value, key) {\ + result.push(key, value);\ + return result;\ + }, [])' + ) + .add(otherName, '\ + _.reduce(object, function(result, value, key) {\ + result.push(key, value);\ + return result;\ + }, [])' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.reduceRight` iterating an array') + .add(buildName, '\ + lodash.reduceRight(numbers, function(result, value, index) {\ + result[index] = value;\ + return result;\ + }, {})' + ) + .add(otherName, '\ + _.reduceRight(numbers, function(result, value, index) {\ + result[index] = value;\ + return result;\ + }, {})' + ) + ); + + suites.push( + Benchmark.Suite('`_.reduceRight` iterating an object') + .add(buildName, '\ + lodash.reduceRight(object, function(result, value, key) {\ + result.push(key, value);\ + return result;\ + }, [])' + ) + .add(otherName, '\ + _.reduceRight(object, function(result, value, key) {\ + result.push(key, value);\ + return result;\ + }, [])' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.reject` iterating an array') + .add(buildName, '\ + lodash.reject(numbers, function(num) {\ + return num % 2;\ + })' + ) + .add(otherName, '\ + _.reject(numbers, function(num) {\ + return num % 2;\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.reject` iterating an object') + .add(buildName, '\ + lodash.reject(object, function(num) {\ + return num % 2;\ + })' + ) + .add(otherName, '\ + _.reject(object, function(num) {\ + return num % 2;\ + })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sampleSize`') + .add(buildName, '\ + lodash.sampleSize(numbers, limit / 2)' + ) + .add(otherName, '\ + _.sampleSize(numbers, limit / 2)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.shuffle`') + .add(buildName, '\ + lodash.shuffle(numbers)' + ) + .add(otherName, '\ + _.shuffle(numbers)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.size` with an object') + .add(buildName, '\ + lodash.size(object)' + ) + .add(otherName, '\ + _.size(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.some` iterating an array') + .add(buildName, '\ + lodash.some(numbers, function(num) {\ + return num == (limit - 1);\ + })' + ) + .add(otherName, '\ + _.some(numbers, function(num) {\ + return num == (limit - 1);\ + })' + ) + ); + + suites.push( + Benchmark.Suite('`_.some` iterating an object') + .add(buildName, '\ + lodash.some(object, function(num) {\ + return num == (limit - 1);\ + })' + ) + .add(otherName, '\ + _.some(object, function(num) {\ + return num == (limit - 1);\ + })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sortBy` with `callback`') + .add(buildName, '\ + lodash.sortBy(numbers, function(num) { return Math.sin(num); })' + ) + .add(otherName, '\ + _.sortBy(numbers, function(num) { return Math.sin(num); })' + ) + ); + + suites.push( + Benchmark.Suite('`_.sortBy` with `property` name') + .add(buildName, { + 'fn': 'lodash.sortBy(words, "length")', + 'teardown': 'function countBy(){}' + }) + .add(otherName, { + 'fn': '_.sortBy(words, "length")', + 'teardown': 'function countBy(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sortedIndex`') + .add(buildName, '\ + lodash.sortedIndex(numbers, limit)' + ) + .add(otherName, '\ + _.sortedIndex(numbers, limit)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sortedIndexBy`') + .add(buildName, { + 'fn': '\ + lodash.sortedIndexBy(words, "twenty-five", function(value) {\ + return wordToNumber[value];\ + })', + 'teardown': 'function countBy(){}' + }) + .add(otherName, { + 'fn': '\ + _.sortedIndexBy(words, "twenty-five", function(value) {\ + return wordToNumber[value];\ + })', + 'teardown': 'function countBy(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sortedIndexOf`') + .add(buildName, { + 'fn': 'lodash.sortedIndexOf(hundredSortedValues, 99)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.sortedIndexOf(hundredSortedValues, 99)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sortedLastIndexOf`') + .add(buildName, { + 'fn': 'lodash.sortedLastIndexOf(hundredSortedValues, 0)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.sortedLastIndexOf(hundredSortedValues, 0)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.sum`') + .add(buildName, '\ + lodash.sum(numbers)' + ) + .add(otherName, '\ + _.sum(numbers)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.template` (slow path)') + .add(buildName, { + 'fn': 'lodash.template(tpl)(tplData)', + 'teardown': 'function template(){}' + }) + .add(otherName, { + 'fn': '_.template(tpl)(tplData)', + 'teardown': 'function template(){}' + }) + ); + + suites.push( + Benchmark.Suite('compiled template') + .add(buildName, { + 'fn': 'lodashTpl(tplData)', + 'teardown': 'function template(){}' + }) + .add(otherName, { + 'fn': '_tpl(tplData)', + 'teardown': 'function template(){}' + }) + ); + + suites.push( + Benchmark.Suite('compiled template without a with-statement') + .add(buildName, { + 'fn': 'lodashTplVerbose(tplData)', + 'teardown': 'function template(){}' + }) + .add(otherName, { + 'fn': '_tplVerbose(tplData)', + 'teardown': 'function template(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.times`') + .add(buildName, '\ + var result = [];\ + lodash.times(limit, function(n) { result.push(n); })' + ) + .add(otherName, '\ + var result = [];\ + _.times(limit, function(n) { result.push(n); })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.toArray` with an array (edge case)') + .add(buildName, '\ + lodash.toArray(numbers)' + ) + .add(otherName, '\ + _.toArray(numbers)' + ) + ); + + suites.push( + Benchmark.Suite('`_.toArray` with an object') + .add(buildName, '\ + lodash.toArray(object)' + ) + .add(otherName, '\ + _.toArray(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.toPairs`') + .add(buildName, '\ + lodash.toPairs(object)' + ) + .add(otherName, '\ + _.toPairs(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.unescape` string without html entities') + .add(buildName, '\ + lodash.unescape("`&`, `<`, `>`, `\\"`, and `\'`")' + ) + .add(otherName, '\ + _.unescape("`&`, `<`, `>`, `\\"`, and `\'`")' + ) + ); + + suites.push( + Benchmark.Suite('`_.unescape` string with html entities') + .add(buildName, '\ + lodash.unescape("`&`, `<`, `>`, `"`, and `'`")' + ) + .add(otherName, '\ + _.unescape("`&`, `<`, `>`, `"`, and `'`")' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.union`') + .add(buildName, '\ + lodash.union(numbers, twoNumbers, fourNumbers)' + ) + .add(otherName, '\ + _.union(numbers, twoNumbers, fourNumbers)' + ) + ); + + suites.push( + Benchmark.Suite('`_.union` iterating an array of 200 elements') + .add(buildName, { + 'fn': 'lodash.union(hundredValues, hundredValues2)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.union(hundredValues, hundredValues2)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.uniq`') + .add(buildName, '\ + lodash.uniq(numbers.concat(twoNumbers, fourNumbers))' + ) + .add(otherName, '\ + _.uniq(numbers.concat(twoNumbers, fourNumbers))' + ) + ); + + suites.push( + Benchmark.Suite('`_.uniq` iterating an array of 200 elements') + .add(buildName, { + 'fn': 'lodash.uniq(twoHundredValues)', + 'teardown': 'function multiArrays(){}' + }) + .add(otherName, { + 'fn': '_.uniq(twoHundredValues)', + 'teardown': 'function multiArrays(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.uniqBy`') + .add(buildName, '\ + lodash.uniqBy(numbers.concat(twoNumbers, fourNumbers), function(num) {\ + return num % 2;\ + })' + ) + .add(otherName, '\ + _.uniqBy(numbers.concat(twoNumbers, fourNumbers), function(num) {\ + return num % 2;\ + })' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.values`') + .add(buildName, '\ + lodash.values(object)' + ) + .add(otherName, '\ + _.values(object)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.without`') + .add(buildName, '\ + lodash.without(numbers, 9, 12, 14, 15)' + ) + .add(otherName, '\ + _.without(numbers, 9, 12, 14, 15)' + ) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.wrap` result called') + .add(buildName, { + 'fn': 'lodashWrapped(2, 5)', + 'teardown': 'function wrap(){}' + }) + .add(otherName, { + 'fn': '_wrapped(2, 5)', + 'teardown': 'function wrap(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + suites.push( + Benchmark.Suite('`_.zip`') + .add(buildName, { + 'fn': 'lodash.zip.apply(lodash, unzipped)', + 'teardown': 'function zip(){}' + }) + .add(otherName, { + 'fn': '_.zip.apply(_, unzipped)', + 'teardown': 'function zip(){}' + }) + ); + + /*--------------------------------------------------------------------------*/ + + if (Benchmark.platform + '') { + log(Benchmark.platform); + } + // Expose `run` to be called later when executing in a browser. + if (document) { + root.run = run; + } else { + run(); + } +}.call(this)); diff --git a/public/vendor/lodash/test/asset/test-ui.js b/public/vendor/lodash/test/asset/test-ui.js new file mode 100644 index 00000000000..968f0f8ec83 --- /dev/null +++ b/public/vendor/lodash/test/asset/test-ui.js @@ -0,0 +1,155 @@ +;(function(window) { + 'use strict'; + + /** The base path of the lodash builds. */ + var basePath = '../'; + + /** The lodash build to load. */ + var build = (build = /build=([^&]+)/.exec(location.search)) && decodeURIComponent(build[1]); + + /** The module loader to use. */ + var loader = (loader = /loader=([^&]+)/.exec(location.search)) && decodeURIComponent(loader[1]); + + /** The `ui` object. */ + var ui = {}; + + /*--------------------------------------------------------------------------*/ + + // Initialize controls. + addEventListener('load', function() { + function eventHandler(event) { + var buildIndex = buildList.selectedIndex, + loaderIndex = loaderList.selectedIndex, + search = location.search.replace(/^\?|&?(?:build|loader)=[^&]*&?/g, ''); + + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } + location.href = + location.href.split('?')[0] + '?' + + (search ? search + '&' : '') + + 'build=' + (buildIndex < 0 ? build : buildList[buildIndex].value) + '&' + + 'loader=' + (loaderIndex < 0 ? loader : loaderList[loaderIndex].value); + } + + function init() { + var toolbar = document.getElementById('qunit-testrunner-toolbar'); + if (!toolbar) { + setTimeout(init, 15); + return; + } + toolbar.insertBefore(span2, toolbar.lastChild); + toolbar.insertBefore(span1, span2); + + buildList.selectedIndex = (function() { + switch (build) { + case 'lodash': return 1; + case 'lodash-core-dev': return 2; + case 'lodash-core': return 3; + case 'lodash-dev': + case null: return 0; + } + return -1; + }()); + + loaderList.selectedIndex = (function() { + switch (loader) { + case 'curl': return 1; + case 'dojo': return 2; + case 'requirejs': return 3; + case 'none': + case null: return 0; + } + return -1; + }()); + + buildList.addEventListener('change', eventHandler); + loaderList.addEventListener('change', eventHandler); + } + + var span1 = document.createElement('span'); + span1.innerHTML = + '' + + ''; + + var span2 = document.createElement('span'); + span2.innerHTML = + '' + + ''; + + span1.style.cssText = + span2.style.cssText = 'display:inline-block;float:right;line-height:2.1em;margin-left:1em;margin-top:0;'; + + span1.firstChild.style.cssText = + span2.firstChild.style.cssText = 'display:inline-block;margin-right:.5em;'; + + var buildList = span1.lastChild, + loaderList = span2.lastChild; + + setTimeout(function() { + ui.timing.loadEventEnd = +new Date; + }, 1); + + init(); + }); + + // The lodash build file path. + ui.buildPath = (function() { + var result; + switch (build) { + case 'lodash': result = 'dist/lodash.min.js'; break; + case 'lodash-core-dev': result = 'dist/lodash.core.js'; break; + case 'lodash-core': result = 'dist/lodash.core.min.js'; break; + case null: build = 'lodash-dev'; + case 'lodash-dev': result = 'lodash.js'; break; + default: return build; + } + return basePath + result; + }()); + + // The module loader file path. + ui.loaderPath = (function() { + var result; + switch (loader) { + case 'curl': result = 'node_modules/curl-amd/dist/curl-kitchen-sink/curl.js'; break; + case 'dojo': result = 'node_modules/dojo/dojo.js'; break; + case 'requirejs': result = 'node_modules/requirejs/require.js'; break; + case null: loader = 'none'; return ''; + default: return loader; + } + return basePath + result; + }()); + + // Used to indicate testing a core build. + ui.isCore = /\bcore(\.min)?\.js\b/.test(ui.buildPath); + + // Used to indicate testing a foreign file. + ui.isForeign = RegExp('^(\\w+:)?//').test(build); + + // Used to indicate testing a modularized build. + ui.isModularize = /\b(?:amd|commonjs|es|node|npm|(index|main)\.js)\b/.test([location.pathname, location.search]); + + // Used to indicate testing in Sauce Labs' automated test cloud. + ui.isSauceLabs = location.port == '9001'; + + // Used to indicate that lodash is in strict mode. + ui.isStrict = /\bes\b/.test([location.pathname, location.search]); + + ui.urlParams = { 'build': build, 'loader': loader }; + ui.timing = { 'loadEventEnd': 0 }; + + window.ui = ui; + +}(this)); diff --git a/public/vendor/lodash/test/asset/worker.js b/public/vendor/lodash/test/asset/worker.js new file mode 100644 index 00000000000..95e3ab8a8e0 --- /dev/null +++ b/public/vendor/lodash/test/asset/worker.js @@ -0,0 +1,17 @@ +'use strict'; + +self.console || (self.console = { 'log': function() {} }); + +addEventListener('message', function(e) { + if (e.data) { + try { + importScripts('../' + e.data); + } catch (e) { + var lineNumber = e.lineNumber, + message = (lineNumber == null ? '' : (lineNumber + ': ')) + e.message; + + self._ = { 'VERSION': message }; + } + postMessage(_.VERSION); + } +}, false); diff --git a/public/vendor/lodash/test/backbone.html b/public/vendor/lodash/test/backbone.html new file mode 100644 index 00000000000..dd3df9fe05e --- /dev/null +++ b/public/vendor/lodash/test/backbone.html @@ -0,0 +1,170 @@ + + + + + Backbone Test Suite + + + + + + + + + + + + + + + diff --git a/public/vendor/lodash/test/fp.html b/public/vendor/lodash/test/fp.html new file mode 100644 index 00000000000..712296664f1 --- /dev/null +++ b/public/vendor/lodash/test/fp.html @@ -0,0 +1,41 @@ + + + + + lodash-fp Test Suite + + + + + + + + + + + +
+ + + diff --git a/public/vendor/lodash/test/index.html b/public/vendor/lodash/test/index.html new file mode 100644 index 00000000000..aee39424991 --- /dev/null +++ b/public/vendor/lodash/test/index.html @@ -0,0 +1,351 @@ + + + + + lodash Test Suite + + + + + + + + + + + +
+
+
+ + + + + diff --git a/public/vendor/lodash/test/remove.js b/public/vendor/lodash/test/remove.js new file mode 100644 index 00000000000..bd5aaf9271a --- /dev/null +++ b/public/vendor/lodash/test/remove.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node +'use strict'; + +var _ = require('../lodash'), + fs = require('fs'), + path = require('path'); + +var args = (args = process.argv) + .slice((args[0] === process.execPath || args[0] === 'node') ? 2 : 0); + +var filePath = path.resolve(args[1]), + reLine = /.*/gm; + +var pattern = (function() { + var result = args[0], + delimiter = result.charAt(0), + lastIndex = result.lastIndexOf(delimiter); + + return RegExp(result.slice(1, lastIndex), result.slice(lastIndex + 1)); +}()); + +/*----------------------------------------------------------------------------*/ + +fs.writeFileSync(filePath, fs.readFileSync(filePath, 'utf8').replace(pattern, function(match) { + var snippet = _.slice(arguments, -3, -2)[0]; + return match.replace(snippet, snippet.replace(reLine, '')); +})); diff --git a/public/vendor/lodash/test/saucelabs.js b/public/vendor/lodash/test/saucelabs.js new file mode 100644 index 00000000000..38c8bb7b3cd --- /dev/null +++ b/public/vendor/lodash/test/saucelabs.js @@ -0,0 +1,914 @@ +#!/usr/bin/env node +'use strict'; + +/** Environment shortcut. */ +var env = process.env; + +if (env.TRAVIS_SECURE_ENV_VARS == 'false') { + console.log('Skipping Sauce Labs jobs; secure environment variables are unavailable'); + process.exit(0); +} + +/** Load Node.js modules. */ +var EventEmitter = require('events').EventEmitter, + http = require('http'), + path = require('path'), + url = require('url'), + util = require('util'); + +/** Load other modules. */ +var _ = require('../lodash.js'), + chalk = require('chalk'), + ecstatic = require('ecstatic'), + request = require('request'), + SauceTunnel = require('sauce-tunnel'); + +/** Used for Sauce Labs credentials. */ +var accessKey = env.SAUCE_ACCESS_KEY, + username = env.SAUCE_USERNAME; + +/** Used as the default maximum number of times to retry a job and tunnel. */ +var maxJobRetries = 3, + maxTunnelRetries = 3; + +/** Used as the static file server middleware. */ +var mount = ecstatic({ + 'cache': 'no-cache', + 'root': process.cwd() +}); + +/** Used as the list of ports supported by Sauce Connect. */ +var ports = [ + 80, 443, 888, 2000, 2001, 2020, 2109, 2222, 2310, 3000, 3001, 3030, 3210, + 3333, 4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555, 5432, + 6000, 6001, 6060, 6666, 6543, 7000, 7070, 7774, 7777, 8000, 8001, 8003, 8031, + 8080, 8081, 8765, 8777, 8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221, + 55001 +]; + +/** Used by `logInline` to clear previously logged messages. */ +var prevLine = ''; + +/** Method shortcut. */ +var push = Array.prototype.push; + +/** Used to detect error messages. */ +var reError = /(?:\be|E)rror\b/; + +/** Used to detect valid job ids. */ +var reJobId = /^[a-z0-9]{32}$/; + +/** Used to display the wait throbber. */ +var throbberDelay = 500, + waitCount = -1; + +/** + * Used as Sauce Labs config values. + * See the [Sauce Labs documentation](https://docs.saucelabs.com/reference/test-configuration/) + * for more details. + */ +var advisor = getOption('advisor', false), + build = getOption('build', (env.TRAVIS_COMMIT || '').slice(0, 10)), + commandTimeout = getOption('commandTimeout', 90), + compatMode = getOption('compatMode', null), + customData = Function('return {' + getOption('customData', '').replace(/^\{|}$/g, '') + '}')(), + deviceOrientation = getOption('deviceOrientation', 'portrait'), + framework = getOption('framework', 'qunit'), + idleTimeout = getOption('idleTimeout', 60), + jobName = getOption('name', 'unit tests'), + maxDuration = getOption('maxDuration', 180), + port = ports[Math.min(_.sortedIndex(ports, getOption('port', 9001)), ports.length - 1)], + publicAccess = getOption('public', true), + queueTimeout = getOption('queueTimeout', 240), + recordVideo = getOption('recordVideo', true), + recordScreenshots = getOption('recordScreenshots', false), + runner = getOption('runner', 'test/index.html').replace(/^\W+/, ''), + runnerUrl = getOption('runnerUrl', 'http://localhost:' + port + '/' + runner), + statusInterval = getOption('statusInterval', 5), + tags = getOption('tags', []), + throttled = getOption('throttled', 10), + tunneled = getOption('tunneled', true), + tunnelId = getOption('tunnelId', 'tunnel_' + (env.TRAVIS_JOB_ID || 0)), + tunnelTimeout = getOption('tunnelTimeout', 120), + videoUploadOnPass = getOption('videoUploadOnPass', false); + +/** Used to convert Sauce Labs browser identifiers to their formal names. */ +var browserNameMap = { + 'googlechrome': 'Chrome', + 'iehta': 'Internet Explorer', + 'ipad': 'iPad', + 'iphone': 'iPhone', + 'microsoftedge': 'Edge' +}; + +/** List of platforms to load the runner on. */ +var platforms = [ + ['Linux', 'android', '5.1'], + ['Windows 10', 'chrome', '51'], + ['Windows 10', 'chrome', '50'], + ['Windows 10', 'firefox', '47'], + ['Windows 10', 'firefox', '46'], + ['Windows 10', 'microsoftedge', '13'], + ['Windows 10', 'internet explorer', '11'], + ['Windows 8', 'internet explorer', '10'], + ['Windows 7', 'internet explorer', '9'], + // ['OS X 10.10', 'ipad', '9.1'], + ['OS X 10.11', 'safari', '9'], + ['OS X 10.10', 'safari', '8'] +]; + +/** Used to tailor the `platforms` array. */ +var isAMD = _.includes(tags, 'amd'), + isBackbone = _.includes(tags, 'backbone'), + isModern = _.includes(tags, 'modern'); + +// The platforms to test IE compatibility modes. +if (compatMode) { + platforms = [ + ['Windows 10', 'internet explorer', '11'], + ['Windows 8', 'internet explorer', '10'], + ['Windows 7', 'internet explorer', '9'], + ['Windows 7', 'internet explorer', '8'] + ]; +} +// The platforms for AMD tests. +if (isAMD) { + platforms = _.filter(platforms, function(platform) { + var browser = browserName(platform[1]), + version = +platform[2]; + + switch (browser) { + case 'Android': return version >= 4.4; + case 'Opera': return version >= 10; + } + return true; + }); +} +// The platforms for Backbone tests. +if (isBackbone) { + platforms = _.filter(platforms, function(platform) { + var browser = browserName(platform[1]), + version = +platform[2]; + + switch (browser) { + case 'Firefox': return version >= 4; + case 'Internet Explorer': return version >= 7; + case 'iPad': return version >= 5; + case 'Opera': return version >= 12; + } + return true; + }); +} +// The platforms for modern builds. +if (isModern) { + platforms = _.filter(platforms, function(platform) { + var browser = browserName(platform[1]), + version = +platform[2]; + + switch (browser) { + case 'Android': return version >= 4.1; + case 'Firefox': return version >= 10; + case 'Internet Explorer': return version >= 9; + case 'iPad': return version >= 6; + case 'Opera': return version >= 12; + case 'Safari': return version >= 6; + } + return true; + }); +} + +/** Used as the default `Job` options object. */ +var jobOptions = { + 'build': build, + 'command-timeout': commandTimeout, + 'custom-data': customData, + 'device-orientation': deviceOrientation, + 'framework': framework, + 'idle-timeout': idleTimeout, + 'max-duration': maxDuration, + 'name': jobName, + 'public': publicAccess, + 'platforms': platforms, + 'record-screenshots': recordScreenshots, + 'record-video': recordVideo, + 'sauce-advisor': advisor, + 'tags': tags, + 'url': runnerUrl, + 'video-upload-on-pass': videoUploadOnPass +}; + +if (publicAccess === true) { + jobOptions['public'] = 'public'; +} +if (tunneled) { + jobOptions['tunnel-identifier'] = tunnelId; +} + +/*----------------------------------------------------------------------------*/ + +/** + * Resolves the formal browser name for a given Sauce Labs browser identifier. + * + * @private + * @param {string} identifier The browser identifier. + * @returns {string} Returns the formal browser name. + */ +function browserName(identifier) { + return browserNameMap[identifier] || _.startCase(identifier); +} + +/** + * Gets the value for the given option name. If no value is available the + * `defaultValue` is returned. + * + * @private + * @param {string} name The name of the option. + * @param {*} defaultValue The default option value. + * @returns {*} Returns the option value. + */ +function getOption(name, defaultValue) { + var isArr = _.isArray(defaultValue); + return _.reduce(process.argv, function(result, value) { + if (isArr) { + value = optionToArray(name, value); + return _.isEmpty(value) ? result : value; + } + value = optionToValue(name, value); + + return value == null ? result : value; + }, defaultValue); +} + +/** + * Checks if `value` is a job ID. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a job ID, else `false`. + */ +function isJobId(value) { + return reJobId.test(value); +} + +/** + * Writes an inline message to standard output. + * + * @private + * @param {string} [text=''] The text to log. + */ +function logInline(text) { + var blankLine = _.repeat(' ', _.size(prevLine)); + prevLine = text = _.truncate(text, { 'length': 40 }); + process.stdout.write(text + blankLine.slice(text.length) + '\r'); +} + +/** + * Writes the wait throbber to standard output. + * + * @private + */ +function logThrobber() { + logInline('Please wait' + _.repeat('.', (++waitCount % 3) + 1)); +} + +/** + * Converts a comma separated option value into an array. + * + * @private + * @param {string} name The name of the option to inspect. + * @param {string} string The options string. + * @returns {Array} Returns the new converted array. + */ +function optionToArray(name, string) { + return _.compact(_.invokeMap((optionToValue(name, string) || '').split(/, */), 'trim')); +} + +/** + * Extracts the option value from an option string. + * + * @private + * @param {string} name The name of the option to inspect. + * @param {string} string The options string. + * @returns {string|undefined} Returns the option value, else `undefined`. + */ +function optionToValue(name, string) { + var result = string.match(RegExp('^' + name + '(?:=([\\s\\S]+))?$')); + if (result) { + result = _.get(result, 1); + result = result ? _.trim(result) : true; + } + if (result === 'false') { + return false; + } + return result || undefined; +} + +/*----------------------------------------------------------------------------*/ + +/** + * The `Job#remove` and `Tunnel#stop` callback used by `Jobs#restart` + * and `Tunnel#restart` respectively. + * + * @private + */ +function onGenericRestart() { + this.restarting = false; + this.emit('restart'); + this.start(); +} + +/** + * The `request.put` and `SauceTunnel#stop` callback used by `Jobs#stop` + * and `Tunnel#stop` respectively. + * + * @private + * @param {Object} [error] The error object. + */ +function onGenericStop(error) { + this.running = this.stopping = false; + this.emit('stop', error); +} + +/** + * The `request.del` callback used by `Jobs#remove`. + * + * @private + */ +function onJobRemove(error, res, body) { + this.id = this.taskId = this.url = null; + this.removing = false; + this.emit('remove'); +} + +/** + * The `Job#remove` callback used by `Jobs#reset`. + * + * @private + */ +function onJobReset() { + this.attempts = 0; + this.failed = this.resetting = false; + this._pollerId = this.id = this.result = this.taskId = this.url = null; + this.emit('reset'); +} + +/** + * The `request.post` callback used by `Jobs#start`. + * + * @private + * @param {Object} [error] The error object. + * @param {Object} res The response data object. + * @param {Object} body The response body JSON object. + */ +function onJobStart(error, res, body) { + this.starting = false; + + if (this.stopping) { + return; + } + var statusCode = _.get(res, 'statusCode'), + taskId = _.first(_.get(body, 'js tests')); + + if (error || !taskId || statusCode != 200) { + if (this.attempts < this.retries) { + this.restart(); + return; + } + var na = 'unavailable', + bodyStr = _.isObject(body) ? '\n' + JSON.stringify(body) : na, + statusStr = _.isFinite(statusCode) ? statusCode : na; + + logInline(); + console.error('Failed to start job; status: %s, body: %s', statusStr, bodyStr); + if (error) { + console.error(error); + } + this.failed = true; + this.emit('complete'); + return; + } + this.running = true; + this.taskId = taskId; + this.timestamp = _.now(); + this.emit('start'); + this.status(); +} + +/** + * The `request.post` callback used by `Job#status`. + * + * @private + * @param {Object} [error] The error object. + * @param {Object} res The response data object. + * @param {Object} body The response body JSON object. + */ +function onJobStatus(error, res, body) { + this.checking = false; + + if (!this.running || this.stopping) { + return; + } + var completed = _.get(body, 'completed', false), + data = _.first(_.get(body, 'js tests')), + elapsed = (_.now() - this.timestamp) / 1000, + jobId = _.get(data, 'job_id', null), + jobResult = _.get(data, 'result', null), + jobStatus = _.get(data, 'status', ''), + jobUrl = _.get(data, 'url', null), + expired = (elapsed >= queueTimeout && !_.includes(jobStatus, 'in progress')), + options = this.options, + platform = options.platforms[0]; + + if (_.isObject(jobResult)) { + var message = _.get(jobResult, 'message'); + } else { + if (typeof jobResult == 'string') { + message = jobResult; + } + jobResult = null; + } + if (isJobId(jobId)) { + this.id = jobId; + this.result = jobResult; + this.url = jobUrl; + } else { + completed = false; + } + this.emit('status', jobStatus); + + if (!completed && !expired) { + this._pollerId = _.delay(_.bind(this.status, this), this.statusInterval * 1000); + return; + } + var description = browserName(platform[1]) + ' ' + platform[2] + ' on ' + _.startCase(platform[0]), + errored = !jobResult || !jobResult.passed || reError.test(message) || reError.test(jobStatus), + failures = _.get(jobResult, 'failed'), + label = options.name + ':', + tunnel = this.tunnel; + + if (errored || failures) { + if (errored && this.attempts < this.retries) { + this.restart(); + return; + } + var details = 'See ' + jobUrl + ' for details.'; + this.failed = true; + + logInline(); + if (failures) { + console.error(label + ' %s ' + chalk.red('failed') + ' %d test' + (failures > 1 ? 's' : '') + '. %s', description, failures, details); + } + else if (tunnel.attempts < tunnel.retries) { + tunnel.restart(); + return; + } + else { + if (message === undefined) { + message = 'Results are unavailable. ' + details; + } + console.error(label, description, chalk.red('failed') + ';', message); + } + } + else { + logInline(); + console.log(label, description, chalk.green('passed')); + } + this.running = false; + this.emit('complete'); +} + +/** + * The `SauceTunnel#start` callback used by `Tunnel#start`. + * + * @private + * @param {boolean} success The connection success indicator. + */ +function onTunnelStart(success) { + this.starting = false; + + if (this._timeoutId) { + clearTimeout(this._timeoutId); + this._timeoutId = null; + } + if (!success) { + if (this.attempts < this.retries) { + this.restart(); + return; + } + logInline(); + console.error('Failed to open Sauce Connect tunnel'); + process.exit(2); + } + logInline(); + console.log('Sauce Connect tunnel opened'); + + var jobs = this.jobs; + push.apply(jobs.queue, jobs.all); + + this.running = true; + this.emit('start'); + + console.log('Starting jobs...'); + this.dequeue(); +} + +/*----------------------------------------------------------------------------*/ + +/** + * The Job constructor. + * + * @private + * @param {Object} [properties] The properties to initialize a job with. + */ +function Job(properties) { + EventEmitter.call(this); + + this.options = {}; + _.merge(this, properties); + _.defaults(this.options, _.cloneDeep(jobOptions)); + + this.attempts = 0; + this.checking = this.failed = this.removing = this.resetting = this.restarting = this.running = this.starting = this.stopping = false; + this._pollerId = this.id = this.result = this.taskId = this.url = null; +} + +util.inherits(Job, EventEmitter); + +/** + * Removes the job. + * + * @memberOf Job + * @param {Function} callback The function called once the job is removed. + * @param {Object} Returns the job instance. + */ +Job.prototype.remove = function(callback) { + this.once('remove', _.iteratee(callback)); + if (this.removing) { + return this; + } + this.removing = true; + return this.stop(function() { + var onRemove = _.bind(onJobRemove, this); + if (!this.id) { + _.defer(onRemove); + return; + } + request.del(_.template('https://saucelabs.com/rest/v1/${user}/jobs/${id}')(this), { + 'auth': { 'user': this.user, 'pass': this.pass } + }, onRemove); + }); +}; + +/** + * Resets the job. + * + * @memberOf Job + * @param {Function} callback The function called once the job is reset. + * @param {Object} Returns the job instance. + */ +Job.prototype.reset = function(callback) { + this.once('reset', _.iteratee(callback)); + if (this.resetting) { + return this; + } + this.resetting = true; + return this.remove(onJobReset); +}; + +/** + * Restarts the job. + * + * @memberOf Job + * @param {Function} callback The function called once the job is restarted. + * @param {Object} Returns the job instance. + */ +Job.prototype.restart = function(callback) { + this.once('restart', _.iteratee(callback)); + if (this.restarting) { + return this; + } + this.restarting = true; + + var options = this.options, + platform = options.platforms[0], + description = browserName(platform[1]) + ' ' + platform[2] + ' on ' + _.startCase(platform[0]), + label = options.name + ':'; + + logInline(); + console.log('%s %s restart %d of %d', label, description, ++this.attempts, this.retries); + + return this.remove(onGenericRestart); +}; + +/** + * Starts the job. + * + * @memberOf Job + * @param {Function} callback The function called once the job is started. + * @param {Object} Returns the job instance. + */ +Job.prototype.start = function(callback) { + this.once('start', _.iteratee(callback)); + if (this.starting || this.running) { + return this; + } + this.starting = true; + request.post(_.template('https://saucelabs.com/rest/v1/${user}/js-tests')(this), { + 'auth': { 'user': this.user, 'pass': this.pass }, + 'json': this.options + }, _.bind(onJobStart, this)); + + return this; +}; + +/** + * Checks the status of a job. + * + * @memberOf Job + * @param {Function} callback The function called once the status is resolved. + * @param {Object} Returns the job instance. + */ +Job.prototype.status = function(callback) { + this.once('status', _.iteratee(callback)); + if (this.checking || this.removing || this.resetting || this.restarting || this.starting || this.stopping) { + return this; + } + this._pollerId = null; + this.checking = true; + request.post(_.template('https://saucelabs.com/rest/v1/${user}/js-tests/status')(this), { + 'auth': { 'user': this.user, 'pass': this.pass }, + 'json': { 'js tests': [this.taskId] } + }, _.bind(onJobStatus, this)); + + return this; +}; + +/** + * Stops the job. + * + * @memberOf Job + * @param {Function} callback The function called once the job is stopped. + * @param {Object} Returns the job instance. + */ +Job.prototype.stop = function(callback) { + this.once('stop', _.iteratee(callback)); + if (this.stopping) { + return this; + } + this.stopping = true; + if (this._pollerId) { + clearTimeout(this._pollerId); + this._pollerId = null; + this.checking = false; + } + var onStop = _.bind(onGenericStop, this); + if (!this.running || !this.id) { + _.defer(onStop); + return this; + } + request.put(_.template('https://saucelabs.com/rest/v1/${user}/jobs/${id}/stop')(this), { + 'auth': { 'user': this.user, 'pass': this.pass } + }, onStop); + + return this; +}; + +/*----------------------------------------------------------------------------*/ + +/** + * The Tunnel constructor. + * + * @private + * @param {Object} [properties] The properties to initialize the tunnel with. + */ +function Tunnel(properties) { + EventEmitter.call(this); + + _.merge(this, properties); + + var active = [], + queue = []; + + var all = _.map(this.platforms, _.bind(function(platform) { + return new Job(_.merge({ + 'user': this.user, + 'pass': this.pass, + 'tunnel': this, + 'options': { 'platforms': [platform] } + }, this.job)); + }, this)); + + var completed = 0, + restarted = [], + success = true, + total = all.length, + tunnel = this; + + _.invokeMap(all, 'on', 'complete', function() { + _.pull(active, this); + if (success) { + success = !this.failed; + } + if (++completed == total) { + tunnel.stop(_.partial(tunnel.emit, 'complete', success)); + return; + } + tunnel.dequeue(); + }); + + _.invokeMap(all, 'on', 'restart', function() { + if (!_.includes(restarted, this)) { + restarted.push(this); + } + // Restart tunnel if all active jobs have restarted. + var threshold = Math.min(all.length, _.isFinite(throttled) ? throttled : 3); + if (tunnel.attempts < tunnel.retries && + active.length >= threshold && _.isEmpty(_.difference(active, restarted))) { + tunnel.restart(); + } + }); + + this.on('restart', function() { + completed = 0; + success = true; + restarted.length = 0; + }); + + this._timeoutId = null; + this.attempts = 0; + this.restarting = this.running = this.starting = this.stopping = false; + this.jobs = { 'active': active, 'all': all, 'queue': queue }; + this.connection = new SauceTunnel(this.user, this.pass, this.id, this.tunneled, ['-P', '0']); +} + +util.inherits(Tunnel, EventEmitter); + +/** + * Restarts the tunnel. + * + * @memberOf Tunnel + * @param {Function} callback The function called once the tunnel is restarted. + */ +Tunnel.prototype.restart = function(callback) { + this.once('restart', _.iteratee(callback)); + if (this.restarting) { + return this; + } + this.restarting = true; + + logInline(); + console.log('Tunnel %s: restart %d of %d', this.id, ++this.attempts, this.retries); + + var jobs = this.jobs, + active = jobs.active, + all = jobs.all; + + var reset = _.after(all.length, _.bind(this.stop, this, onGenericRestart)), + stop = _.after(active.length, _.partial(_.invokeMap, all, 'reset', reset)); + + if (_.isEmpty(active)) { + _.defer(stop); + } + if (_.isEmpty(all)) { + _.defer(reset); + } + _.invokeMap(active, 'stop', function() { + _.pull(active, this); + stop(); + }); + + if (this._timeoutId) { + clearTimeout(this._timeoutId); + this._timeoutId = null; + } + return this; +}; + +/** + * Starts the tunnel. + * + * @memberOf Tunnel + * @param {Function} callback The function called once the tunnel is started. + * @param {Object} Returns the tunnel instance. + */ +Tunnel.prototype.start = function(callback) { + this.once('start', _.iteratee(callback)); + if (this.starting || this.running) { + return this; + } + this.starting = true; + + logInline(); + console.log('Opening Sauce Connect tunnel...'); + + var onStart = _.bind(onTunnelStart, this); + if (this.timeout) { + this._timeoutId = _.delay(onStart, this.timeout * 1000, false); + } + this.connection.start(onStart); + return this; +}; + +/** + * Removes jobs from the queue and starts them. + * + * @memberOf Tunnel + * @param {Object} Returns the tunnel instance. + */ +Tunnel.prototype.dequeue = function() { + var count = 0, + jobs = this.jobs, + active = jobs.active, + queue = jobs.queue, + throttled = this.throttled; + + while (queue.length && (active.length < throttled)) { + var job = queue.shift(); + active.push(job); + _.delay(_.bind(job.start, job), ++count * 1000); + } + return this; +}; + +/** + * Stops the tunnel. + * + * @memberOf Tunnel + * @param {Function} callback The function called once the tunnel is stopped. + * @param {Object} Returns the tunnel instance. + */ +Tunnel.prototype.stop = function(callback) { + this.once('stop', _.iteratee(callback)); + if (this.stopping) { + return this; + } + this.stopping = true; + + logInline(); + console.log('Shutting down Sauce Connect tunnel...'); + + var jobs = this.jobs, + active = jobs.active; + + var stop = _.after(active.length, _.bind(function() { + var onStop = _.bind(onGenericStop, this); + if (this.running) { + this.connection.stop(onStop); + } else { + onStop(); + } + }, this)); + + jobs.queue.length = 0; + if (_.isEmpty(active)) { + _.defer(stop); + } + _.invokeMap(active, 'stop', function() { + _.pull(active, this); + stop(); + }); + + if (this._timeoutId) { + clearTimeout(this._timeoutId); + this._timeoutId = null; + } + return this; +}; + +/*----------------------------------------------------------------------------*/ + +// Cleanup any inline logs when exited via `ctrl+c`. +process.on('SIGINT', function() { + logInline(); + process.exit(); +}); + +// Create a web server for the current working directory. +http.createServer(function(req, res) { + // See http://msdn.microsoft.com/en-us/library/ff955275(v=vs.85).aspx. + if (compatMode && path.extname(url.parse(req.url).pathname) == '.html') { + res.setHeader('X-UA-Compatible', 'IE=' + compatMode); + } + mount(req, res); +}).listen(port); + +// Setup Sauce Connect so we can use this server from Sauce Labs. +var tunnel = new Tunnel({ + 'user': username, + 'pass': accessKey, + 'id': tunnelId, + 'job': { 'retries': maxJobRetries, 'statusInterval': statusInterval }, + 'platforms': platforms, + 'retries': maxTunnelRetries, + 'throttled': throttled, + 'tunneled': tunneled, + 'timeout': tunnelTimeout +}); + +tunnel.on('complete', function(success) { + process.exit(success ? 0 : 1); +}); + +tunnel.start(); + +setInterval(logThrobber, throbberDelay); diff --git a/public/vendor/lodash/test/test-fp.js b/public/vendor/lodash/test/test-fp.js new file mode 100644 index 00000000000..f06ac71c370 --- /dev/null +++ b/public/vendor/lodash/test/test-fp.js @@ -0,0 +1,2301 @@ +;(function() { + 'use strict'; + + /** Used as a safe reference for `undefined` in pre-ES5 environments. */ + var undefined; + + /** Used as the size to cover large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** Used as a reference to the global object. */ + var root = (typeof global == 'object' && global) || this; + + /** Used for native method references. */ + var arrayProto = Array.prototype; + + /** Method and object shortcuts. */ + var phantom = root.phantom, + argv = root.process && process.argv, + document = !phantom && root.document, + slice = arrayProto.slice, + WeakMap = root.WeakMap; + + /** Math helpers. */ + var add = function(x, y) { return x + y; }, + isEven = function(n) { return n % 2 == 0; }, + isEvenIndex = function(n, index) { return isEven(index); }, + square = function(n) { return n * n; }; + + // Leak to avoid sporadic `noglobals` fails on Edge in Sauce Labs. + root.msWDfn = undefined; + + /*--------------------------------------------------------------------------*/ + + /** Load QUnit and extras. */ + var QUnit = root.QUnit || require('qunit-extras'); + + /** Load stable Lodash. */ + var _ = root._ || require('../lodash.js'); + + var convert = (function() { + var baseConvert = root.fp || require('../fp/_baseConvert.js'); + if (!root.fp) { + return function(name, func, options) { + return baseConvert(_, name, func, options); + }; + } + return function(name, func, options) { + if (typeof name == 'function') { + options = func; + func = name; + name = undefined; + } + return name === undefined + ? baseConvert(func, options) + : baseConvert(_.runInContext(), options)[name]; + }; + }()); + + var allFalseOptions = { + 'cap': false, + 'curry': false, + 'fixed': false, + 'immutable': false, + 'rearg': false + }; + + var fp = root.fp + ? (fp = _.noConflict(), _ = root._, fp) + : convert(_.runInContext()); + + var mapping = root.mapping || require('../fp/_mapping.js'); + + /*--------------------------------------------------------------------------*/ + + /** + * Skips a given number of tests with a passing result. + * + * @private + * @param {Object} assert The QUnit assert object. + * @param {number} [count=1] The number of tests to skip. + */ + function skipAssert(assert, count) { + count || (count = 1); + while (count--) { + assert.ok(true, 'test skipped'); + } + } + + /*--------------------------------------------------------------------------*/ + + if (argv) { + console.log('Running lodash/fp tests.'); + } + + QUnit.module('convert module'); + + (function() { + QUnit.test('should work with `name` and `func`', function(assert) { + assert.expect(2); + + var array = [1, 2, 3, 4], + remove = convert('remove', _.remove), + actual = remove(isEven)(array); + + assert.deepEqual(array, [1, 2, 3, 4]); + assert.deepEqual(actual, [1, 3]); + }); + + QUnit.test('should work with `name`, `func`, and `options`', function(assert) { + assert.expect(3); + + var array = [1, 2, 3, 4], + remove = convert('remove', _.remove, allFalseOptions); + + var actual = remove(array, function(n, index) { + return isEven(index); + }); + + assert.deepEqual(array, [2, 4]); + assert.deepEqual(actual, [1, 3]); + assert.deepEqual(remove(), []); + }); + + QUnit.test('should work with an object', function(assert) { + assert.expect(2); + + if (!document) { + var array = [1, 2, 3, 4], + lodash = convert({ 'remove': _.remove }), + actual = lodash.remove(isEven)(array); + + assert.deepEqual(array, [1, 2, 3, 4]); + assert.deepEqual(actual, [1, 3]); + } + else { + skipAssert(assert, 2); + } + }); + + QUnit.test('should work with an object and `options`', function(assert) { + assert.expect(3); + + if (!document) { + var array = [1, 2, 3, 4], + lodash = convert({ 'remove': _.remove }, allFalseOptions), + actual = lodash.remove(array, isEvenIndex); + + assert.deepEqual(array, [2, 4]); + assert.deepEqual(actual, [1, 3]); + assert.deepEqual(lodash.remove(), []); + } + else { + skipAssert(assert, 3); + } + }); + + QUnit.test('should work with lodash and `options`', function(assert) { + assert.expect(3); + + var array = [1, 2, 3, 4], + lodash = convert(_.runInContext(), allFalseOptions), + actual = lodash.remove(array, isEvenIndex); + + assert.deepEqual(array, [2, 4]); + assert.deepEqual(actual, [1, 3]); + assert.deepEqual(lodash.remove(), []); + }); + + QUnit.test('should work with `runInContext` and `options`', function(assert) { + assert.expect(3); + + var array = [1, 2, 3, 4], + runInContext = convert('runInContext', _.runInContext, allFalseOptions), + lodash = runInContext(), + actual = lodash.remove(array, isEvenIndex); + + assert.deepEqual(array, [2, 4]); + assert.deepEqual(actual, [1, 3]); + assert.deepEqual(lodash.remove(), []); + }); + + QUnit.test('should accept a variety of options', function(assert) { + assert.expect(8); + + var array = [1, 2, 3, 4], + value = _.clone(array), + remove = convert('remove', _.remove, { 'cap': false }), + actual = remove(isEvenIndex)(value); + + assert.deepEqual(value, [1, 2, 3, 4]); + assert.deepEqual(actual, [2, 4]); + + remove = convert('remove', _.remove, { 'curry': false }); + actual = remove(isEven); + + assert.deepEqual(actual, []); + + var trim = convert('trim', _.trim, { 'fixed': false }); + assert.strictEqual(trim('_-abc-_', '_-'), 'abc'); + + value = _.clone(array); + remove = convert('remove', _.remove, { 'immutable': false }); + actual = remove(isEven)(value); + + assert.deepEqual(value, [1, 3]); + assert.deepEqual(actual, [2, 4]); + + value = _.clone(array); + remove = convert('remove', _.remove, { 'rearg': false }); + actual = remove(value)(isEven); + + assert.deepEqual(value, [1, 2, 3, 4]); + assert.deepEqual(actual, [1, 3]); + }); + + QUnit.test('should respect the `cap` option', function(assert) { + assert.expect(1); + + var iteratee = convert('iteratee', _.iteratee, { 'cap': false }); + + var func = iteratee(function(a, b, c) { + return [a, b, c]; + }, 3); + + assert.deepEqual(func(1, 2, 3), [1, 2, 3]); + }); + + QUnit.test('should respect the `rearg` option', function(assert) { + assert.expect(1); + + var add = convert('add', _.add, { 'rearg': true }); + + assert.strictEqual(add('2')('1'), '12'); + }); + + QUnit.test('should only add a `placeholder` property if needed', function(assert) { + assert.expect(2); + + if (!document) { + var methodNames = _.keys(mapping.placeholder), + expected = _.map(methodNames, _.constant(true)); + + var actual = _.map(methodNames, function(methodName) { + var object = {}; + object[methodName] = _[methodName]; + + var lodash = convert(object); + return methodName in lodash; + }); + + assert.deepEqual(actual, expected); + + var lodash = convert({ 'add': _.add }); + assert.notOk('placeholder' in lodash); + } + else { + skipAssert(assert, 2); + } + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('method.convert'); + + (function() { + QUnit.test('should exist on unconverted methods', function(assert) { + assert.expect(2); + + var array = [], + isArray = fp.isArray.convert({ 'curry': true }); + + assert.strictEqual(fp.isArray(array), true); + assert.strictEqual(isArray()(array), true); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('convert methods'); + + _.each(['fp.convert', 'method.convert'], function(methodName) { + var isFp = methodName == 'fp.convert', + func = isFp ? fp.convert : fp.remove.convert; + + QUnit.test('`' + methodName + '` should work with an object', function(assert) { + assert.expect(3); + + var array = [1, 2, 3, 4], + lodash = func(allFalseOptions), + remove = isFp ? lodash.remove : lodash, + actual = remove(array, isEvenIndex); + + assert.deepEqual(array, [2, 4]); + assert.deepEqual(actual, [1, 3]); + assert.deepEqual(remove(), []); + }); + + QUnit.test('`' + methodName + '` should extend existing configs', function(assert) { + assert.expect(2); + + var array = [1, 2, 3, 4], + lodash = func({ 'cap': false }), + remove = (isFp ? lodash.remove : lodash).convert({ 'rearg': false }), + actual = remove(array)(isEvenIndex); + + assert.deepEqual(array, [1, 2, 3, 4]); + assert.deepEqual(actual, [2, 4]); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('method arity checks'); + + (function() { + QUnit.test('should wrap methods with an arity > `1`', function(assert) { + assert.expect(1); + + var methodNames = _.filter(_.functions(fp), function(methodName) { + return fp[methodName].length > 1; + }); + + assert.deepEqual(methodNames, []); + }); + + QUnit.test('should have >= arity of `aryMethod` designation', function(assert) { + assert.expect(4); + + _.times(4, function(index) { + var aryCap = index + 1; + + var methodNames = _.filter(mapping.aryMethod[aryCap], function(methodName) { + var key = _.get(mapping.remap, methodName, methodName), + arity = _[key].length; + + return arity != 0 && arity < aryCap; + }); + + assert.deepEqual(methodNames, [], '`aryMethod[' + aryCap + ']`'); + }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('method aliases'); + + (function() { + QUnit.test('should have correct aliases', function(assert) { + assert.expect(1); + + var actual = _.transform(mapping.aliasToReal, function(result, realName, alias) { + result.push([alias, fp[alias] === fp[realName]]); + }, []); + + assert.deepEqual(_.reject(actual, 1), []); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('method ary caps'); + + (function() { + QUnit.test('should have a cap of 1', function(assert) { + assert.expect(1); + + var funcMethods = [ + 'curry', 'iteratee', 'memoize', 'over', 'overEvery', 'overSome', + 'method', 'methodOf', 'rest', 'runInContext' + ]; + + var exceptions = funcMethods.concat('mixin', 'nthArg', 'template'), + expected = _.map(mapping.aryMethod[1], _.constant(true)); + + var actual = _.map(mapping.aryMethod[1], function(methodName) { + var arg = _.includes(funcMethods, methodName) ? _.noop : 1, + result = _.attempt(function() { return fp[methodName](arg); }); + + if (_.includes(exceptions, methodName) + ? typeof result == 'function' + : typeof result != 'function' + ) { + return true; + } + console.log(methodName, result); + return false; + }); + + assert.deepEqual(actual, expected); + }); + + QUnit.test('should have a cap of 2', function(assert) { + assert.expect(1); + + var funcMethods = [ + 'after', 'ary', 'before', 'bind', 'bindKey', 'curryN', 'debounce', + 'delay', 'overArgs', 'partial', 'partialRight', 'rearg', 'throttle', + 'wrap' + ]; + + var exceptions = _.without(funcMethods.concat('matchesProperty'), 'delay'), + expected = _.map(mapping.aryMethod[2], _.constant(true)); + + var actual = _.map(mapping.aryMethod[2], function(methodName) { + var args = _.includes(funcMethods, methodName) ? [methodName == 'curryN' ? 1 : _.noop, _.noop] : [1, []], + result = _.attempt(function() { return fp[methodName](args[0])(args[1]); }); + + if (_.includes(exceptions, methodName) + ? typeof result == 'function' + : typeof result != 'function' + ) { + return true; + } + console.log(methodName, result); + return false; + }); + + assert.deepEqual(actual, expected); + }); + + QUnit.test('should have a cap of 3', function(assert) { + assert.expect(1); + + var funcMethods = [ + 'assignWith', 'extendWith', 'isEqualWith', 'isMatchWith', 'reduce', + 'reduceRight', 'transform', 'zipWith' + ]; + + var expected = _.map(mapping.aryMethod[3], _.constant(true)); + + var actual = _.map(mapping.aryMethod[3], function(methodName) { + var args = _.includes(funcMethods, methodName) ? [_.noop, 0, 1] : [0, 1, []], + result = _.attempt(function() { return fp[methodName](args[0])(args[1])(args[2]); }); + + if (typeof result != 'function') { + return true; + } + console.log(methodName, result); + return false; + }); + + assert.deepEqual(actual, expected); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('methods that use `indexOf`'); + + (function() { + QUnit.test('should work with `fp.indexOf`', function(assert) { + assert.expect(10); + + var array = ['a', 'b', 'c'], + other = ['b', 'd', 'b'], + object = { 'a': 1, 'b': 2, 'c': 2 }, + actual = fp.difference(array)(other); + + assert.deepEqual(actual, ['a', 'c'], 'fp.difference'); + + actual = fp.includes('b')(array); + assert.strictEqual(actual, true, 'fp.includes'); + + actual = fp.intersection(other)(array); + assert.deepEqual(actual, ['b'], 'fp.intersection'); + + actual = fp.omit(other)(object); + assert.deepEqual(actual, { 'a': 1, 'c': 2 }, 'fp.omit'); + + actual = fp.union(other)(array); + assert.deepEqual(actual, ['a', 'b', 'c', 'd'], 'fp.union'); + + actual = fp.uniq(other); + assert.deepEqual(actual, ['b', 'd'], 'fp.uniq'); + + actual = fp.uniqBy(_.identity, other); + assert.deepEqual(actual, ['b', 'd'], 'fp.uniqBy'); + + actual = fp.without(other)(array); + assert.deepEqual(actual, ['a', 'c'], 'fp.without'); + + actual = fp.xor(other)(array); + assert.deepEqual(actual, ['a', 'c', 'd'], 'fp.xor'); + + actual = fp.pull('b')(array); + assert.deepEqual(actual, ['a', 'c'], 'fp.pull'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('cherry-picked methods'); + + (function() { + QUnit.test('should provide the correct `iteratee` arguments', function(assert) { + assert.expect(4); + + var args, + array = [1, 2, 3], + object = { 'a': 1, 'b': 2 }, + isFIFO = _.keys(object)[0] == 'a', + map = convert('map', _.map), + reduce = convert('reduce', _.reduce); + + map(function() { + args || (args = slice.call(arguments)); + })(array); + + assert.deepEqual(args, [1]); + + args = undefined; + map(function() { + args || (args = slice.call(arguments)); + })(object); + + assert.deepEqual(args, isFIFO ? [1] : [2]); + + args = undefined; + reduce(function() { + args || (args = slice.call(arguments)); + })(0)(array); + + assert.deepEqual(args, [0, 1]); + + args = undefined; + reduce(function() { + args || (args = slice.call(arguments)); + })(0)(object); + + assert.deepEqual(args, isFIFO ? [0, 1] : [0, 2]); + }); + + QUnit.test('should not support shortcut fusion', function(assert) { + assert.expect(3); + + var array = fp.range(0, LARGE_ARRAY_SIZE), + filterCount = 0, + mapCount = 0; + + var iteratee = function(value) { + mapCount++; + return value * value; + }; + + var predicate = function(value) { + filterCount++; + return isEven(value); + }; + + var map1 = convert('map', _.map), + filter1 = convert('filter', _.filter), + take1 = convert('take', _.take); + + var filter2 = filter1(predicate), + map2 = map1(iteratee), + take2 = take1(2); + + var combined = fp.flow(map2, filter2, fp.compact, take2); + + assert.deepEqual(combined(array), [4, 16]); + assert.strictEqual(filterCount, 200, 'filterCount'); + assert.strictEqual(mapCount, 200, 'mapCount'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('iteratee shorthands'); + + (function() { + var objects = [{ 'a': 1, 'b': 2 }, { 'a': 3, 'b': 4 }]; + + QUnit.test('should work with "_.matches" shorthands', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.filter({ 'a': 3 })(objects), [objects[1]]); + }); + + QUnit.test('should work with "_.matchesProperty" shorthands', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.filter(['a', 3])(objects), [objects[1]]); + }); + + QUnit.test('should work with "_.property" shorthands', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.map('a')(objects), [1, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('placeholder methods'); + + (function() { + QUnit.test('should use `fp` as the default placeholder', function(assert) { + assert.expect(3); + + var actual = fp.add(fp, 'b')('a'); + assert.strictEqual(actual, 'ab'); + + actual = fp.fill(fp, 2)(1, '*')([1, 2, 3]); + assert.deepEqual(actual, [1, '*', 3]); + + actual = fp.slice(fp, 2)(1)(['a', 'b', 'c']); + assert.deepEqual(actual, ['b']); + }); + + QUnit.test('should support `fp.placeholder`', function(assert) { + assert.expect(6); + + _.each([[], fp.__], function(ph) { + fp.placeholder = ph; + + var actual = fp.add(ph, 'b')('a'); + assert.strictEqual(actual, 'ab'); + + actual = fp.fill(ph, 2)(1, '*')([1, 2, 3]); + assert.deepEqual(actual, [1, '*', 3]); + + actual = fp.slice(ph, 2)(1)(['a', 'b', 'c']); + assert.deepEqual(actual, ['b']); + }); + }); + + _.forOwn(mapping.placeholder, function(truthy, methodName) { + var func = fp[methodName]; + + QUnit.test('`_.' + methodName + '` should have a `placeholder` property', function(assert) { + assert.expect(2); + + assert.ok(_.isObject(func.placeholder)); + assert.strictEqual(func.placeholder, fp.__); + }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('setter methods'); + + (function() { + QUnit.test('should only clone objects in `path`', function(assert) { + assert.expect(11); + + var object = { 'a': { 'b': 2, 'c': 3 }, 'd': { 'e': 4 } }, + value = _.cloneDeep(object), + actual = fp.set('a.b.c.d', 5, value); + + assert.ok(_.isObject(actual.a.b), 'fp.set'); + assert.ok(_.isNumber(actual.a.b), 'fp.set'); + + assert.strictEqual(actual.a.b.c.d, 5, 'fp.set'); + assert.strictEqual(actual.d, value.d, 'fp.set'); + + value = _.cloneDeep(object); + actual = fp.setWith(Object)('[0][1]')('a')(value); + + assert.deepEqual(actual[0], { '1': 'a' }, 'fp.setWith'); + + value = _.cloneDeep(object); + actual = fp.unset('a.b')(value); + + assert.notOk('b' in actual.a, 'fp.unset'); + assert.strictEqual(actual.a.c, value.a.c, 'fp.unset'); + + value = _.cloneDeep(object); + actual = fp.update('a.b')(square)(value); + + assert.strictEqual(actual.a.b, 4, 'fp.update'); + assert.strictEqual(actual.d, value.d, 'fp.update'); + + value = _.cloneDeep(object); + actual = fp.updateWith(Object)('[0][1]')(_.constant('a'))(value); + + assert.deepEqual(actual[0], { '1': 'a' }, 'fp.updateWith'); + assert.strictEqual(actual.d, value.d, 'fp.updateWith'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.add and fp.subtract'); + + _.each(['add', 'subtract'], function(methodName) { + var func = fp[methodName], + isAdd = methodName == 'add'; + + QUnit.test('`fp.' + methodName + '` should not have `rearg` applied', function(assert) { + assert.expect(1); + + assert.strictEqual(func('1')('2'), isAdd ? '12' : -1); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('assign methods'); + + _.each(['assign', 'assignIn', 'defaults', 'defaultsDeep', 'merge'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should not mutate values', function(assert) { + assert.expect(2); + + var object = { 'a': 1 }, + actual = func(object)({ 'b': 2 }); + + assert.deepEqual(object, { 'a': 1 }); + assert.deepEqual(actual, { 'a': 1, 'b': 2 }); + }); + }); + + _.each(['assignAll', 'assignInAll', 'defaultsAll', 'defaultsDeepAll', 'mergeAll'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should not mutate values', function(assert) { + assert.expect(2); + + var objects = [{ 'a': 1 }, { 'b': 2 }], + actual = func(objects); + + assert.deepEqual(objects[0], { 'a': 1 }); + assert.deepEqual(actual, { 'a': 1, 'b': 2 }); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('assignWith methods'); + + _.each(['assignWith', 'assignInWith', 'extendWith'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })({ 'a': 1 })({ 'b': 2 }); + + assert.deepEqual(args, [undefined, 2, 'b', { 'a': 1 }, { 'b': 2 }]); + }); + + QUnit.test('`fp.' + methodName + '` should not mutate values', function(assert) { + assert.expect(2); + + var objects = [{ 'a': 1 }, { 'b': 2 }], + actual = func(_.nthArg(1))(objects[0])(objects[1]); + + assert.deepEqual(objects[0], { 'a': 1 }); + assert.deepEqual(actual, { 'a': 1, 'b': 2 }); + }); + }); + + _.each(['assignAllWith', 'assignInAllWith', 'extendAllWith'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })([{ 'a': 1 }, { 'b': 2 }]); + + assert.deepEqual(args, [undefined, 2, 'b', { 'a': 1 }, { 'b': 2 }]); + }); + + QUnit.test('`fp.' + methodName + '` should not mutate values', function(assert) { + assert.expect(2); + + var objects = [{ 'a': 1 }, { 'b': 2 }], + actual = func(_.nthArg(1))(objects); + + assert.deepEqual(objects[0], { 'a': 1 }); + assert.deepEqual(actual, { 'a': 1, 'b': 2 }); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.castArray'); + + (function() { + QUnit.test('should shallow clone array values', function(assert) { + assert.expect(2); + + var array = [1], + actual = fp.castArray(array); + + assert.deepEqual(actual, array); + assert.notStrictEqual(actual, array); + }); + + QUnit.test('should not shallow clone non-array values', function(assert) { + assert.expect(2); + + var object = { 'a': 1 }, + actual = fp.castArray(object); + + assert.deepEqual(actual, [object]); + assert.strictEqual(actual[0], object); + }); + + QUnit.test('should convert by name', function(assert) { + assert.expect(4); + + var array = [1], + object = { 'a': 1 }, + castArray = convert('castArray', _.castArray), + actual = castArray(array); + + assert.deepEqual(actual, array); + assert.notStrictEqual(actual, array); + + actual = castArray(object); + assert.deepEqual(actual, [object]); + assert.strictEqual(actual[0], object); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('curry methods'); + + _.each(['curry', 'curryRight'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`_.' + methodName + '` should only accept a `func` param', function(assert) { + assert.expect(1); + + assert.raises(function() { func(1, _.noop); }, TypeError); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('curryN methods'); + + _.each(['curryN', 'curryRightN'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`_.' + methodName + '` should accept an `arity` param', function(assert) { + assert.expect(1); + + var actual = func(1)(function(a, b) { return [a, b]; })('a'); + assert.deepEqual(actual, ['a', undefined]); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.defaultTo'); + + (function() { + QUnit.test('should have an argument order of `defaultValue` then `value`', function(assert) { + assert.expect(2); + + assert.strictEqual(fp.defaultTo(1)(0), 0); + assert.strictEqual(fp.defaultTo(1)(undefined), 1); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.difference'); + + (function() { + QUnit.test('should return the elements of the first array not included in the second array', function(assert) { + assert.expect(1); + + var actual = fp.difference([2, 1], [2, 3]); + assert.deepEqual(actual, [1]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.differenceBy'); + + (function() { + QUnit.test('should have an argument order of `iteratee`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.differenceBy(Math.floor, [2.1, 1.2], [2.3, 3.4]); + assert.deepEqual(actual, [1.2]); + }); + + QUnit.test('should provide the correct `iteratee` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.differenceBy(function() { + args || (args = slice.call(arguments)); + })([2.1, 1.2], [2.3, 3.4]); + + assert.deepEqual(args, [2.3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.differenceWith'); + + (function() { + QUnit.test('should have an argument order of `comparator`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.differenceWith(fp.eq)([2, 1])([2, 3]); + assert.deepEqual(actual, [1]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.divide and fp.multiply'); + + _.each(['divide', 'multiply'], function(methodName) { + var func = fp[methodName], + isDivide = methodName == 'divide'; + + QUnit.test('`fp.' + methodName + '` should not have `rearg` applied', function(assert) { + assert.expect(1); + + assert.strictEqual(func('2')('4'), isDivide ? 0.5 : 8); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.extend'); + + (function() { + QUnit.test('should convert by name', function(assert) { + assert.expect(2); + + function Foo() {} + Foo.prototype = { 'b': 2 }; + + var object = { 'a': 1 }, + extend = convert('extend', _.extend), + actual = extend(object)(new Foo); + + assert.deepEqual(object, { 'a': 1 }); + assert.deepEqual(actual, { 'a': 1, 'b': 2 }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.fill'); + + (function() { + QUnit.test('should have an argument order of `start`, `end`, then `value`', function(assert) { + assert.expect(1); + + var array = [1, 2, 3]; + assert.deepEqual(fp.fill(1)(2)('*')(array), [1, '*', 3]); + }); + + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var array = [1, 2, 3], + actual = fp.fill(1)(2)('*')(array); + + assert.deepEqual(array, [1, 2, 3]); + assert.deepEqual(actual, [1, '*', 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.findFrom methods'); + + _.each(['findFrom', 'findIndexFrom', 'findLastFrom', 'findLastIndexFrom'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`_.' + methodName + '` should provide the correct `predicate` arguments', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = slice.call(arguments)); + })(1)([1, 2, 3]); + + assert.deepEqual(args, [2]); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.findFrom'); + + (function() { + function resolve(value) { + return fp.flow(fp.property('a'), fp.eq(value)); + } + + QUnit.test('should have an argument order of `value`, `fromIndex`, then `array`', function(assert) { + assert.expect(2); + + var objects = [{ 'a': 1 }, { 'a': 2 }, { 'a': 1 }, { 'a': 2 }]; + + assert.strictEqual(fp.findFrom(resolve(1))(1)(objects), objects[2]); + assert.strictEqual(fp.findFrom(resolve(2))(-2)(objects), objects[3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.findLastFrom'); + + (function() { + function resolve(value) { + return fp.flow(fp.property('a'), fp.eq(value)); + } + + QUnit.test('should have an argument order of `value`, `fromIndex`, then `array`', function(assert) { + assert.expect(2); + + var objects = [{ 'a': 1 }, { 'a': 2 }, { 'a': 1 }, { 'a': 2 }]; + + assert.strictEqual(fp.findLastFrom(resolve(1))(1)(objects), objects[0]); + assert.strictEqual(fp.findLastFrom(resolve(2))(-2)(objects), objects[1]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.findIndexFrom and fp.indexOfFrom'); + + _.each(['findIndexFrom', 'indexOfFrom'], function(methodName) { + var func = fp[methodName], + resolve = methodName == 'findIndexFrom' ? fp.eq : _.identity; + + QUnit.test('`_.' + methodName + '` should have an argument order of `value`, `fromIndex`, then `array`', function(assert) { + assert.expect(2); + + var array = [1, 2, 3, 1, 2, 3]; + + assert.strictEqual(func(resolve(1))(2)(array), 3); + assert.strictEqual(func(resolve(2))(-3)(array), 4); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.findLastIndexFrom and fp.lastIndexOfFrom'); + + _.each(['findLastIndexFrom', 'lastIndexOfFrom'], function(methodName) { + var func = fp[methodName], + resolve = methodName == 'findLastIndexFrom' ? fp.eq : _.identity; + + QUnit.test('`_.' + methodName + '` should have an argument order of `value`, `fromIndex`, then `array`', function(assert) { + assert.expect(2); + + var array = [1, 2, 3, 1, 2, 3]; + + assert.strictEqual(func(resolve(2))(3)(array), 1); + assert.strictEqual(func(resolve(3))(-3)(array), 2); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.flatMapDepth'); + + (function() { + QUnit.test('should have an argument order of `iteratee`, `depth`, then `collection`', function(assert) { + assert.expect(2); + + function duplicate(n) { + return [[[n, n]]]; + } + + var array = [1, 2], + object = { 'a': 1, 'b': 2 }, + expected = [[1, 1], [2, 2]]; + + assert.deepEqual(fp.flatMapDepth(duplicate)(2)(array), expected); + assert.deepEqual(fp.flatMapDepth(duplicate)(2)(object), expected); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('flow methods'); + + _.each(['flow', 'flowRight'], function(methodName) { + var func = fp[methodName], + isFlow = methodName == 'flow'; + + QUnit.test('`fp.' + methodName + '` should support shortcut fusion', function(assert) { + assert.expect(6); + + var filterCount, + mapCount, + array = fp.range(0, LARGE_ARRAY_SIZE); + + var iteratee = function(value) { + mapCount++; + return square(value); + }; + + var predicate = function(value) { + filterCount++; + return isEven(value); + }; + + var filter = fp.filter(predicate), + map = fp.map(iteratee), + take = fp.take(2); + + _.times(2, function(index) { + var combined = isFlow + ? func(map, filter, fp.compact, take) + : func(take, fp.compact, filter, map); + + filterCount = mapCount = 0; + + if (WeakMap && WeakMap.name) { + assert.deepEqual(combined(array), [4, 16]); + assert.strictEqual(filterCount, 5, 'filterCount'); + assert.strictEqual(mapCount, 5, 'mapCount'); + } + else { + skipAssert(assert, 3); + } + }); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('forEach methods'); + + _.each(['forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should provide `value` to `iteratee`', function(assert) { + assert.expect(2); + + var args; + + func(function() { + args || (args = slice.call(arguments)); + })(['a']); + + assert.deepEqual(args, ['a']); + + args = undefined; + + func(function() { + args || (args = slice.call(arguments)); + })({ 'a': 1 }); + + assert.deepEqual(args, [1]); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.getOr'); + + (function() { + QUnit.test('should accept a `defaultValue` param', function(assert) { + assert.expect(1); + + var actual = fp.getOr('default')('path')({}); + assert.strictEqual(actual, 'default'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.gt and fp.gte'); + + _.each(['gt', 'gte'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should have `rearg` applied', function(assert) { + assert.expect(1); + + assert.strictEqual(func(2)(1), true); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.inRange'); + + (function() { + QUnit.test('should have an argument order of `start`, `end`, then `value`', function(assert) { + assert.expect(2); + + assert.strictEqual(fp.inRange(2)(4)(3), true); + assert.strictEqual(fp.inRange(-2)(-6)(-3), true); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.intersectionBy'); + + (function() { + QUnit.test('should have an argument order of `iteratee`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.intersectionBy(Math.floor, [2.1, 1.2], [2.3, 3.4]); + assert.deepEqual(actual, [2.1]); + }); + + QUnit.test('should provide the correct `iteratee` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.intersectionBy(function() { + args || (args = slice.call(arguments)); + })([2.1, 1.2], [2.3, 3.4]); + + assert.deepEqual(args, [2.3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.intersectionWith'); + + (function() { + QUnit.test('should have an argument order of `comparator`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.intersectionWith(fp.eq)([2, 1])([2, 3]); + assert.deepEqual(actual, [2]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.invoke'); + + (function() { + QUnit.test('should not accept an `args` param', function(assert) { + assert.expect(1); + + var actual = fp.invoke('toUpperCase')('a'); + assert.strictEqual(actual, 'A'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.invokeMap'); + + (function() { + QUnit.test('should not accept an `args` param', function(assert) { + assert.expect(1); + + var actual = fp.invokeMap('toUpperCase')(['a', 'b']); + assert.deepEqual(actual, ['A', 'B']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.invokeArgs'); + + (function() { + QUnit.test('should accept an `args` param', function(assert) { + assert.expect(1); + + var actual = fp.invokeArgs('concat')(['b', 'c'])('a'); + assert.strictEqual(actual, 'abc'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.invokeArgsMap'); + + (function() { + QUnit.test('should accept an `args` param', function(assert) { + assert.expect(1); + + var actual = fp.invokeArgsMap('concat')(['b', 'c'])(['a', 'A']); + assert.deepEqual(actual, ['abc', 'Abc']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.isEqualWith'); + + (function() { + QUnit.test('should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args, + iteration = 0, + objects = [{ 'a': 1 }, { 'a': 2 }], + stack = { '__data__': { '__data__': [objects, objects.slice().reverse()] } }, + expected = [1, 2, 'a', objects[0], objects[1], stack]; + + fp.isEqualWith(function() { + if (++iteration == 2) { + args = _.map(arguments, _.cloneDeep); + } + })(objects[0])(objects[1]); + + args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + + assert.deepEqual(args, expected); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.isMatchWith'); + + (function() { + QUnit.test('should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args, + objects = [{ 'a': 1 }, { 'a': 2 }], + stack = { '__data__': { '__data__': [] } }, + expected = [2, 1, 'a', objects[1], objects[0], stack]; + + fp.isMatchWith(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })(objects[0])(objects[1]); + + args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + + assert.deepEqual(args, expected); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.iteratee'); + + (function() { + QUnit.test('should return a iteratee with capped params', function(assert) { + assert.expect(1); + + var func = fp.iteratee(function(a, b, c) { return [a, b, c]; }, 3); + assert.deepEqual(func(1, 2, 3), [1, undefined, undefined]); + }); + + QUnit.test('should convert by name', function(assert) { + assert.expect(1); + + var iteratee = convert('iteratee', _.iteratee), + func = iteratee(function(a, b, c) { return [a, b, c]; }, 3); + + assert.deepEqual(func(1, 2, 3), [1, undefined, undefined]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.lt and fp.lte'); + + _.each(['lt', 'lte'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should have `rearg` applied', function(assert) { + assert.expect(1); + + assert.strictEqual(func(1)(2), true); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.mapKeys'); + + (function() { + QUnit.test('should only provide `key` to `iteratee`', function(assert) { + assert.expect(1); + + var args; + + fp.mapKeys(function() { + args || (args = slice.call(arguments)); + }, { 'a': 1 }); + + assert.deepEqual(args, ['a']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.maxBy and fp.minBy'); + + _.each(['maxBy', 'minBy'], function(methodName) { + var array = [1, 2, 3], + func = fp[methodName], + isMax = methodName == 'maxBy'; + + QUnit.test('`fp.' + methodName + '` should work with an `iteratee` argument', function(assert) { + assert.expect(1); + + var actual = func(function(num) { + return -num; + })(array); + + assert.strictEqual(actual, isMax ? 1 : 3); + }); + + QUnit.test('`fp.' + methodName + '` should provide the correct `iteratee` arguments', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = slice.call(arguments)); + })(array); + + assert.deepEqual(args, [1]); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.mergeWith'); + + (function() { + QUnit.test('should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args, + stack = { '__data__': { '__data__': [] } }, + expected = [[1, 2], [3], 'a', { 'a': [1, 2] }, { 'a': [3] }, stack]; + + fp.mergeWith(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })({ 'a': [1, 2] })({ 'a': [3] }); + + args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + + assert.deepEqual(args, expected); + }); + + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var objects = [{ 'a': [1, 2] }, { 'a': [3] }], + actual = fp.mergeWith(_.noop, objects[0], objects[1]); + + assert.deepEqual(objects[0], { 'a': [1, 2] }); + assert.deepEqual(actual, { 'a': [3, 2] }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.mergeAllWith'); + + (function() { + QUnit.test('should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args, + stack = { '__data__': { '__data__': [] } }, + expected = [[1, 2], [3], 'a', { 'a': [1, 2] }, { 'a': [3] }, stack]; + + fp.mergeAllWith(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })([{ 'a': [1, 2] }, { 'a': [3] }]); + + args[5] = _.omitBy(args[5], _.isFunction); + args[5].__data__ = _.omitBy(args[5].__data__, _.isFunction); + + assert.deepEqual(args, expected); + }); + + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var objects = [{ 'a': [1, 2] }, { 'a': [3] }], + actual = fp.mergeAllWith(_.noop, objects); + + assert.deepEqual(objects[0], { 'a': [1, 2] }); + assert.deepEqual(actual, { 'a': [3, 2] }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.mixin'); + + (function() { + var source = { 'a': _.noop }; + + QUnit.test('should mixin static methods but not prototype methods', function(assert) { + assert.expect(2); + + fp.mixin(source); + + assert.strictEqual(typeof fp.a, 'function'); + assert.notOk('a' in fp.prototype); + + delete fp.a; + delete fp.prototype.a; + }); + + QUnit.test('should not assign inherited `source` methods', function(assert) { + assert.expect(2); + + function Foo() {} + Foo.prototype.a = _.noop; + fp.mixin(new Foo); + + assert.notOk('a' in fp); + assert.notOk('a' in fp.prototype); + + delete fp.a; + delete fp.prototype.a; + }); + + QUnit.test('should not remove existing prototype methods', function(assert) { + assert.expect(2); + + var each1 = fp.each, + each2 = fp.prototype.each; + + fp.mixin({ 'each': source.a }); + + assert.strictEqual(fp.each, source.a); + assert.strictEqual(fp.prototype.each, each2); + + fp.each = each1; + fp.prototype.each = each2; + }); + + QUnit.test('should not export to the global when `source` is not an object', function(assert) { + assert.expect(2); + + var props = _.without(_.keys(_), '_'); + + _.times(2, function(index) { + fp.mixin.apply(fp, index ? [1] : []); + + assert.ok(_.every(props, function(key) { + return root[key] !== fp[key]; + })); + + _.each(props, function(key) { + if (root[key] === fp[key]) { + delete root[key]; + } + }); + }); + }); + + QUnit.test('should convert by name', function(assert) { + assert.expect(3); + + var object = { 'mixin': convert('mixin', _.mixin) }; + + function Foo() {} + Foo.mixin = object.mixin; + Foo.mixin(source); + + assert.ok('a' in Foo); + assert.notOk('a' in Foo.prototype); + + object.mixin(source); + assert.ok('a' in object); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.nthArg'); + + (function() { + QUnit.test('should return a curried function', function(assert) { + assert.expect(2); + + var func = fp.nthArg(1); + assert.strictEqual(func(1)(2), 2); + + func = fp.nthArg(-1); + assert.strictEqual(func(1), 1); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.over'); + + (function() { + QUnit.test('should not cap iteratee args', function(assert) { + assert.expect(2); + + _.each([fp.over, convert('over', _.over)], function(func) { + var over = func([Math.max, Math.min]); + assert.deepEqual(over(1, 2, 3, 4), [4, 1]); + }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.omitBy and fp.pickBy'); + + _.each(['omitBy', 'pickBy'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should provide `value` and `key` to `iteratee`', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = slice.call(arguments)); + })({ 'a': 1 }); + + assert.deepEqual(args, [1, 'a']); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('padChars methods'); + + _.each(['padChars', 'padCharsStart', 'padCharsEnd'], function(methodName) { + var func = fp[methodName], + isPad = methodName == 'padChars', + isStart = methodName == 'padCharsStart'; + + QUnit.test('`_.' + methodName + '` should truncate pad characters to fit the pad length', function(assert) { + assert.expect(1); + + if (isPad) { + assert.strictEqual(func('_-')(8)('abc'), '_-abc_-_'); + } else { + assert.strictEqual(func('_-')(6)('abc'), isStart ? '_-_abc' : 'abc_-_'); + } + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('partial methods'); + + _.each(['partial', 'partialRight'], function(methodName) { + var func = fp[methodName], + isPartial = methodName == 'partial'; + + QUnit.test('`_.' + methodName + '` should accept an `args` param', function(assert) { + assert.expect(1); + + var expected = isPartial ? [1, 2, 3] : [0, 1, 2]; + + var actual = func(function(a, b, c) { + return [a, b, c]; + })([1, 2])(isPartial ? 3 : 0); + + assert.deepEqual(actual, expected); + }); + + QUnit.test('`_.' + methodName + '` should convert by name', function(assert) { + assert.expect(2); + + var expected = isPartial ? [1, 2, 3] : [0, 1, 2], + par = convert(methodName, _[methodName]), + ph = par.placeholder; + + var actual = par(function(a, b, c) { + return [a, b, c]; + })([1, 2])(isPartial ? 3 : 0); + + assert.deepEqual(actual, expected); + + actual = par(function(a, b, c) { + return [a, b, c]; + })([ph, 2])(isPartial ? 1 : 0, isPartial ? 3 : 1); + + assert.deepEqual(actual, expected); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.propertyOf'); + + (function() { + QUnit.test('should be curried', function(assert) { + assert.expect(2); + + var object = { 'a': 1 }; + + assert.strictEqual(fp.propertyOf(object, 'a'), 1); + assert.strictEqual(fp.propertyOf(object)('a'), 1); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.pull'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var array = [1, 2, 3], + actual = fp.pull(2)(array); + + assert.deepEqual(array, [1, 2, 3]); + assert.deepEqual(actual, [1, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.pullAll'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var array = [1, 2, 3], + actual = fp.pullAll([1, 3])(array); + + assert.deepEqual(array, [1, 2, 3]); + assert.deepEqual(actual, [2]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.pullAt'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var array = [1, 2, 3], + actual = fp.pullAt([0, 2])(array); + + assert.deepEqual(array, [1, 2, 3]); + assert.deepEqual(actual, [2]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.random'); + + (function() { + var array = Array(1000); + + QUnit.test('should support a `min` and `max` argument', function(assert) { + assert.expect(1); + + var min = 5, + max = 10; + + assert.ok(_.some(array, function() { + var result = fp.random(min)(max); + return result >= min && result <= max; + })); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.range'); + + (function() { + QUnit.test('should have an argument order of `start` then `end`', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.range(1)(4), [1, 2, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.rearg'); + + (function() { + function fn(a, b, c) { + return [a, b, c]; + } + + QUnit.test('should be curried', function(assert) { + assert.expect(1); + + var rearged = fp.rearg([1, 2, 0])(fn); + assert.deepEqual(rearged('c', 'a', 'b'), ['a', 'b', 'c']); + }); + + QUnit.test('should return a curried function', function(assert) { + assert.expect(1); + + var rearged = fp.rearg([1, 2, 0], fn); + assert.deepEqual(rearged('c')('a')('b'), ['a', 'b', 'c']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('reduce methods'); + + _.each(['reduce', 'reduceRight'], function(methodName) { + var func = fp[methodName], + isReduce = methodName == 'reduce'; + + QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an array', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = slice.call(arguments)); + })(0)([1, 2, 3]); + + assert.deepEqual(args, isReduce ? [0, 1] : [0, 3]); + }); + + QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an object', function(assert) { + assert.expect(1); + + var args, + object = { 'a': 1, 'b': 2 }, + isFIFO = _.keys(object)[0] == 'a'; + + var expected = isFIFO + ? (isReduce ? [0, 1] : [0, 2]) + : (isReduce ? [0, 2] : [0, 1]); + + func(function() { + args || (args = slice.call(arguments)); + })(0)(object); + + assert.deepEqual(args, expected); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.remove'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var array = [1, 2, 3], + actual = fp.remove(fp.eq(2))(array); + + assert.deepEqual(array, [1, 2, 3]); + assert.deepEqual(actual, [1, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.restFrom'); + + (function() { + QUnit.test('should accept a `start` param', function(assert) { + assert.expect(1); + + var actual = fp.restFrom(2)(function() { + return slice.call(arguments); + })('a', 'b', 'c', 'd'); + + assert.deepEqual(actual, ['a', 'b', ['c', 'd']]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.reverse'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var array = [1, 2, 3], + actual = fp.reverse(array); + + assert.deepEqual(array, [1, 2, 3]); + assert.deepEqual(actual, [3, 2, 1]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.runInContext'); + + (function() { + QUnit.test('should return a converted lodash instance', function(assert) { + assert.expect(1); + + assert.strictEqual(typeof fp.runInContext({}).curryN, 'function'); + }); + + QUnit.test('should convert by name', function(assert) { + assert.expect(1); + + var runInContext = convert('runInContext', _.runInContext); + assert.strictEqual(typeof runInContext({}).curryN, 'function'); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.set'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var object = { 'a': { 'b': 2, 'c': 3 } }, + actual = fp.set('a.b')(3)(object); + + assert.deepEqual(object, { 'a': { 'b': 2, 'c': 3 } }); + assert.deepEqual(actual, { 'a': { 'b': 3, 'c': 3 } }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.setWith'); + + (function() { + QUnit.test('should provide the correct `customizer` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.setWith(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })('b.c')(2)({ 'a': 1 }); + + assert.deepEqual(args, [undefined, 'b', { 'a': 1 }]); + }); + + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var object = { 'a': { 'b': 2, 'c': 3 } }, + actual = fp.setWith(Object)('d.e')(4)(object); + + assert.deepEqual(object, { 'a': { 'b': 2, 'c': 3 } }); + assert.deepEqual(actual, { 'a': { 'b': 2, 'c': 3 }, 'd': { 'e': 4 } }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.spreadFrom'); + + (function() { + QUnit.test('should accept a `start` param', function(assert) { + assert.expect(1); + + var actual = fp.spreadFrom(2)(function() { + return slice.call(arguments); + })('a', 'b', ['c', 'd']); + + assert.deepEqual(actual, ['a', 'b', 'c', 'd']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('trimChars methods'); + + _.each(['trimChars', 'trimCharsStart', 'trimCharsEnd'], function(methodName, index) { + var func = fp[methodName], + parts = []; + + if (index != 2) { + parts.push('leading'); + } + if (index != 1) { + parts.push('trailing'); + } + parts = parts.join(' and '); + + QUnit.test('`_.' + methodName + '` should remove ' + parts + ' `chars`', function(assert) { + assert.expect(1); + + var string = '-_-a-b-c-_-', + expected = (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : ''); + + assert.strictEqual(func('_-')(string), expected); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.unionBy'); + + (function() { + QUnit.test('should have an argument order of `iteratee`, `array`, then `other`', function(assert) { + assert.expect(1); + + var actual = fp.unionBy(Math.floor, [2.1], [1.2, 2.3]); + assert.deepEqual(actual, [2.1, 1.2]); + }); + + QUnit.test('should provide the correct `iteratee` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.unionBy(function() { + args || (args = slice.call(arguments)); + })([2.1], [1.2, 2.3]); + + assert.deepEqual(args, [2.1]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.unionWith'); + + (function() { + QUnit.test('should have an argument order of `comparator`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.unionWith(fp.eq)([2, 1])([2, 3]); + assert.deepEqual(actual, [2, 1, 3]); + }); + + QUnit.test('should provide the correct `comparator` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.unionWith(function() { + args || (args = slice.call(arguments)); + })([2, 1])([2, 3]); + + assert.deepEqual(args, [1, 2]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.uniqBy'); + + (function() { + var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }]; + + QUnit.test('should work with an `iteratee` argument', function(assert) { + assert.expect(1); + + var expected = objects.slice(0, 3), + actual = fp.uniqBy(_.property('a'))(objects); + + assert.deepEqual(actual, expected); + }); + + QUnit.test('should provide the correct `iteratee` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.uniqBy(function() { + args || (args = slice.call(arguments)); + })(objects); + + assert.deepEqual(args, [objects[0]]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.uniqWith'); + + (function() { + QUnit.test('should have an argument order of `comparator`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.uniqWith(fp.eq)([2, 1, 2]); + assert.deepEqual(actual, [2, 1]); + }); + + QUnit.test('should provide the correct `comparator` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.uniqWith(function() { + args || (args = slice.call(arguments)); + })([2, 1, 2]); + + assert.deepEqual(args, [1, 2]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.update'); + + (function() { + QUnit.test('should not convert end of `path` to an object', function(assert) { + assert.expect(1); + + var actual = fp.update('a.b')(_.identity)({ 'a': { 'b': 1 } }); + assert.strictEqual(typeof actual.a.b, 'number'); + }); + + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var object = { 'a': { 'b': 2, 'c': 3 } }, + actual = fp.update('a.b')(square)(object); + + assert.deepEqual(object, { 'a': { 'b': 2, 'c': 3 } }); + assert.deepEqual(actual, { 'a': { 'b': 4, 'c': 3 } }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.updateWith'); + + (function() { + QUnit.test('should provide the correct `customizer` arguments', function(assert) { + var args; + + fp.updateWith(function() { + args || (args = _.map(arguments, _.cloneDeep)); + })('b.c')(_.constant(2))({ 'a': 1 }); + + assert.deepEqual(args, [undefined, 'b', { 'a': 1 }]); + }); + + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var object = { 'a': { 'b': 2, 'c': 3 } }, + actual = fp.updateWith(Object)('d.e')(_.constant(4))(object); + + assert.deepEqual(object, { 'a': { 'b': 2, 'c': 3 } }); + assert.deepEqual(actual, { 'a': { 'b': 2, 'c': 3 }, 'd': { 'e': 4 } }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.unset'); + + (function() { + QUnit.test('should not mutate values', function(assert) { + assert.expect(2); + + var object = { 'a': { 'b': 2, 'c': 3 } }, + actual = fp.unset('a.b')(object); + + assert.deepEqual(object, { 'a': { 'b': 2, 'c': 3 } }); + assert.deepEqual(actual, { 'a': { 'c': 3 } }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.xorBy'); + + (function() { + QUnit.test('should have an argument order of `iteratee`, `array`, then `other`', function(assert) { + assert.expect(1); + + var actual = fp.xorBy(Math.floor, [2.1, 1.2], [2.3, 3.4]); + assert.deepEqual(actual, [1.2, 3.4]); + }); + + QUnit.test('should provide the correct `iteratee` arguments', function(assert) { + assert.expect(1); + + var args; + + fp.xorBy(function() { + args || (args = slice.call(arguments)); + })([2.1, 1.2], [2.3, 3.4]); + + assert.deepEqual(args, [2.3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.xorWith'); + + (function() { + QUnit.test('should have an argument order of `comparator`, `array`, then `values`', function(assert) { + assert.expect(1); + + var actual = fp.xorWith(fp.eq)([2, 1])([2, 3]); + assert.deepEqual(actual, [1, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('with methods'); + + _.each(['differenceWith', 'intersectionWith', 'xorWith'], function(methodName) { + var func = fp[methodName]; + + QUnit.test('`fp.' + methodName + '` should provide the correct `comparator` arguments', function(assert) { + assert.expect(1); + + var args; + + func(function() { + args || (args = slice.call(arguments)); + })([2, 1])([2, 3]); + + assert.deepEqual(args, [2, 2]); + }); + }); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.zip'); + + (function() { + QUnit.test('should zip together two arrays', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.zip([1, 2])([3, 4]), [[1, 3], [2, 4]]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.zipAll'); + + (function() { + QUnit.test('should zip together an array of arrays', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.zipAll([[1, 2], [3, 4], [5, 6]]), [[1, 3, 5], [2, 4, 6]]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.zipObject'); + + (function() { + QUnit.test('should zip together key/value arrays into an object', function(assert) { + assert.expect(1); + + assert.deepEqual(fp.zipObject(['a', 'b'])([1, 2]), { 'a': 1, 'b': 2 }); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.module('fp.zipWith'); + + (function() { + QUnit.test('should zip arrays combining grouped elements with `iteratee`', function(assert) { + assert.expect(1); + + var array1 = [1, 2, 3], + array2 = [4, 5, 6], + actual = fp.zipWith(add)(array1)(array2); + + assert.deepEqual(actual, [5, 7, 9]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + + QUnit.config.asyncRetries = 10; + QUnit.config.hidepassed = true; + + if (!document) { + QUnit.config.noglobals = true; + QUnit.load(); + QUnit.start(); + } +}.call(this)); diff --git a/public/vendor/lodash/test/test.js b/public/vendor/lodash/test/test.js new file mode 100644 index 00000000000..decd47a0247 --- /dev/null +++ b/public/vendor/lodash/test/test.js @@ -0,0 +1,26556 @@ +;(function() { + + /** Used as a safe reference for `undefined` in pre-ES5 environments. */ + var undefined; + + /** Used to detect when a function becomes hot. */ + var HOT_COUNT = 150; + + /** Used as the size to cover large array optimizations. */ + var LARGE_ARRAY_SIZE = 200; + + /** Used as the `TypeError` message for "Functions" methods. */ + var FUNC_ERROR_TEXT = 'Expected a function'; + + /** Used as references for various `Number` constants. */ + var MAX_SAFE_INTEGER = 9007199254740991, + MAX_INTEGER = 1.7976931348623157e+308; + + /** Used as references for the maximum length and index of an array. */ + var MAX_ARRAY_LENGTH = 4294967295, + MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1; + + /** `Object#toString` result references. */ + var funcTag = '[object Function]', + numberTag = '[object Number]', + objectTag = '[object Object]'; + + /** Used as a reference to the global object. */ + var root = (typeof global == 'object' && global) || this; + + /** Used to store lodash to test for bad extensions/shims. */ + var lodashBizarro = root.lodashBizarro; + + /** Used for native method references. */ + var arrayProto = Array.prototype, + funcProto = Function.prototype, + objectProto = Object.prototype, + numberProto = Number.prototype, + stringProto = String.prototype; + + /** Method and object shortcuts. */ + var phantom = root.phantom, + process = root.process, + amd = root.define && define.amd, + argv = process && process.argv, + defineProperty = Object.defineProperty, + document = !phantom && root.document, + body = root.document && root.document.body, + create = Object.create, + fnToString = funcProto.toString, + freeze = Object.freeze, + getSymbols = Object.getOwnPropertySymbols, + identity = function(value) { return value; }, + JSON = root.JSON, + noop = function() {}, + objToString = objectProto.toString, + params = argv, + push = arrayProto.push, + realm = {}, + slice = arrayProto.slice; + + var ArrayBuffer = root.ArrayBuffer, + Buffer = root.Buffer, + Promise = root.Promise, + Map = root.Map, + Set = root.Set, + Symbol = root.Symbol, + Uint8Array = root.Uint8Array, + WeakMap = root.WeakMap, + WeakSet = root.WeakSet; + + var arrayBuffer = ArrayBuffer ? new ArrayBuffer(2) : undefined, + map = Map ? new Map : undefined, + promise = Promise ? Promise.resolve(1) : undefined, + set = Set ? new Set : undefined, + symbol = Symbol ? Symbol('a') : undefined, + weakMap = WeakMap ? new WeakMap : undefined, + weakSet = WeakSet ? new WeakSet : undefined; + + /** Math helpers. */ + var add = function(x, y) { return x + y; }, + doubled = function(n) { return n * 2; }, + isEven = function(n) { return n % 2 == 0; }, + square = function(n) { return n * n; }; + + /** Stub functions. */ + var stubA = function() { return 'a'; }, + stubB = function() { return 'b'; }, + stubC = function() { return 'c'; }; + + var stubTrue = function() { return true; }, + stubFalse = function() { return false; }; + + var stubNaN = function() { return NaN; }, + stubNull = function() { return null; }; + + var stubZero = function() { return 0; }, + stubOne = function() { return 1; }, + stubTwo = function() { return 2; }, + stubThree = function() { return 3; }, + stubFour = function() { return 4; }; + + var stubArray = function() { return []; }, + stubObject = function() { return {}; }, + stubString = function() { return ''; }; + + + + /** List of Latin Unicode letters. */ + var burredLetters = [ + // Latin-1 Supplement letters. + '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf', + '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', + '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', + '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff', + // Latin Extended-A letters. + '\u0100', '\u0101', '\u0102', '\u0103', '\u0104', '\u0105', '\u0106', '\u0107', '\u0108', '\u0109', '\u010a', '\u010b', '\u010c', '\u010d', '\u010e', '\u010f', + '\u0110', '\u0111', '\u0112', '\u0113', '\u0114', '\u0115', '\u0116', '\u0117', '\u0118', '\u0119', '\u011a', '\u011b', '\u011c', '\u011d', '\u011e', '\u011f', + '\u0120', '\u0121', '\u0122', '\u0123', '\u0124', '\u0125', '\u0126', '\u0127', '\u0128', '\u0129', '\u012a', '\u012b', '\u012c', '\u012d', '\u012e', '\u012f', + '\u0130', '\u0131', '\u0132', '\u0133', '\u0134', '\u0135', '\u0136', '\u0137', '\u0138', '\u0139', '\u013a', '\u013b', '\u013c', '\u013d', '\u013e', '\u013f', + '\u0140', '\u0141', '\u0142', '\u0143', '\u0144', '\u0145', '\u0146', '\u0147', '\u0148', '\u0149', '\u014a', '\u014b', '\u014c', '\u014d', '\u014e', '\u014f', + '\u0150', '\u0151', '\u0152', '\u0153', '\u0154', '\u0155', '\u0156', '\u0157', '\u0158', '\u0159', '\u015a', '\u015b', '\u015c', '\u015d', '\u015e', '\u015f', + '\u0160', '\u0161', '\u0162', '\u0163', '\u0164', '\u0165', '\u0166', '\u0167', '\u0168', '\u0169', '\u016a', '\u016b', '\u016c', '\u016d', '\u016e', '\u016f', + '\u0170', '\u0171', '\u0172', '\u0173', '\u0174', '\u0175', '\u0176', '\u0177', '\u0178', '\u0179', '\u017a', '\u017b', '\u017c', '\u017d', '\u017e', '\u017f' + ]; + + /** List of combining diacritical marks. */ + var comboMarks = [ + '\u0300', '\u0301', '\u0302', '\u0303', '\u0304', '\u0305', '\u0306', '\u0307', '\u0308', '\u0309', '\u030a', '\u030b', '\u030c', '\u030d', '\u030e', '\u030f', + '\u0310', '\u0311', '\u0312', '\u0313', '\u0314', '\u0315', '\u0316', '\u0317', '\u0318', '\u0319', '\u031a', '\u031b', '\u031c', '\u031d', '\u031e', '\u031f', + '\u0320', '\u0321', '\u0322', '\u0323', '\u0324', '\u0325', '\u0326', '\u0327', '\u0328', '\u0329', '\u032a', '\u032b', '\u032c', '\u032d', '\u032e', '\u032f', + '\u0330', '\u0331', '\u0332', '\u0333', '\u0334', '\u0335', '\u0336', '\u0337', '\u0338', '\u0339', '\u033a', '\u033b', '\u033c', '\u033d', '\u033e', '\u033f', + '\u0340', '\u0341', '\u0342', '\u0343', '\u0344', '\u0345', '\u0346', '\u0347', '\u0348', '\u0349', '\u034a', '\u034b', '\u034c', '\u034d', '\u034e', '\u034f', + '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357', '\u0358', '\u0359', '\u035a', '\u035b', '\u035c', '\u035d', '\u035e', '\u035f', + '\u0360', '\u0361', '\u0362', '\u0363', '\u0364', '\u0365', '\u0366', '\u0367', '\u0368', '\u0369', '\u036a', '\u036b', '\u036c', '\u036d', '\u036e', '\u036f', + '\ufe20', '\ufe21', '\ufe22', '\ufe23' + ]; + + /** List of converted Latin Unicode letters. */ + var deburredLetters = [ + // Converted Latin-1 Supplement letters. + 'A', 'A', 'A', 'A', 'A', 'A', 'Ae', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', + 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 'Th', + 'ss', 'a', 'a', 'a', 'a', 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', + 'i', 'd', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'th', 'y', + // Converted Latin Extended-A letters. + 'A', 'a', 'A', 'a', 'A', 'a', 'C', 'c', 'C', 'c', 'C', 'c', 'C', 'c', + 'D', 'd', 'D', 'd', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', + 'G', 'g', 'G', 'g', 'G', 'g', 'G', 'g', 'H', 'h', 'H', 'h', + 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'IJ', 'ij', 'J', 'j', + 'K', 'k', 'k', 'L', 'l', 'L', 'l', 'L', 'l', 'L', 'l', 'L', 'l', + 'N', 'n', 'N', 'n', 'N', 'n', "'n", 'N', 'n', + 'O', 'o', 'O', 'o', 'O', 'o', 'Oe', 'oe', + 'R', 'r', 'R', 'r', 'R', 'r', 'S', 's', 'S', 's', 'S', 's', 'S', 's', + 'T', 't', 'T', 't', 'T', 't', + 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', + 'W', 'w', 'Y', 'y', 'Y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 'ss' + ]; + + /** Used to provide falsey values to methods. */ + var falsey = [, null, undefined, false, 0, NaN, '']; + + /** Used to specify the emoji style glyph variant of characters. */ + var emojiVar = '\ufe0f'; + + /** Used to provide empty values to methods. */ + var empties = [[], {}].concat(falsey.slice(1)); + + /** Used to test error objects. */ + var errors = [ + new Error, + new EvalError, + new RangeError, + new ReferenceError, + new SyntaxError, + new TypeError, + new URIError + ]; + + /** List of fitzpatrick modifiers. */ + var fitzModifiers = [ + '\ud83c\udffb', + '\ud83c\udffc', + '\ud83c\udffd', + '\ud83c\udffe', + '\ud83c\udfff' + ]; + + /** Used to provide primitive values to methods. */ + var primitives = [null, undefined, false, true, 1, NaN, 'a']; + + /** Used to check whether methods support typed arrays. */ + var typedArrays = [ + 'Float32Array', + 'Float64Array', + 'Int8Array', + 'Int16Array', + 'Int32Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Uint16Array', + 'Uint32Array' + ]; + + /** Used to check whether methods support array views. */ + var arrayViews = typedArrays.concat('DataView'); + + /** The file path of the lodash file to test. */ + var filePath = (function() { + var min = 2, + result = params || []; + + if (phantom) { + min = 0; + result = params = phantom.args || require('system').args; + } + var last = result[result.length - 1]; + result = (result.length > min && !/test(?:\.js)?$/.test(last)) ? last : '../lodash.js'; + + if (!amd) { + try { + result = require('fs').realpathSync(result); + } catch (e) {} + + try { + result = require.resolve(result); + } catch (e) {} + } + return result; + }()); + + /** The `ui` object. */ + var ui = root.ui || (root.ui = { + 'buildPath': filePath, + 'loaderPath': '', + 'isModularize': /\b(?:amd|commonjs|es|node|npm|(index|main)\.js)\b/.test(filePath), + 'isStrict': /\bes\b/.test(filePath) || 'default' in require(filePath), + 'urlParams': {} + }); + + /** The basename of the lodash file to test. */ + var basename = /[\w.-]+$/.exec(filePath)[0]; + + /** Used to indicate testing a modularized build. */ + var isModularize = ui.isModularize; + + /** Detect if testing `npm` modules. */ + var isNpm = isModularize && /\bnpm\b/.test([ui.buildPath, ui.urlParams.build]); + + /** Detect if running in PhantomJS. */ + var isPhantom = phantom || (typeof callPhantom == 'function'); + + /** Detect if lodash is in strict mode. */ + var isStrict = ui.isStrict; + + /*--------------------------------------------------------------------------*/ + + // Leak to avoid sporadic `noglobals` fails on Edge in Sauce Labs. + root.msWDfn = undefined; + + // Assign `setTimeout` to itself to avoid being flagged as a leak. + setProperty(root, 'setTimeout', setTimeout); + + // Exit early if going to run tests in a PhantomJS web page. + if (phantom && isModularize) { + var page = require('webpage').create(); + + page.onCallback = function(details) { + var coverage = details.coverage; + if (coverage) { + var fs = require('fs'), + cwd = fs.workingDirectory, + sep = fs.separator; + + fs.write([cwd, 'coverage', 'coverage.json'].join(sep), JSON.stringify(coverage)); + } + phantom.exit(details.failed ? 1 : 0); + }; + + page.onConsoleMessage = function(message) { + console.log(message); + }; + + page.onInitialized = function() { + page.evaluate(function() { + document.addEventListener('DOMContentLoaded', function() { + QUnit.done(function(details) { + details.coverage = window.__coverage__; + callPhantom(details); + }); + }); + }); + }; + + page.open(filePath, function(status) { + if (status != 'success') { + console.log('PhantomJS failed to load page: ' + filePath); + phantom.exit(1); + } + }); + + console.log('test.js invoked with arguments: ' + JSON.stringify(slice.call(params))); + return; + } + + /*--------------------------------------------------------------------------*/ + + /** Used to test Web Workers. */ + var Worker = !(ui.isForeign || ui.isSauceLabs || isModularize) && + (document && document.origin != 'null') && root.Worker; + + /** Used to test host objects in IE. */ + try { + var xml = new ActiveXObject('Microsoft.XMLDOM'); + } catch (e) {} + + /** Poison the free variable `root` in Node.js */ + try { + defineProperty(global.root, 'root', { + 'configurable': false, + 'enumerable': false, + 'get': function() { throw new ReferenceError; } + }); + } catch (e) {} + + /** Load QUnit and extras. */ + var QUnit = root.QUnit || require('qunit-extras'); + + /** Load stable Lodash. */ + var lodashStable = root.lodashStable; + if (!lodashStable) { + try { + lodashStable = interopRequire('../node_modules/lodash/lodash.js'); + } catch (e) { + console.log('Error: The stable lodash dev dependency should be at least a version behind master branch.'); + return; + } + lodashStable = lodashStable.noConflict(); + } + + /** The `lodash` function to test. */ + var _ = root._ || (root._ = interopRequire(filePath)); + + /** Used to test pseudo private map caches. */ + var mapCaches = (function() { + var MapCache = _.memoize.Cache; + var result = { + 'Hash': new MapCache().__data__.hash.constructor, + 'MapCache': MapCache + }; + _.isMatchWith({ 'a': 1 }, { 'a': 1 }, function() { + var stack = lodashStable.last(arguments); + result.ListCache = stack.__data__.constructor; + result.Stack = stack.constructor; + }); + return result; + }()); + + /** Used to detect instrumented istanbul code coverage runs. */ + var coverage = root.__coverage__ || root[lodashStable.find(lodashStable.keys(root), function(key) { + return /^(?:\$\$cov_\d+\$\$)$/.test(key); + })]; + + /** Used to test generator functions. */ + var generator = lodashStable.attempt(function() { + return Function('return function*(){}'); + }); + + /** Used to restore the `_` reference. */ + var oldDash = root._; + + /** + * Used to check for problems removing whitespace. For a whitespace reference, + * see [V8's unit test](https://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/whitespaces.js). + */ + var whitespace = lodashStable.filter([ + // Basic whitespace characters. + ' ', '\t', '\x0b', '\f', '\xa0', '\ufeff', + + // Line terminators. + '\n', '\r', '\u2028', '\u2029', + + // Unicode category "Zs" space separators. + '\u1680', '\u180e', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', + '\u2006', '\u2007', '\u2008', '\u2009', '\u200a', '\u202f', '\u205f', '\u3000' + ], + function(chr) { return /\s/.exec(chr); }) + .join(''); + + /** + * Creates a custom error object. + * + * @private + * @constructor + * @param {string} message The error message. + */ + function CustomError(message) { + this.name = 'CustomError'; + this.message = message; + } + + CustomError.prototype = lodashStable.create(Error.prototype, { + 'constructor': CustomError + }); + + /** + * Removes all own enumerable string keyed properties from a given object. + * + * @private + * @param {Object} object The object to empty. + */ + function emptyObject(object) { + lodashStable.forOwn(object, function(value, key, object) { + delete object[key]; + }); + } + + /** + * Extracts the unwrapped value from its wrapper. + * + * @private + * @param {Object} wrapper The wrapper to unwrap. + * @returns {*} Returns the unwrapped value. + */ + function getUnwrappedValue(wrapper) { + var index = -1, + actions = wrapper.__actions__, + length = actions.length, + result = wrapper.__wrapped__; + + while (++index < length) { + var args = [result], + action = actions[index]; + + push.apply(args, action.args); + result = action.func.apply(action.thisArg, args); + } + return result; + } + + /** + * Loads the module of `id`. If the module has an `exports.default`, the + * exported default value is returned as the resolved module. + * + * @private + * @param {string} id The identifier of the module to resolve. + * @returns {*} Returns the resolved module. + */ + function interopRequire(id) { + var result = require(id); + return 'default' in result ? result['default'] : result; + } + + /** + * Sets a non-enumerable property value on `object`. + * + * Note: This function is used to avoid a bug in older versions of V8 where + * overwriting non-enumerable built-ins makes them enumerable. + * See https://code.google.com/p/v8/issues/detail?id=1623 + * + * @private + * @param {Object} object The object modify. + * @param {string} key The name of the property to set. + * @param {*} value The property value. + */ + function setProperty(object, key, value) { + try { + defineProperty(object, key, { + 'configurable': true, + 'enumerable': false, + 'writable': true, + 'value': value + }); + } catch (e) { + object[key] = value; + } + return object; + } + + /** + * Skips a given number of tests with a passing result. + * + * @private + * @param {Object} assert The QUnit assert object. + * @param {number} [count=1] The number of tests to skip. + */ + function skipAssert(assert, count) { + count || (count = 1); + while (count--) { + assert.ok(true, 'test skipped'); + } + } + + /*--------------------------------------------------------------------------*/ + + // Add bizarro values. + (function() { + if (document || (typeof require != 'function')) { + return; + } + var nativeString = fnToString.call(toString), + reToString = /toString/g; + + function createToString(funcName) { + return lodashStable.constant(nativeString.replace(reToString, funcName)); + } + + // Allow bypassing native checks. + setProperty(funcProto, 'toString', function wrapper() { + setProperty(funcProto, 'toString', fnToString); + var result = lodashStable.has(this, 'toString') ? this.toString() : fnToString.call(this); + setProperty(funcProto, 'toString', wrapper); + return result; + }); + + // Add prototype extensions. + funcProto._method = noop; + + // Set bad shims. + setProperty(Object, 'create', (function() { + function object() {} + return function(prototype) { + if (lodashStable.isObject(prototype)) { + object.prototype = prototype; + var result = new object; + object.prototype = undefined; + } + return result || {}; + }; + }())); + + setProperty(Object, 'getOwnPropertySymbols', undefined); + + var _propertyIsEnumerable = objectProto.propertyIsEnumerable; + setProperty(objectProto, 'propertyIsEnumerable', function(key) { + return !(key == 'valueOf' && this && this.valueOf === 1) && _propertyIsEnumerable.call(this, key); + }); + + if (Buffer) { + defineProperty(root, 'Buffer', { + 'configurable': true, + 'enumerable': true, + 'get': function get() { + var caller = get.caller, + name = caller ? caller.name : ''; + + if (!(name == 'runInContext' || name.length == 1 || /\b_\.isBuffer\b/.test(caller))) { + return Buffer; + } + } + }); + } + if (Map) { + setProperty(root, 'Map', (function() { + var count = 0; + return function() { + if (count++) { + return new Map; + } + setProperty(root, 'Map', Map); + return {}; + }; + }())); + + setProperty(root.Map, 'toString', createToString('Map')); + } + setProperty(root, 'Promise', noop); + setProperty(root, 'Set', noop); + setProperty(root, 'Symbol', undefined); + setProperty(root, 'WeakMap', noop); + + // Fake `WinRTError`. + setProperty(root, 'WinRTError', Error); + + // Clear cache so lodash can be reloaded. + emptyObject(require.cache); + + // Load lodash and expose it to the bad extensions/shims. + lodashBizarro = interopRequire(filePath); + root._ = oldDash; + + // Restore built-in methods. + setProperty(Object, 'create', create); + setProperty(objectProto, 'propertyIsEnumerable', _propertyIsEnumerable); + setProperty(root, 'Buffer', Buffer); + + if (getSymbols) { + Object.getOwnPropertySymbols = getSymbols; + } else { + delete Object.getOwnPropertySymbols; + } + if (Map) { + setProperty(root, 'Map', Map); + } else { + delete root.Map; + } + if (Promise) { + setProperty(root, 'Promise', Promise); + } else { + delete root.Promise; + } + if (Set) { + setProperty(root, 'Set', Set); + } else { + delete root.Set; + } + if (Symbol) { + setProperty(root, 'Symbol', Symbol); + } else { + delete root.Symbol; + } + if (WeakMap) { + setProperty(root, 'WeakMap', WeakMap); + } else { + delete root.WeakMap; + } + delete root.WinRTError; + delete funcProto._method; + }()); + + // Add other realm values from the `vm` module. + lodashStable.attempt(function() { + lodashStable.assign(realm, require('vm').runInNewContext([ + '(function() {', + ' var noop = function() {},', + ' root = this;', + '', + ' var object = {', + " 'ArrayBuffer': root.ArrayBuffer,", + " 'arguments': (function() { return arguments; }(1, 2, 3)),", + " 'array': [1],", + " 'arrayBuffer': root.ArrayBuffer ? new root.ArrayBuffer : undefined,", + " 'boolean': Object(false),", + " 'date': new Date,", + " 'errors': [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError],", + " 'function': noop,", + " 'map': root.Map ? new root.Map : undefined,", + " 'nan': NaN,", + " 'null': null,", + " 'number': Object(0),", + " 'object': { 'a': 1 },", + " 'promise': root.Promise ? Promise.resolve(1) : undefined,", + " 'regexp': /x/,", + " 'set': root.Set ? new root.Set : undefined,", + " 'string': Object('a'),", + " 'symbol': root.Symbol ? root.Symbol() : undefined,", + " 'undefined': undefined,", + " 'weakMap': root.WeakMap ? new root.WeakMap : undefined,", + " 'weakSet': root.WeakSet ? new root.WeakSet : undefined", + ' };', + '', + " ['" + arrayViews.join("', '") + "'].forEach(function(type) {", + ' var Ctor = root[type]', + ' object[type] = Ctor;', + ' object[type.toLowerCase()] = Ctor ? new Ctor(new ArrayBuffer(24)) : undefined;', + ' });', + '', + ' return object;', + '}())' + ].join('\n'))); + }); + + // Add other realm values from an iframe. + lodashStable.attempt(function() { + _._realm = realm; + + var iframe = document.createElement('iframe'); + iframe.frameBorder = iframe.height = iframe.width = 0; + body.appendChild(iframe); + + var idoc = (idoc = iframe.contentDocument || iframe.contentWindow).document || idoc; + idoc.write([ + ' + + + + + + + + + + + diff --git a/public/vendor/lodash/vendor/backbone/LICENSE b/public/vendor/lodash/vendor/backbone/LICENSE new file mode 100644 index 00000000000..02c89b26081 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2010-2016 Jeremy Ashkenas, DocumentCloud + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/public/vendor/lodash/vendor/backbone/backbone.js b/public/vendor/lodash/vendor/backbone/backbone.js new file mode 100644 index 00000000000..02722ac8110 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/backbone.js @@ -0,0 +1,1946 @@ +// Backbone.js 1.3.3 + +// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(factory) { + + // Establish the root object, `window` (`self`) in the browser, or `global` on the server. + // We use `self` instead of `window` for `WebWorker` support. + var root = (typeof self == 'object' && self.self === self && self) || + (typeof global == 'object' && global.global === global && global); + + // Set up Backbone appropriately for the environment. Start with AMD. + if (typeof define === 'function' && define.amd) { + define(['underscore', 'jquery', 'exports'], function(_, $, exports) { + // Export global even in AMD case in case this script is loaded with + // others that may still expect a global Backbone. + root.Backbone = factory(root, exports, _, $); + }); + + // Next for Node.js or CommonJS. jQuery may not be needed as a module. + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'), $; + try { $ = require('jquery'); } catch (e) {} + factory(root, exports, _, $); + + // Finally, as a browser global. + } else { + root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); + } + +})(function(root, Backbone, _, $) { + + // Initial Setup + // ------------- + + // Save the previous value of the `Backbone` variable, so that it can be + // restored later on, if `noConflict` is used. + var previousBackbone = root.Backbone; + + // Create a local reference to a common array method we'll want to use later. + var slice = Array.prototype.slice; + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '1.3.3'; + + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = $; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option + // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and + // set a `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... this will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Proxy Backbone class methods to Underscore functions, wrapping the model's + // `attributes` object or collection's `models` array behind the scenes. + // + // collection.filter(function(model) { return model.get('age') > 10 }); + // collection.each(this.addView); + // + // `Function#apply` can be slow so we use the method's arg count, if we know it. + var addMethod = function(length, method, attribute) { + switch (length) { + case 1: return function() { + return _[method](this[attribute]); + }; + case 2: return function(value) { + return _[method](this[attribute], value); + }; + case 3: return function(iteratee, context) { + return _[method](this[attribute], cb(iteratee, this), context); + }; + case 4: return function(iteratee, defaultVal, context) { + return _[method](this[attribute], cb(iteratee, this), defaultVal, context); + }; + default: return function() { + var args = slice.call(arguments); + args.unshift(this[attribute]); + return _[method].apply(_, args); + }; + } + }; + var addUnderscoreMethods = function(Class, methods, attribute) { + _.each(methods, function(length, method) { + if (_[method]) Class.prototype[method] = addMethod(length, method, attribute); + }); + }; + + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. + var cb = function(iteratee, instance) { + if (_.isFunction(iteratee)) return iteratee; + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; + return iteratee; + }; + var modelMatcher = function(attrs) { + var matcher = _.matches(attrs); + return function(model) { + return matcher(model.attributes); + }; + }; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // a custom event channel. You may bind a callback to an event with `on` or + // remove with `off`; `trigger`-ing an event fires all callbacks in + // succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + var Events = Backbone.Events = {}; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Iterates over the standard `event, callback` (as well as the fancy multiple + // space-separated events `"change blur", callback` and jQuery-style event + // maps `{event: callback}`). + var eventsApi = function(iteratee, events, name, callback, opts) { + var i = 0, names; + if (name && typeof name === 'object') { + // Handle event maps. + if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; + for (names = _.keys(name); i < names.length ; i++) { + events = eventsApi(iteratee, events, names[i], name[names[i]], opts); + } + } else if (name && eventSplitter.test(name)) { + // Handle space-separated event names by delegating them individually. + for (names = name.split(eventSplitter); i < names.length; i++) { + events = iteratee(events, names[i], callback, opts); + } + } else { + // Finally, standard events. + events = iteratee(events, name, callback, opts); + } + return events; + }; + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + Events.on = function(name, callback, context) { + return internalOn(this, name, callback, context); + }; + + // Guard the `listening` argument from the public API. + var internalOn = function(obj, name, callback, context, listening) { + obj._events = eventsApi(onApi, obj._events || {}, name, callback, { + context: context, + ctx: obj, + listening: listening + }); + + if (listening) { + var listeners = obj._listeners || (obj._listeners = {}); + listeners[listening.id] = listening; + } + + return obj; + }; + + // Inversion-of-control versions of `on`. Tell *this* object to listen to + // an event in another object... keeping track of what it's listening to + // for easier unbinding later. + Events.listenTo = function(obj, name, callback) { + if (!obj) return this; + var id = obj._listenId || (obj._listenId = _.uniqueId('l')); + var listeningTo = this._listeningTo || (this._listeningTo = {}); + var listening = listeningTo[id]; + + // This object is not listening to any other events on `obj` yet. + // Setup the necessary references to track the listening callbacks. + if (!listening) { + var thisId = this._listenId || (this._listenId = _.uniqueId('l')); + listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0}; + } + + // Bind callbacks on obj, and keep track of them on listening. + internalOn(obj, name, callback, this, listening); + return this; + }; + + // The reducing API that adds a callback to the `events` object. + var onApi = function(events, name, callback, options) { + if (callback) { + var handlers = events[name] || (events[name] = []); + var context = options.context, ctx = options.ctx, listening = options.listening; + if (listening) listening.count++; + + handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); + } + return events; + }; + + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + Events.off = function(name, callback, context) { + if (!this._events) return this; + this._events = eventsApi(offApi, this._events, name, callback, { + context: context, + listeners: this._listeners + }); + return this; + }; + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + Events.stopListening = function(obj, name, callback) { + var listeningTo = this._listeningTo; + if (!listeningTo) return this; + + var ids = obj ? [obj._listenId] : _.keys(listeningTo); + + for (var i = 0; i < ids.length; i++) { + var listening = listeningTo[ids[i]]; + + // If listening doesn't exist, this object is not currently + // listening to obj. Break out early. + if (!listening) break; + + listening.obj.off(name, callback, this); + } + + return this; + }; + + // The reducing API that removes a callback from the `events` object. + var offApi = function(events, name, callback, options) { + if (!events) return; + + var i = 0, listening; + var context = options.context, listeners = options.listeners; + + // Delete all events listeners and "drop" events. + if (!name && !callback && !context) { + var ids = _.keys(listeners); + for (; i < ids.length; i++) { + listening = listeners[ids[i]]; + delete listeners[listening.id]; + delete listening.listeningTo[listening.objId]; + } + return; + } + + var names = name ? [name] : _.keys(events); + for (; i < names.length; i++) { + name = names[i]; + var handlers = events[name]; + + // Bail out if there are no events stored. + if (!handlers) break; + + // Replace events if there are any remaining. Otherwise, clean up. + var remaining = []; + for (var j = 0; j < handlers.length; j++) { + var handler = handlers[j]; + if ( + callback && callback !== handler.callback && + callback !== handler.callback._callback || + context && context !== handler.context + ) { + remaining.push(handler); + } else { + listening = handler.listening; + if (listening && --listening.count === 0) { + delete listeners[listening.id]; + delete listening.listeningTo[listening.objId]; + } + } + } + + // Update tail event if the list has any events. Otherwise, clean up. + if (remaining.length) { + events[name] = remaining; + } else { + delete events[name]; + } + } + return events; + }; + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, its listener will be removed. If multiple events + // are passed in using the space-separated syntax, the handler will fire + // once for each event, not once for a combination of all events. + Events.once = function(name, callback, context) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); + if (typeof name === 'string' && context == null) callback = void 0; + return this.on(events, callback, context); + }; + + // Inversion-of-control versions of `once`. + Events.listenToOnce = function(obj, name, callback) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj)); + return this.listenTo(obj, events); + }; + + // Reduces the event callbacks into a map of `{event: onceWrapper}`. + // `offer` unbinds the `onceWrapper` after it has been called. + var onceMap = function(map, name, callback, offer) { + if (callback) { + var once = map[name] = _.once(function() { + offer(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + } + return map; + }; + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + Events.trigger = function(name) { + if (!this._events) return this; + + var length = Math.max(0, arguments.length - 1); + var args = Array(length); + for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; + + eventsApi(triggerApi, this._events, name, void 0, args); + return this; + }; + + // Handles triggering the appropriate event callbacks. + var triggerApi = function(objEvents, name, callback, args) { + if (objEvents) { + var events = objEvents[name]; + var allEvents = objEvents.all; + if (events && allEvents) allEvents = allEvents.slice(); + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, [name].concat(args)); + } + return objEvents; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; + } + }; + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + + // Backbone.Model + // -------------- + + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var attrs = attributes || {}; + options || (options = {}); + this.preinitialize.apply(this, arguments); + this.cid = _.uniqueId(this.cidPrefix); + this.attributes = {}; + if (options.collection) this.collection = options.collection; + if (options.parse) attrs = this.parse(attrs, options) || {}; + var defaults = _.result(this, 'defaults'); + attrs = _.defaults(_.extend({}, defaults, attrs), defaults); + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // The value returned during the last failed validation. + validationError: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // The prefix is used to create the client id which is used to identify models locally. + // You may want to override this if you're experiencing name clashes with model ids. + cidPrefix: 'c', + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the Model. + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Return a copy of the model's `attributes` object. + toJSON: function(options) { + return _.clone(this.attributes); + }, + + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Get the value of an attribute. + get: function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape: function(attr) { + return _.escape(this.get(attr)); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Special-cased proxy to underscore's `_.matches` method. + matches: function(attrs) { + return !!_.iteratee(attrs, this)(this.attributes); + }, + + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + var unset = options.unset; + var silent = options.silent; + var changes = []; + var changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + + var current = this.attributes; + var changed = this.changed; + var prev = this._previousAttributes; + + // For each `set` attribute, update or delete the current value. + for (var attr in attrs) { + val = attrs[attr]; + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { + changed[attr] = val; + } else { + delete changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Update the `id`. + if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0; i < changes.length; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. + unset: function(attr, options) { + return this.set(attr, void 0, _.extend({}, options, {unset: true})); + }, + + // Clear all attributes on the model, firing `"change"`. + clear: function(options) { + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (attr == null) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var old = this._changing ? this._previousAttributes : this.attributes; + var changed = {}; + var hasChanged; + for (var attr in diff) { + var val = diff[attr]; + if (_.isEqual(old[attr], val)) continue; + changed[attr] = val; + hasChanged = true; + } + return hasChanged ? changed : false; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (attr == null || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + // Fetch the model from the server, merging the response with the model's + // local attributes. Any changed attributes will trigger a "change" event. + fetch: function(options) { + options = _.extend({parse: true}, options); + var model = this; + var success = options.success; + options.success = function(resp) { + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (!model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save: function(key, val, options) { + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options = _.extend({validate: true, parse: true}, options); + var wait = options.wait; + + // If we're not waiting and attributes exist, save acts as + // `set(attr).save(null, opts)` with validation. Otherwise, check if + // the model will be valid when the attributes, if any, are set. + if (attrs && !wait) { + if (!this.set(attrs, options)) return false; + } else if (!this._validate(attrs, options)) { + return false; + } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. + var model = this; + var success = options.success; + var attributes = this.attributes; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); + if (serverAttrs && !model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + + // Set temporary attributes if `{wait: true}` to properly find new ids. + if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); + + var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); + if (method === 'patch' && !options.attrs) options.attrs = attrs; + var xhr = this.sync(method, this, options); + + // Restore attributes. + this.attributes = attributes; + + return xhr; + }, + + // Destroy this model on the server if it was already persisted. + // Optimistically removes the model from its collection, if it has one. + // If `wait: true` is passed, waits for the server to respond before removal. + destroy: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + var wait = options.wait; + + var destroy = function() { + model.stopListening(); + model.trigger('destroy', model, model.collection, options); + }; + + options.success = function(resp) { + if (wait) destroy(); + if (success) success.call(options.context, model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); + }; + + var xhr = false; + if (this.isNew()) { + _.defer(options.success); + } else { + wrapError(this, options); + xhr = this.sync('delete', this, options); + } + if (!wait) destroy(); + return xhr; + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url: function() { + var base = + _.result(this, 'urlRoot') || + _.result(this.collection, 'url') || + urlError(); + if (this.isNew()) return base; + var id = this.get(this.idAttribute); + return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse: function(resp, options) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone: function() { + return new this.constructor(this.attributes); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew: function() { + return !this.has(this.idAttribute); + }, + + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend({}, options, {validate: true})); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. + _validate: function(attrs, options) { + if (!options.validate || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validationError = this.validate(attrs, options) || null; + if (!error) return true; + this.trigger('invalid', this, error, _.extend(options, {validationError: error})); + return false; + } + + }); + + // Underscore methods that we want to implement on the Model, mapped to the + // number of arguments they take. + var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, + omit: 0, chain: 1, isEmpty: 1}; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + addUnderscoreMethods(Model, modelMethods, 'attributes'); + + // Backbone.Collection + // ------------------- + + // If models tend to represent a single row of data, a Backbone Collection is + // more analogous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + var Collection = Backbone.Collection = function(models, options) { + options || (options = {}); + this.preinitialize.apply(this, arguments); + if (options.model) this.model = options.model; + if (options.comparator !== void 0) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, _.extend({silent: true}, options)); + }; + + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, remove: false}; + + // Splices `insert` into `array` at index `at`. + var splice = function(array, insert, at) { + at = Math.min(Math.max(at, 0), array.length); + var tail = Array(array.length - at); + var length = insert.length; + var i; + for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; + for (i = 0; i < length; i++) array[i + at] = insert[i]; + for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; + }; + + // Define the Collection's inheritable methods. + _.extend(Collection.prototype, Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model: Model, + + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the Collection. + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON: function(options) { + return this.map(function(model) { return model.toJSON(options); }); + }, + + // Proxy `Backbone.sync` by default. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Add a model, or list of models to the set. `models` may be Backbone + // Models or raw JavaScript objects to be converted to Models, or any + // combination of the two. + add: function(models, options) { + return this.set(models, _.extend({merge: false}, options, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { + options = _.extend({}, options); + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + var removed = this._removeModels(models, options); + if (!options.silent && removed.length) { + options.changes = {added: [], merged: [], removed: removed}; + this.trigger('update', this, options); + } + return singular ? removed[0] : removed; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + if (models == null) return; + + options = _.extend({}, setOptions, options); + if (options.parse && !this._isModel(models)) { + models = this.parse(models, options) || []; + } + + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + + var at = options.at; + if (at != null) at = +at; + if (at > this.length) at = this.length; + if (at < 0) at += this.length + 1; + + var set = []; + var toAdd = []; + var toMerge = []; + var toRemove = []; + var modelMap = {}; + + var add = options.add; + var merge = options.merge; + var remove = options.remove; + + var sort = false; + var sortable = this.comparator && at == null && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + var model, i; + for (i = 0; i < models.length; i++) { + model = models[i]; + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + var existing = this.get(model); + if (existing) { + if (merge && model !== existing) { + var attrs = this._isModel(model) ? model.attributes : model; + if (options.parse) attrs = existing.parse(attrs, options); + existing.set(attrs, options); + toMerge.push(existing); + if (sortable && !sort) sort = existing.hasChanged(sortAttr); + } + if (!modelMap[existing.cid]) { + modelMap[existing.cid] = true; + set.push(existing); + } + models[i] = existing; + + // If this is a new, valid model, push it to the `toAdd` list. + } else if (add) { + model = models[i] = this._prepareModel(model, options); + if (model) { + toAdd.push(model); + this._addReference(model, options); + modelMap[model.cid] = true; + set.push(model); + } + } + } + + // Remove stale models. + if (remove) { + for (i = 0; i < this.length; i++) { + model = this.models[i]; + if (!modelMap[model.cid]) toRemove.push(model); + } + if (toRemove.length) this._removeModels(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + var orderChanged = false; + var replace = !sortable && add && remove; + if (set.length && replace) { + orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { + return m !== set[index]; + }); + this.models.length = 0; + splice(this.models, set, 0); + this.length = this.models.length; + } else if (toAdd.length) { + if (sortable) sort = true; + splice(this.models, toAdd, at == null ? this.length : at); + this.length = this.models.length; + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + // Unless silenced, it's time to fire all appropriate add/sort/update events. + if (!options.silent) { + for (i = 0; i < toAdd.length; i++) { + if (at != null) options.index = at + i; + model = toAdd[i]; + model.trigger('add', model, this, options); + } + if (sort || orderChanged) this.trigger('sort', this, options); + if (toAdd.length || toRemove.length || toMerge.length) { + options.changes = { + added: toAdd, + removed: toRemove, + merged: toMerge + }; + this.trigger('update', this, options); + } + } + + // Return the added (or merged) model (or models). + return singular ? models[0] : models; + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options = options ? _.clone(options) : {}; + for (var i = 0; i < this.models.length; i++) { + this._removeReference(this.models[i], options); + } + options.previousModels = this.models; + this._reset(); + models = this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return models; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + return this.add(model, _.extend({at: this.length}, options)); + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + return this.remove(model, options); + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + return this.add(model, _.extend({at: 0}, options)); + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + return this.remove(model, options); + }, + + // Slice out a sub-array of models from the collection. + slice: function() { + return slice.apply(this.models, arguments); + }, + + // Get a model from the set by id, cid, model object with id or cid + // properties, or an attributes object that is transformed through modelId. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj] || + this._byId[this.modelId(obj.attributes || obj)] || + obj.cid && this._byId[obj.cid]; + }, + + // Returns `true` if the model is in the collection. + has: function(obj) { + return this.get(obj) != null; + }, + + // Get the model at the given index. + at: function(index) { + if (index < 0) index += this.length; + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + return this[first ? 'find' : 'filter'](attrs); + }, + + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + + // Force the collection to re-sort itself. You don't need to call this under + // normal circumstances, as the set will maintain sort order as each item + // is added. + sort: function(options) { + var comparator = this.comparator; + if (!comparator) throw new Error('Cannot sort a set without a comparator'); + options || (options = {}); + + var length = comparator.length; + if (_.isFunction(comparator)) comparator = _.bind(comparator, this); + + // Run sort based on type of `comparator`. + if (length === 1 || _.isString(comparator)) { + this.models = this.sortBy(comparator); + } else { + this.models.sort(comparator); + } + if (!options.silent) this.trigger('sort', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return this.map(attr + ''); + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. + fetch: function(options) { + options = _.extend({parse: true}, options); + var success = options.success; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success.call(options.context, collection, resp, options); + collection.trigger('sync', collection, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Create a new instance of a model in this collection. Add the model to the + // collection immediately, unless `wait: true` is passed, in which case we + // wait for the server to agree. + create: function(model, options) { + options = options ? _.clone(options) : {}; + var wait = options.wait; + model = this._prepareModel(model, options); + if (!model) return false; + if (!wait) this.add(model, options); + var collection = this; + var success = options.success; + options.success = function(m, resp, callbackOpts) { + if (wait) collection.add(m, callbackOpts); + if (success) success.call(callbackOpts.context, m, resp, callbackOpts); + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse: function(resp, options) { + return resp; + }, + + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models, { + model: this.model, + comparator: this.comparator + }); + }, + + // Define how to uniquely identify models in the collection. + modelId: function(attrs) { + return attrs[this.model.prototype.idAttribute || 'id']; + }, + + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { + this.length = 0; + this.models = []; + this._byId = {}; + }, + + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (this._isModel(attrs)) { + if (!attrs.collection) attrs.collection = this; + return attrs; + } + options = options ? _.clone(options) : {}; + options.collection = this; + var model = new this.model(attrs, options); + if (!model.validationError) return model; + this.trigger('invalid', this, model.validationError, options); + return false; + }, + + // Internal method called by both remove and set. + _removeModels: function(models, options) { + var removed = []; + for (var i = 0; i < models.length; i++) { + var model = this.get(models[i]); + if (!model) continue; + + var index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + + // Remove references before triggering 'remove' event to prevent an + // infinite loop. #3693 + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + + removed.push(model); + this._removeReference(model, options); + } + return removed; + }, + + // Method for checking whether an object should be considered a model for + // the purposes of adding to the collection. + _isModel: function(model) { + return model instanceof Model; + }, + + // Internal method to create a model's ties to a collection. + _addReference: function(model, options) { + this._byId[model.cid] = model; + var id = this.modelId(model.attributes); + if (id != null) this._byId[id] = model; + model.on('all', this._onModelEvent, this); + }, + + // Internal method to sever a model's ties to a collection. + _removeReference: function(model, options) { + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + if (this === model.collection) delete model.collection; + model.off('all', this._onModelEvent, this); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent: function(event, model, collection, options) { + if (model) { + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); + if (event === 'change') { + var prevId = this.modelId(model.previousAttributes()); + var id = this.modelId(model.attributes); + if (prevId !== id) { + if (prevId != null) delete this._byId[prevId]; + if (id != null) this._byId[id] = model; + } + } + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, + foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, + head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, + without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, + sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; + + // Mix in each Underscore method as a proxy to `Collection#models`. + addUnderscoreMethods(Collection, collectionMethods, 'models'); + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + this.preinitialize.apply(this, arguments); + _.extend(this, _.pick(options, viewOptions)); + this._ensureElement(); + this.initialize.apply(this, arguments); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be set as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be preferred to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the View + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this._removeElement(); + this.stopListening(); + return this; + }, + + // Remove this view's element from the document and all event listeners + // attached to it. Exposed for subclasses using an alternative DOM + // manipulation API. + _removeElement: function() { + this.$el.remove(); + }, + + // Change the view's element (`this.el` property) and re-delegate the + // view's events on the new element. + setElement: function(element) { + this.undelegateEvents(); + this._setElement(element); + this.delegateEvents(); + return this; + }, + + // Creates the `this.el` and `this.$el` references for this view using the + // given `el`. `el` can be a CSS selector or an HTML string, a jQuery + // context or an element. Subclasses can override this to utilize an + // alternative DOM manipulation API and are only required to set the + // `this.el` property. + _setElement: function(el) { + this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); + this.el = this.$el[0]; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + delegateEvents: function(events) { + events || (events = _.result(this, 'events')); + if (!events) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[method]; + if (!method) continue; + var match = key.match(delegateEventSplitter); + this.delegate(match[1], match[2], _.bind(method, this)); + } + return this; + }, + + // Add a single event listener to the view's element (or a child element + // using `selector`). This only works for delegate-able events: not `focus`, + // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. + delegate: function(eventName, selector, listener) { + this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Clears all callbacks previously bound to the view by `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + if (this.$el) this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // A finer-grained `undelegateEvents` for removing a single delegated event. + // `selector` and `listener` are both optional. + undelegate: function(eventName, selector, listener) { + this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Produces a DOM element to be assigned to your view. Exposed for + // subclasses using an alternative DOM manipulation API. + _createElement: function(tagName) { + return document.createElement(tagName); + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + this.setElement(this._createElement(_.result(this, 'tagName'))); + this._setAttributes(attrs); + } else { + this.setElement(_.result(this, 'el')); + } + }, + + // Set attributes from a hash on this view's element. Exposed for + // subclasses using an alternative DOM manipulation API. + _setAttributes: function(attributes) { + this.$el.attr(attributes); + } + + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // Pass along `textStatus` and `errorThrown` from jQuery. + var error = options.error; + options.error = function(xhr, textStatus, errorThrown) { + options.textStatus = textStatus; + options.errorThrown = errorThrown; + if (error) error.call(options.context, xhr, textStatus, errorThrown); + }; + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + + // Backbone.Router + // --------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + var Router = Backbone.Router = function(options) { + options || (options = {}); + this.preinitialize.apply(this, arguments); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Router.prototype, Events, { + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the Router. + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route: function(route, name, callback) { + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } + if (!callback) callback = this[name]; + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + if (router.execute(callback, args, name) !== false) { + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + } + }); + return this; + }, + + // Execute a route handler with the provided parameters. This is an + // excellent place to do pre-route setup or post-route cleanup. + execute: function(callback, args, name) { + if (callback) callback.apply(this, args); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + return this; + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes: function() { + if (!this.routes) return; + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + return _.map(params, function(param, i) { + // Don't decode the search params. + if (i === params.length - 1) return param || null; + return param ? decodeURIComponent(param) : null; + }); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + this.checkUrl = _.bind(this.checkUrl, this); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } + }; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for stripping urls of hash. + var pathStripper = /#.*$/; + + // Has the history handling already been started? + History.started = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(History.prototype, Events, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Are we at the app root? + atRoot: function() { + var path = this.location.pathname.replace(/[^\/]$/, '$&/'); + return path === this.root && !this.getSearch(); + }, + + // Does the pathname match the root? + matchRoot: function() { + var path = this.decodeFragment(this.location.pathname); + var rootPath = path.slice(0, this.root.length - 1) + '/'; + return rootPath === this.root; + }, + + // Unicode characters in `location.pathname` are percent encoded so they're + // decoded for comparison. `%25` should not be decoded since it may be part + // of an encoded parameter. + decodeFragment: function(fragment) { + return decodeURI(fragment.replace(/%25/g, '%2525')); + }, + + // In IE6, the hash fragment and search params are incorrect if the + // fragment contains `?`. + getSearch: function() { + var match = this.location.href.replace(/#.*/, '').match(/\?.+/); + return match ? match[0] : ''; + }, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the pathname and search params, without the root. + getPath: function() { + var path = this.decodeFragment( + this.location.pathname + this.getSearch() + ).slice(this.root.length - 1); + return path.charAt(0) === '/' ? path.slice(1) : path; + }, + + // Get the cross-browser normalized URL fragment from the path or hash. + getFragment: function(fragment) { + if (fragment == null) { + if (this._usePushState || !this._wantsHashChange) { + fragment = this.getPath(); + } else { + fragment = this.getHash(); + } + } + return fragment.replace(routeStripper, ''); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start: function(options) { + if (History.started) throw new Error('Backbone.history has already been started'); + History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); + 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(); + + // Normalize root to always include a leading and trailing slash. + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + // Transition from hashChange to pushState or vice versa if both are + // requested. + if (this._wantsHashChange && this._wantsPushState) { + + // If we've started off with a route from a `pushState`-enabled + // browser, but we're currently in a browser that doesn't support it... + if (!this._hasPushState && !this.atRoot()) { + var rootPath = this.root.slice(0, -1) || '/'; + this.location.replace(rootPath + '#' + this.getPath()); + // Return immediately as browser will do redirect to new url + return true; + + // Or if we've started out with a hash-based route, but we're currently + // in a browser where it could be `pushState`-based instead... + } else if (this._hasPushState && this.atRoot()) { + this.navigate(this.getHash(), {replace: true}); + } + + } + + // Proxy an iframe to handle location events if the browser doesn't + // support the `hashchange` event, HTML5 history, or the user wants + // `hashChange` but not `pushState`. + 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 body = document.body; + // Using `appendChild` will throw on IE < 9 if the document is not ready. + var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow; + iWindow.document.open(); + iWindow.document.close(); + iWindow.location.hash = '#' + this.fragment; + } + + // Add a cross-platform `addEventListener` shim for older browsers. + var addEventListener = window.addEventListener || function(eventName, listener) { + return attachEvent('on' + eventName, listener); + }; + + // Depending on whether we're using pushState or hashes, and whether + // 'onhashchange' is supported, determine how we check the URL state. + if (this._usePushState) { + addEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + addEventListener('hashchange', this.checkUrl, false); + } else if (this._wantsHashChange) { + this._checkUrlInterval = setInterval(this.checkUrl, this.interval); + } + + if (!this.options.silent) return this.loadUrl(); + }, + + // Disable Backbone.history, perhaps temporarily. Not useful in a real app, + // but possibly useful for unit testing Routers. + stop: function() { + // Add a cross-platform `removeEventListener` shim for older browsers. + var removeEventListener = window.removeEventListener || function(eventName, listener) { + return detachEvent('on' + eventName, listener); + }; + + // Remove window listeners. + if (this._usePushState) { + removeEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + removeEventListener('hashchange', this.checkUrl, false); + } + + // Clean up the iframe if necessary. + if (this.iframe) { + document.body.removeChild(this.iframe); + this.iframe = null; + } + + // Some environments will throw when clearing an undefined interval. + if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); + History.started = false; + }, + + // Add a route to be tested when the fragment changes. Routes added later + // may override previous routes. + route: function(route, callback) { + this.handlers.unshift({route: route, callback: callback}); + }, + + // Checks the current URL to see if it has changed, and if it has, + // calls `loadUrl`, normalizing across the hidden iframe. + checkUrl: function(e) { + var current = this.getFragment(); + + // If the user pressed the back button, the iframe's hash will have + // changed and we should use that for comparison. + if (current === this.fragment && this.iframe) { + current = this.getHash(this.iframe.contentWindow); + } + + if (current === this.fragment) return false; + if (this.iframe) this.navigate(current); + this.loadUrl(); + }, + + // Attempt to load the current URL fragment. If a route succeeds with a + // match, returns `true`. If no defined routes matches the fragment, + // returns `false`. + loadUrl: function(fragment) { + // If the root doesn't match, no routes can match either. + if (!this.matchRoot()) return false; + fragment = this.fragment = this.getFragment(fragment); + return _.some(this.handlers, function(handler) { + if (handler.route.test(fragment)) { + handler.callback(fragment); + return true; + } + }); + }, + + // Save a fragment into the hash history, or replace the URL state if the + // 'replace' option is passed. You are responsible for properly URL-encoding + // the fragment in advance. + // + // The options object can contain `trigger: true` if you wish to have the + // route callback be fired (not usually desirable), or `replace: true`, if + // you wish to modify the current URL without adding an entry to the history. + navigate: function(fragment, options) { + if (!History.started) return false; + if (!options || options === true) options = {trigger: !!options}; + + // Normalize the fragment. + fragment = this.getFragment(fragment || ''); + + // Don't include a trailing slash on the root. + var rootPath = this.root; + if (fragment === '' || fragment.charAt(0) === '?') { + rootPath = rootPath.slice(0, -1) || '/'; + } + var url = rootPath + fragment; + + // Strip the fragment of the query and hash for matching. + fragment = fragment.replace(pathStripper, ''); + + // Decode for matching. + var decodedFragment = this.decodeFragment(fragment); + + if (this.fragment === decodedFragment) return; + this.fragment = decodedFragment; + + // If pushState is available, we use it to set the fragment as a real URL. + if (this._usePushState) { + this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); + + // If hash changes haven't been explicitly disabled, update the hash + // fragment to store history. + } else if (this._wantsHashChange) { + this._updateHash(this.location, fragment, options.replace); + if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) { + var iWindow = this.iframe.contentWindow; + + // Opening and closing the iframe tricks IE7 and earlier to push a + // history entry on hash-tag change. When replace is true, we don't + // want this. + if (!options.replace) { + iWindow.document.open(); + iWindow.document.close(); + } + + this._updateHash(iWindow.location, fragment, options.replace); + } + + // If you've told us that you explicitly don't want fallback hashchange- + // based history, then `navigate` becomes a page refresh. + } else { + return this.location.assign(url); + } + if (options.trigger) return this.loadUrl(fragment); + }, + + // Update the hash location, either replacing the current entry, or adding + // a new one to the browser history. + _updateHash: function(location, fragment, replace) { + if (replace) { + var href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + // Some browsers require that `hash` contains a leading #. + location.hash = '#' + fragment; + } + } + + }); + + // Create the default Backbone.history. + Backbone.history = new History; + + // Helpers + // ------- + + // Helper function to correctly set up the prototype chain for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var extend = function(protoProps, staticProps) { + var parent = this; + var child; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function and add the prototype properties. + child.prototype = _.create(parent.prototype, protoProps); + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed + // later. + child.__super__ = parent.prototype; + + return child; + }; + + // Set up inheritance for the model, collection, router, view and history. + Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; + + // Throw an error when a URL is needed, and none is supplied. + var urlError = function() { + throw new Error('A "url" property or function must be specified'); + }; + + // Wrap an optional error callback with a fallback error event. + var wrapError = function(model, options) { + var error = options.error; + options.error = function(resp) { + if (error) error.call(options.context, model, resp, options); + model.trigger('error', model, resp, options); + }; + }; + + return Backbone; +}); diff --git a/public/vendor/lodash/vendor/backbone/test/collection.js b/public/vendor/lodash/vendor/backbone/test/collection.js new file mode 100644 index 00000000000..4d1dd44832a --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/collection.js @@ -0,0 +1,2023 @@ +(function(QUnit) { + + var a, b, c, d, e, col, otherCol; + + QUnit.module('Backbone.Collection', { + + beforeEach: function(assert) { + a = new Backbone.Model({id: 3, label: 'a'}); + b = new Backbone.Model({id: 2, label: 'b'}); + c = new Backbone.Model({id: 1, label: 'c'}); + d = new Backbone.Model({id: 0, label: 'd'}); + e = null; + col = new Backbone.Collection([a, b, c, d]); + otherCol = new Backbone.Collection(); + } + + }); + + QUnit.test('new and sort', function(assert) { + assert.expect(6); + var counter = 0; + col.on('sort', function(){ counter++; }); + assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']); + col.comparator = function(m1, m2) { + return m1.id > m2.id ? -1 : 1; + }; + col.sort(); + assert.equal(counter, 1); + assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']); + col.comparator = function(model) { return model.id; }; + col.sort(); + assert.equal(counter, 2); + assert.deepEqual(col.pluck('label'), ['d', 'c', 'b', 'a']); + assert.equal(col.length, 4); + }); + + QUnit.test('String comparator.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([ + {id: 3}, + {id: 1}, + {id: 2} + ], {comparator: 'id'}); + assert.deepEqual(collection.pluck('id'), [1, 2, 3]); + }); + + QUnit.test('new and parse', function(assert) { + assert.expect(3); + var Collection = Backbone.Collection.extend({ + parse: function(data) { + return _.filter(data, function(datum) { + return datum.a % 2 === 0; + }); + } + }); + var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}]; + var collection = new Collection(models, {parse: true}); + assert.strictEqual(collection.length, 2); + assert.strictEqual(collection.first().get('a'), 2); + assert.strictEqual(collection.last().get('a'), 4); + }); + + QUnit.test('clone preserves model and comparator', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend(); + var comparator = function(model){ return model.id; }; + + var collection = new Backbone.Collection([{id: 1}], { + model: Model, + comparator: comparator + }).clone(); + collection.add({id: 2}); + assert.ok(collection.at(0) instanceof Model); + assert.ok(collection.at(1) instanceof Model); + assert.strictEqual(collection.comparator, comparator); + }); + + QUnit.test('get', function(assert) { + assert.expect(6); + assert.equal(col.get(0), d); + assert.equal(col.get(d.clone()), d); + assert.equal(col.get(2), b); + assert.equal(col.get({id: 1}), c); + assert.equal(col.get(c.clone()), c); + assert.equal(col.get(col.first().cid), col.first()); + }); + + QUnit.test('get with non-default ids', function(assert) { + assert.expect(5); + var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); + var model = new MongoModel({_id: 100}); + var collection = new Backbone.Collection([model], {model: MongoModel}); + assert.equal(collection.get(100), model); + assert.equal(collection.get(model.cid), model); + assert.equal(collection.get(model), model); + assert.equal(collection.get(101), void 0); + + var collection2 = new Backbone.Collection(); + collection2.model = MongoModel; + collection2.add(model.attributes); + assert.equal(collection2.get(model.clone()), collection2.first()); + }); + + QUnit.test('has', function(assert) { + assert.expect(15); + assert.ok(col.has(a)); + assert.ok(col.has(b)); + assert.ok(col.has(c)); + assert.ok(col.has(d)); + assert.ok(col.has(a.id)); + assert.ok(col.has(b.id)); + assert.ok(col.has(c.id)); + assert.ok(col.has(d.id)); + assert.ok(col.has(a.cid)); + assert.ok(col.has(b.cid)); + assert.ok(col.has(c.cid)); + assert.ok(col.has(d.cid)); + var outsider = new Backbone.Model({id: 4}); + assert.notOk(col.has(outsider)); + assert.notOk(col.has(outsider.id)); + assert.notOk(col.has(outsider.cid)); + }); + + QUnit.test('update index when id changes', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection(); + collection.add([ + {id: 0, name: 'one'}, + {id: 1, name: 'two'} + ]); + var one = collection.get(0); + assert.equal(one.get('name'), 'one'); + collection.on('change:name', function(model) { assert.ok(this.get(model)); }); + one.set({name: 'dalmatians', id: 101}); + assert.equal(collection.get(0), null); + assert.equal(collection.get(101).get('name'), 'dalmatians'); + }); + + QUnit.test('at', function(assert) { + assert.expect(2); + assert.equal(col.at(2), c); + assert.equal(col.at(-2), c); + }); + + QUnit.test('pluck', function(assert) { + assert.expect(1); + assert.equal(col.pluck('label').join(' '), 'a b c d'); + }); + + QUnit.test('add', function(assert) { + assert.expect(14); + var added, opts, secondAdded; + added = opts = secondAdded = null; + e = new Backbone.Model({id: 10, label: 'e'}); + otherCol.add(e); + otherCol.on('add', function() { + secondAdded = true; + }); + col.on('add', function(model, collection, options){ + added = model.get('label'); + opts = options; + }); + col.add(e, {amazing: true}); + assert.equal(added, 'e'); + assert.equal(col.length, 5); + assert.equal(col.last(), e); + assert.equal(otherCol.length, 1); + assert.equal(secondAdded, null); + assert.ok(opts.amazing); + + var f = new Backbone.Model({id: 20, label: 'f'}); + var g = new Backbone.Model({id: 21, label: 'g'}); + var h = new Backbone.Model({id: 22, label: 'h'}); + var atCol = new Backbone.Collection([f, g, h]); + assert.equal(atCol.length, 3); + atCol.add(e, {at: 1}); + assert.equal(atCol.length, 4); + assert.equal(atCol.at(1), e); + assert.equal(atCol.last(), h); + + var coll = new Backbone.Collection(new Array(2)); + var addCount = 0; + coll.on('add', function(){ + addCount += 1; + }); + coll.add([undefined, f, g]); + assert.equal(coll.length, 5); + assert.equal(addCount, 3); + coll.add(new Array(4)); + assert.equal(coll.length, 9); + assert.equal(addCount, 7); + }); + + QUnit.test('add multiple models', function(assert) { + assert.expect(6); + var collection = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]); + collection.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2}); + for (var i = 0; i <= 5; i++) { + assert.equal(collection.at(i).get('at'), i); + } + }); + + QUnit.test('add; at should have preference over comparator', function(assert) { + assert.expect(1); + var Col = Backbone.Collection.extend({ + comparator: function(m1, m2) { + return m1.id > m2.id ? -1 : 1; + } + }); + + var collection = new Col([{id: 2}, {id: 3}]); + collection.add(new Backbone.Model({id: 1}), {at: 1}); + + assert.equal(collection.pluck('id').join(' '), '3 1 2'); + }); + + QUnit.test('add; at should add to the end if the index is out of bounds', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 2}, {id: 3}]); + collection.add(new Backbone.Model({id: 1}), {at: 5}); + + assert.equal(collection.pluck('id').join(' '), '2 3 1'); + }); + + QUnit.test("can't add model to collection twice", function(assert) { + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]); + assert.equal(collection.pluck('id').join(' '), '1 2 3'); + }); + + QUnit.test("can't add different model with same id to collection twice", function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + collection.unshift({id: 101}); + collection.add({id: 101}); + assert.equal(collection.length, 1); + }); + + QUnit.test('merge in duplicate models with {merge: true}', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection; + collection.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]); + collection.add({id: 1, name: 'Moses'}); + assert.equal(collection.first().get('name'), 'Moe'); + collection.add({id: 1, name: 'Moses'}, {merge: true}); + assert.equal(collection.first().get('name'), 'Moses'); + collection.add({id: 1, name: 'Tim'}, {merge: true, silent: true}); + assert.equal(collection.first().get('name'), 'Tim'); + }); + + QUnit.test('add model to multiple collections', function(assert) { + assert.expect(10); + var counter = 0; + var m = new Backbone.Model({id: 10, label: 'm'}); + m.on('add', function(model, collection) { + counter++; + assert.equal(m, model); + if (counter > 1) { + assert.equal(collection, col2); + } else { + assert.equal(collection, col1); + } + }); + var col1 = new Backbone.Collection([]); + col1.on('add', function(model, collection) { + assert.equal(m, model); + assert.equal(col1, collection); + }); + var col2 = new Backbone.Collection([]); + col2.on('add', function(model, collection) { + assert.equal(m, model); + assert.equal(col2, collection); + }); + col1.add(m); + assert.equal(m.collection, col1); + col2.add(m); + assert.equal(m.collection, col1); + }); + + QUnit.test('add model with parse', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + parse: function(obj) { + obj.value += 1; + return obj; + } + }); + + var Col = Backbone.Collection.extend({model: Model}); + var collection = new Col; + collection.add({value: 1}, {parse: true}); + assert.equal(collection.at(0).get('value'), 2); + }); + + QUnit.test('add with parse and merge', function(assert) { + var collection = new Backbone.Collection(); + collection.parse = function(attrs) { + return _.map(attrs, function(model) { + if (model.model) return model.model; + return model; + }); + }; + collection.add({id: 1}); + collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true}); + assert.equal(collection.first().get('name'), 'Alf'); + }); + + QUnit.test('add model to collection with sort()-style comparator', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection; + collection.comparator = function(m1, m2) { + return m1.get('name') < m2.get('name') ? -1 : 1; + }; + var tom = new Backbone.Model({name: 'Tom'}); + var rob = new Backbone.Model({name: 'Rob'}); + var tim = new Backbone.Model({name: 'Tim'}); + collection.add(tom); + collection.add(rob); + collection.add(tim); + assert.equal(collection.indexOf(rob), 0); + assert.equal(collection.indexOf(tim), 1); + assert.equal(collection.indexOf(tom), 2); + }); + + QUnit.test('comparator that depends on `this`', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.negative = function(num) { + return -num; + }; + collection.comparator = function(model) { + return this.negative(model.id); + }; + collection.add([{id: 1}, {id: 2}, {id: 3}]); + assert.deepEqual(collection.pluck('id'), [3, 2, 1]); + collection.comparator = function(m1, m2) { + return this.negative(m2.id) - this.negative(m1.id); + }; + collection.sort(); + assert.deepEqual(collection.pluck('id'), [1, 2, 3]); + }); + + QUnit.test('remove', function(assert) { + assert.expect(12); + var removed = null; + var result = null; + col.on('remove', function(model, collection, options) { + removed = model.get('label'); + assert.equal(options.index, 3); + assert.equal(collection.get(model), undefined, '#3693: model cannot be fetched from collection'); + }); + result = col.remove(d); + assert.equal(removed, 'd'); + assert.strictEqual(result, d); + //if we try to remove d again, it's not going to actually get removed + result = col.remove(d); + assert.strictEqual(result, undefined); + assert.equal(col.length, 3); + assert.equal(col.first(), a); + col.off(); + result = col.remove([c, d]); + assert.equal(result.length, 1, 'only returns removed models'); + assert.equal(result[0], c, 'only returns removed models'); + result = col.remove([c, b]); + assert.equal(result.length, 1, 'only returns removed models'); + assert.equal(result[0], b, 'only returns removed models'); + result = col.remove([]); + assert.deepEqual(result, [], 'returns empty array when nothing removed'); + }); + + QUnit.test('add and remove return values', function(assert) { + assert.expect(13); + var Even = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.id % 2 !== 0) return 'odd'; + } + }); + var collection = new Backbone.Collection; + collection.model = Even; + + var list = collection.add([{id: 2}, {id: 4}], {validate: true}); + assert.equal(list.length, 2); + assert.ok(list[0] instanceof Backbone.Model); + assert.equal(list[1], collection.last()); + assert.equal(list[1].get('id'), 4); + + list = collection.add([{id: 3}, {id: 6}], {validate: true}); + assert.equal(collection.length, 3); + assert.equal(list[0], false); + assert.equal(list[1].get('id'), 6); + + var result = collection.add({id: 6}); + assert.equal(result.cid, list[1].cid); + + result = collection.remove({id: 6}); + assert.equal(collection.length, 2); + assert.equal(result.id, 6); + + list = collection.remove([{id: 2}, {id: 8}]); + assert.equal(collection.length, 1); + assert.equal(list[0].get('id'), 2); + assert.equal(list[1], null); + }); + + QUnit.test('shift and pop', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); + assert.equal(collection.shift().get('a'), 'a'); + assert.equal(collection.pop().get('c'), 'c'); + }); + + QUnit.test('slice', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]); + var array = collection.slice(1, 3); + assert.equal(array.length, 2); + assert.equal(array[0].get('b'), 'b'); + }); + + QUnit.test('events are unbound on remove', function(assert) { + assert.expect(3); + var counter = 0; + var dj = new Backbone.Model(); + var emcees = new Backbone.Collection([dj]); + emcees.on('change', function(){ counter++; }); + dj.set({name: 'Kool'}); + assert.equal(counter, 1); + emcees.reset([]); + assert.equal(dj.collection, undefined); + dj.set({name: 'Shadow'}); + assert.equal(counter, 1); + }); + + QUnit.test('remove in multiple collections', function(assert) { + assert.expect(7); + var modelData = { + id: 5, + title: 'Othello' + }; + var passed = false; + var m1 = new Backbone.Model(modelData); + var m2 = new Backbone.Model(modelData); + m2.on('remove', function() { + passed = true; + }); + var col1 = new Backbone.Collection([m1]); + var col2 = new Backbone.Collection([m2]); + assert.notEqual(m1, m2); + assert.ok(col1.length === 1); + assert.ok(col2.length === 1); + col1.remove(m1); + assert.equal(passed, false); + assert.ok(col1.length === 0); + col2.remove(m1); + assert.ok(col2.length === 0); + assert.equal(passed, true); + }); + + QUnit.test('remove same model in multiple collection', function(assert) { + assert.expect(16); + var counter = 0; + var m = new Backbone.Model({id: 5, title: 'Othello'}); + m.on('remove', function(model, collection) { + counter++; + assert.equal(m, model); + if (counter > 1) { + assert.equal(collection, col1); + } else { + assert.equal(collection, col2); + } + }); + var col1 = new Backbone.Collection([m]); + col1.on('remove', function(model, collection) { + assert.equal(m, model); + assert.equal(col1, collection); + }); + var col2 = new Backbone.Collection([m]); + col2.on('remove', function(model, collection) { + assert.equal(m, model); + assert.equal(col2, collection); + }); + assert.equal(col1, m.collection); + col2.remove(m); + assert.ok(col2.length === 0); + assert.ok(col1.length === 1); + assert.equal(counter, 1); + assert.equal(col1, m.collection); + col1.remove(m); + assert.equal(null, m.collection); + assert.ok(col1.length === 0); + assert.equal(counter, 2); + }); + + QUnit.test('model destroy removes from all collections', function(assert) { + assert.expect(3); + var m = new Backbone.Model({id: 5, title: 'Othello'}); + m.sync = function(method, model, options) { options.success(); }; + var col1 = new Backbone.Collection([m]); + var col2 = new Backbone.Collection([m]); + m.destroy(); + assert.ok(col1.length === 0); + assert.ok(col2.length === 0); + assert.equal(undefined, m.collection); + }); + + QUnit.test('Collection: non-persisted model destroy removes from all collections', function(assert) { + assert.expect(3); + var m = new Backbone.Model({title: 'Othello'}); + m.sync = function(method, model, options) { throw 'should not be called'; }; + var col1 = new Backbone.Collection([m]); + var col2 = new Backbone.Collection([m]); + m.destroy(); + assert.ok(col1.length === 0); + assert.ok(col2.length === 0); + assert.equal(undefined, m.collection); + }); + + QUnit.test('fetch', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection; + collection.url = '/test'; + collection.fetch(); + assert.equal(this.syncArgs.method, 'read'); + assert.equal(this.syncArgs.model, collection); + assert.equal(this.syncArgs.options.parse, true); + + collection.fetch({parse: false}); + assert.equal(this.syncArgs.options.parse, false); + }); + + QUnit.test('fetch with an error response triggers an error event', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection(); + collection.on('error', function() { + assert.ok(true); + }); + collection.sync = function(method, model, options) { options.error(); }; + collection.fetch(); + }); + + QUnit.test('#3283 - fetch with an error response calls error with context', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection(); + var obj = {}; + var options = { + context: obj, + error: function() { + assert.equal(this, obj); + } + }; + collection.sync = function(method, model, opts) { + opts.error.call(opts.context); + }; + collection.fetch(options); + }); + + QUnit.test('ensure fetch only parses once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + var counter = 0; + collection.parse = function(models) { + counter++; + return models; + }; + collection.url = '/test'; + collection.fetch(); + this.syncArgs.options.success([]); + assert.equal(counter, 1); + }); + + QUnit.test('create', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection; + collection.url = '/test'; + var model = collection.create({label: 'f'}, {wait: true}); + assert.equal(this.syncArgs.method, 'create'); + assert.equal(this.syncArgs.model, model); + assert.equal(model.get('label'), 'f'); + assert.equal(model.collection, collection); + }); + + QUnit.test('create with validate:true enforces validation', function(assert) { + assert.expect(3); + var ValidatingModel = Backbone.Model.extend({ + validate: function(attrs) { + return 'fail'; + } + }); + var ValidatingCollection = Backbone.Collection.extend({ + model: ValidatingModel + }); + var collection = new ValidatingCollection(); + collection.on('invalid', function(coll, error, options) { + assert.equal(error, 'fail'); + assert.equal(options.validationError, 'fail'); + }); + assert.equal(collection.create({foo: 'bar'}, {validate: true}), false); + }); + + QUnit.test('create will pass extra options to success callback', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + sync: function(method, model, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, model, options); + } + }); + + var Collection = Backbone.Collection.extend({ + model: Model, + url: '/test' + }); + + var collection = new Collection; + + var success = function(model, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + collection.create({}, {success: success}); + this.ajaxSettings.success(); + }); + + QUnit.test('create with wait:true should not call collection.parse', function(assert) { + assert.expect(0); + var Collection = Backbone.Collection.extend({ + url: '/test', + parse: function() { + assert.ok(false); + } + }); + + var collection = new Collection; + + collection.create({}, {wait: true}); + this.ajaxSettings.success(); + }); + + QUnit.test('a failing create returns model with errors', function(assert) { + var ValidatingModel = Backbone.Model.extend({ + validate: function(attrs) { + return 'fail'; + } + }); + var ValidatingCollection = Backbone.Collection.extend({ + model: ValidatingModel + }); + var collection = new ValidatingCollection(); + var m = collection.create({foo: 'bar'}); + assert.equal(m.validationError, 'fail'); + assert.equal(collection.length, 1); + }); + + QUnit.test('initialize', function(assert) { + assert.expect(1); + var Collection = Backbone.Collection.extend({ + initialize: function() { + this.one = 1; + } + }); + var coll = new Collection; + assert.equal(coll.one, 1); + }); + + QUnit.test('preinitialize', function(assert) { + assert.expect(1); + var Collection = Backbone.Collection.extend({ + preinitialize: function() { + this.one = 1; + } + }); + var coll = new Collection; + assert.equal(coll.one, 1); + }); + + QUnit.test('preinitialize occurs before the collection is set up', function(assert) { + assert.expect(2); + var Collection = Backbone.Collection.extend({ + preinitialize: function() { + assert.notEqual(this.model, FooModel); + } + }); + var FooModel = Backbone.Model.extend({id: 'foo'}); + var coll = new Collection({}, { + model: FooModel + }); + assert.equal(coll.model, FooModel); + }); + + QUnit.test('toJSON', function(assert) { + assert.expect(1); + assert.equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]'); + }); + + QUnit.test('where and findWhere', function(assert) { + assert.expect(8); + var model = new Backbone.Model({a: 1}); + var coll = new Backbone.Collection([ + model, + {a: 1}, + {a: 1, b: 2}, + {a: 2, b: 2}, + {a: 3} + ]); + assert.equal(coll.where({a: 1}).length, 3); + assert.equal(coll.where({a: 2}).length, 1); + assert.equal(coll.where({a: 3}).length, 1); + assert.equal(coll.where({b: 1}).length, 0); + assert.equal(coll.where({b: 2}).length, 2); + assert.equal(coll.where({a: 1, b: 2}).length, 1); + assert.equal(coll.findWhere({a: 1}), model); + assert.equal(coll.findWhere({a: 4}), void 0); + }); + + QUnit.test('Underscore methods', function(assert) { + assert.expect(21); + assert.equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d'); + assert.equal(col.some(function(model){ return model.id === 100; }), false); + assert.equal(col.some(function(model){ return model.id === 0; }), true); + assert.equal(col.reduce(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3); + assert.equal(col.reduceRight(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3); + assert.equal(col.indexOf(b), 1); + assert.equal(col.size(), 4); + assert.equal(col.rest().length, 3); + assert.ok(!_.includes(col.rest(), a)); + assert.ok(_.includes(col.rest(), d)); + assert.ok(!col.isEmpty()); + assert.ok(!_.includes(col.without(d), d)); + + var wrapped = col.chain(); + assert.equal(wrapped.map('id').max().value(), 3); + assert.equal(wrapped.map('id').min().value(), 0); + assert.deepEqual(wrapped + .filter(function(o){ return o.id % 2 === 0; }) + .map(function(o){ return o.id * 2; }) + .value(), + [4, 0]); + assert.deepEqual(col.difference([c, d]), [a, b]); + assert.ok(col.includes(col.sample())); + + var first = col.first(); + assert.deepEqual(col.groupBy(function(model){ return model.id; })[first.id], [first]); + assert.deepEqual(col.countBy(function(model){ return model.id; }), {0: 1, 1: 1, 2: 1, 3: 1}); + assert.deepEqual(col.sortBy(function(model){ return model.id; })[0], col.at(3)); + assert.ok(col.indexBy('id')[first.id] === first); + }); + + QUnit.test('Underscore methods with object-style and property-style iteratee', function(assert) { + assert.expect(26); + var model = new Backbone.Model({a: 4, b: 1, e: 3}); + var coll = new Backbone.Collection([ + {a: 1, b: 1}, + {a: 2, b: 1, c: 1}, + {a: 3, b: 1}, + model + ]); + assert.equal(coll.find({a: 0}), undefined); + assert.deepEqual(coll.find({a: 4}), model); + assert.equal(coll.find('d'), undefined); + assert.deepEqual(coll.find('e'), model); + assert.equal(coll.filter({a: 0}), false); + assert.deepEqual(coll.filter({a: 4}), [model]); + assert.equal(coll.some({a: 0}), false); + assert.equal(coll.some({a: 1}), true); + assert.equal(coll.reject({a: 0}).length, 4); + assert.deepEqual(coll.reject({a: 4}), _.without(coll.models, model)); + assert.equal(coll.every({a: 0}), false); + assert.equal(coll.every({b: 1}), true); + assert.deepEqual(coll.partition({a: 0})[0], []); + assert.deepEqual(coll.partition({a: 0})[1], coll.models); + assert.deepEqual(coll.partition({a: 4})[0], [model]); + assert.deepEqual(coll.partition({a: 4})[1], _.without(coll.models, model)); + assert.deepEqual(coll.map({a: 2}), [false, true, false, false]); + assert.deepEqual(coll.map('a'), [1, 2, 3, 4]); + assert.deepEqual(coll.sortBy('a')[3], model); + assert.deepEqual(coll.sortBy('e')[0], model); + assert.deepEqual(coll.countBy({a: 4}), {'false': 3, 'true': 1}); + assert.deepEqual(coll.countBy('d'), {'undefined': 4}); + assert.equal(coll.findIndex({b: 1}), 0); + assert.equal(coll.findIndex({b: 9}), -1); + assert.equal(coll.findLastIndex({b: 1}), 3); + assert.equal(coll.findLastIndex({b: 9}), -1); + }); + + QUnit.test('reset', function(assert) { + assert.expect(16); + + var resetCount = 0; + var models = col.models; + col.on('reset', function() { resetCount += 1; }); + col.reset([]); + assert.equal(resetCount, 1); + assert.equal(col.length, 0); + assert.equal(col.last(), null); + col.reset(models); + assert.equal(resetCount, 2); + assert.equal(col.length, 4); + assert.equal(col.last(), d); + col.reset(_.map(models, function(m){ return m.attributes; })); + assert.equal(resetCount, 3); + assert.equal(col.length, 4); + assert.ok(col.last() !== d); + assert.ok(_.isEqual(col.last().attributes, d.attributes)); + col.reset(); + assert.equal(col.length, 0); + assert.equal(resetCount, 4); + + var f = new Backbone.Model({id: 20, label: 'f'}); + col.reset([undefined, f]); + assert.equal(col.length, 2); + assert.equal(resetCount, 5); + + col.reset(new Array(4)); + assert.equal(col.length, 4); + assert.equal(resetCount, 6); + }); + + QUnit.test('reset with different values', function(assert) { + var collection = new Backbone.Collection({id: 1}); + collection.reset({id: 1, a: 1}); + assert.equal(collection.get(1).get('a'), 1); + }); + + QUnit.test('same references in reset', function(assert) { + var model = new Backbone.Model({id: 1}); + var collection = new Backbone.Collection({id: 1}); + collection.reset(model); + assert.equal(collection.get(1), model); + }); + + QUnit.test('reset passes caller options', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend({ + initialize: function(attrs, options) { + this.modelParameter = options.modelParameter; + } + }); + var collection = new (Backbone.Collection.extend({model: Model}))(); + collection.reset([{astring: 'green', anumber: 1}, {astring: 'blue', anumber: 2}], {modelParameter: 'model parameter'}); + assert.equal(collection.length, 2); + collection.each(function(model) { + assert.equal(model.modelParameter, 'model parameter'); + }); + }); + + QUnit.test('reset does not alter options by reference', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{id: 1}]); + var origOpts = {}; + collection.on('reset', function(coll, opts){ + assert.equal(origOpts.previousModels, undefined); + assert.equal(opts.previousModels[0].id, 1); + }); + collection.reset([], origOpts); + }); + + QUnit.test('trigger custom events on models', function(assert) { + assert.expect(1); + var fired = null; + a.on('custom', function() { fired = true; }); + a.trigger('custom'); + assert.equal(fired, true); + }); + + QUnit.test('add does not alter arguments', function(assert) { + assert.expect(2); + var attrs = {}; + var models = [attrs]; + new Backbone.Collection().add(models); + assert.equal(models.length, 1); + assert.ok(attrs === models[0]); + }); + + QUnit.test('#714: access `model.collection` in a brand new model.', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.url = '/test'; + var Model = Backbone.Model.extend({ + set: function(attrs) { + assert.equal(attrs.prop, 'value'); + assert.equal(this.collection, collection); + return this; + } + }); + collection.model = Model; + collection.create({prop: 'value'}); + }); + + QUnit.test('#574, remove its own reference to the .models array.', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([ + {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6} + ]); + assert.equal(collection.length, 6); + collection.remove(collection.models); + assert.equal(collection.length, 0); + }); + + QUnit.test('#861, adding models to a collection which do not pass validation, with validate:true', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.id === 3) return "id can't be 3"; + } + }); + + var Collection = Backbone.Collection.extend({ + model: Model + }); + + var collection = new Collection; + collection.on('invalid', function() { assert.ok(true); }); + + collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate: true}); + assert.deepEqual(collection.pluck('id'), [1, 2, 4, 5, 6]); + }); + + QUnit.test('Invalid models are discarded with validate:true.', function(assert) { + assert.expect(5); + var collection = new Backbone.Collection; + collection.on('test', function() { assert.ok(true); }); + collection.model = Backbone.Model.extend({ + validate: function(attrs){ if (!attrs.valid) return 'invalid'; } + }); + var model = new collection.model({id: 1, valid: true}); + collection.add([model, {id: 2}], {validate: true}); + model.trigger('test'); + assert.ok(collection.get(model.cid)); + assert.ok(collection.get(1)); + assert.ok(!collection.get(2)); + assert.equal(collection.length, 1); + }); + + QUnit.test('multiple copies of the same model', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection(); + var model = new Backbone.Model(); + collection.add([model, model]); + assert.equal(collection.length, 1); + collection.add([{id: 1}, {id: 1}]); + assert.equal(collection.length, 2); + assert.equal(collection.last().id, 1); + }); + + QUnit.test('#964 - collection.get return inconsistent', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection(); + assert.ok(collection.get(null) === undefined); + assert.ok(collection.get() === undefined); + }); + + QUnit.test('#1112 - passing options.model sets collection.model', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({}); + var collection = new Backbone.Collection([{id: 1}], {model: Model}); + assert.ok(collection.model === Model); + assert.ok(collection.at(0) instanceof Model); + }); + + QUnit.test('null and undefined are invalid ids.', function(assert) { + assert.expect(2); + var model = new Backbone.Model({id: 1}); + var collection = new Backbone.Collection([model]); + model.set({id: null}); + assert.ok(!collection.get('null')); + model.set({id: 1}); + model.set({id: undefined}); + assert.ok(!collection.get('undefined')); + }); + + QUnit.test('falsy comparator', function(assert) { + assert.expect(4); + var Col = Backbone.Collection.extend({ + comparator: function(model){ return model.id; } + }); + var collection = new Col(); + var colFalse = new Col(null, {comparator: false}); + var colNull = new Col(null, {comparator: null}); + var colUndefined = new Col(null, {comparator: undefined}); + assert.ok(collection.comparator); + assert.ok(!colFalse.comparator); + assert.ok(!colNull.comparator); + assert.ok(colUndefined.comparator); + }); + + QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) { + assert.expect(2); + var m = new Backbone.Model({x: 1}); + var collection = new Backbone.Collection(); + var opts = { + opts: true, + success: function(coll, resp, options) { + assert.ok(options.opts); + } + }; + collection.sync = m.sync = function( method, coll, options ){ + options.success({}); + }; + collection.fetch(opts); + collection.create(m, opts); + }); + + QUnit.test("#1412 - Trigger 'request' and 'sync' events.", function(assert) { + assert.expect(4); + var collection = new Backbone.Collection; + collection.url = '/test'; + Backbone.ajax = function(settings){ settings.success(); }; + + collection.on('request', function(obj, xhr, options) { + assert.ok(obj === collection, "collection has correct 'request' event after fetching"); + }); + collection.on('sync', function(obj, response, options) { + assert.ok(obj === collection, "collection has correct 'sync' event after fetching"); + }); + collection.fetch(); + collection.off(); + + collection.on('request', function(obj, xhr, options) { + assert.ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save"); + }); + collection.on('sync', function(obj, response, options) { + assert.ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save"); + }); + collection.create({id: 1}); + collection.off(); + }); + + QUnit.test('#3283 - fetch, create calls success with context', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.url = '/test'; + Backbone.ajax = function(settings) { + settings.success.call(settings.context); + }; + var obj = {}; + var options = { + context: obj, + success: function() { + assert.equal(this, obj); + } + }; + + collection.fetch(options); + collection.create({id: 1}, options); + }); + + QUnit.test('#1447 - create with wait adds model.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + var model = new Backbone.Model; + model.sync = function(method, m, options){ options.success(); }; + collection.on('add', function(){ assert.ok(true); }); + collection.create(model, {wait: true}); + }); + + QUnit.test('#1448 - add sorts collection after merge.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([ + {id: 1, x: 1}, + {id: 2, x: 2} + ]); + collection.comparator = function(model){ return model.get('x'); }; + collection.add({id: 1, x: 3}, {merge: true}); + assert.deepEqual(collection.pluck('id'), [2, 1]); + }); + + QUnit.test('#1655 - groupBy can be used with a string argument.', function(assert) { + assert.expect(3); + var collection = new Backbone.Collection([{x: 1}, {x: 2}]); + var grouped = collection.groupBy('x'); + assert.strictEqual(_.keys(grouped).length, 2); + assert.strictEqual(grouped[1][0].get('x'), 1); + assert.strictEqual(grouped[2][0].get('x'), 2); + }); + + QUnit.test('#1655 - sortBy can be used with a string argument.', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]); + var values = _.map(collection.sortBy('x'), function(model) { + return model.get('x'); + }); + assert.deepEqual(values, [1, 2, 3]); + }); + + QUnit.test('#1604 - Removal during iteration.', function(assert) { + assert.expect(0); + var collection = new Backbone.Collection([{}, {}]); + collection.on('add', function() { + collection.at(0).destroy(); + }); + collection.add({}, {at: 0}); + }); + + QUnit.test('#1638 - `sort` during `add` triggers correctly.', function(assert) { + var collection = new Backbone.Collection; + collection.comparator = function(model) { return model.get('x'); }; + var added = []; + collection.on('add', function(model) { + model.set({x: 3}); + collection.sort(); + added.push(model.id); + }); + collection.add([{id: 1, x: 1}, {id: 2, x: 2}]); + assert.deepEqual(added, [1, 2]); + }); + + QUnit.test('fetch parses models by default', function(assert) { + assert.expect(1); + var model = {}; + var Collection = Backbone.Collection.extend({ + url: 'test', + model: Backbone.Model.extend({ + parse: function(resp) { + assert.strictEqual(resp, model); + } + }) + }); + new Collection().fetch(); + this.ajaxSettings.success([model]); + }); + + QUnit.test("`sort` shouldn't always fire on `add`", function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], { + comparator: 'id' + }); + collection.sort = function(){ assert.ok(true); }; + collection.add([]); + collection.add({id: 1}); + collection.add([{id: 2}, {id: 3}]); + collection.add({id: 4}); + }); + + QUnit.test('#1407 parse option on constructor parses collection and models', function(assert) { + assert.expect(2); + var model = { + namespace: [{id: 1}, {id: 2}] + }; + var Collection = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + parse: function(m) { + m.name = 'test'; + return m; + } + }), + parse: function(m) { + return m.namespace; + } + }); + var collection = new Collection(model, {parse: true}); + + assert.equal(collection.length, 2); + assert.equal(collection.at(0).get('name'), 'test'); + }); + + QUnit.test('#1407 parse option on reset parses collection and models', function(assert) { + assert.expect(2); + var model = { + namespace: [{id: 1}, {id: 2}] + }; + var Collection = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + parse: function(m) { + m.name = 'test'; + return m; + } + }), + parse: function(m) { + return m.namespace; + } + }); + var collection = new Collection(); + collection.reset(model, {parse: true}); + + assert.equal(collection.length, 2); + assert.equal(collection.at(0).get('name'), 'test'); + }); + + + QUnit.test('Reset includes previous models in triggered event.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + var collection = new Backbone.Collection([model]); + collection.on('reset', function(coll, options) { + assert.deepEqual(options.previousModels, [model]); + }); + collection.reset([]); + }); + + QUnit.test('set', function(assert) { + var m1 = new Backbone.Model(); + var m2 = new Backbone.Model({id: 2}); + var m3 = new Backbone.Model(); + var collection = new Backbone.Collection([m1, m2]); + + // Test add/change/remove events + collection.on('add', function(model) { + assert.strictEqual(model, m3); + }); + collection.on('change', function(model) { + assert.strictEqual(model, m2); + }); + collection.on('remove', function(model) { + assert.strictEqual(model, m1); + }); + + // remove: false doesn't remove any models + collection.set([], {remove: false}); + assert.strictEqual(collection.length, 2); + + // add: false doesn't add any models + collection.set([m1, m2, m3], {add: false}); + assert.strictEqual(collection.length, 2); + + // merge: false doesn't change any models + collection.set([m1, {id: 2, a: 1}], {merge: false}); + assert.strictEqual(m2.get('a'), void 0); + + // add: false, remove: false only merges existing models + collection.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false}); + assert.strictEqual(collection.length, 2); + assert.strictEqual(m2.get('a'), 0); + + // default options add/remove/merge as appropriate + collection.set([{id: 2, a: 1}, m3]); + assert.strictEqual(collection.length, 2); + assert.strictEqual(m2.get('a'), 1); + + // Test removing models not passing an argument + collection.off('remove').on('remove', function(model) { + assert.ok(model === m2 || model === m3); + }); + collection.set([]); + assert.strictEqual(collection.length, 0); + + // Test null models on set doesn't clear collection + collection.off(); + collection.set([{id: 1}]); + collection.set(); + assert.strictEqual(collection.length, 1); + }); + + QUnit.test('set with only cids', function(assert) { + assert.expect(3); + var m1 = new Backbone.Model; + var m2 = new Backbone.Model; + var collection = new Backbone.Collection; + collection.set([m1, m2]); + assert.equal(collection.length, 2); + collection.set([m1]); + assert.equal(collection.length, 1); + collection.set([m1, m1, m1, m2, m2], {remove: false}); + assert.equal(collection.length, 2); + }); + + QUnit.test('set with only idAttribute', function(assert) { + assert.expect(3); + var m1 = {_id: 1}; + var m2 = {_id: 2}; + var Col = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + idAttribute: '_id' + }) + }); + var collection = new Col; + collection.set([m1, m2]); + assert.equal(collection.length, 2); + collection.set([m1]); + assert.equal(collection.length, 1); + collection.set([m1, m1, m1, m2, m2], {remove: false}); + assert.equal(collection.length, 2); + }); + + QUnit.test('set + merge with default values defined', function(assert) { + var Model = Backbone.Model.extend({ + defaults: { + key: 'value' + } + }); + var m = new Model({id: 1}); + var collection = new Backbone.Collection([m], {model: Model}); + assert.equal(collection.first().get('key'), 'value'); + + collection.set({id: 1, key: 'other'}); + assert.equal(collection.first().get('key'), 'other'); + + collection.set({id: 1, other: 'value'}); + assert.equal(collection.first().get('key'), 'other'); + assert.equal(collection.length, 1); + }); + + QUnit.test('merge without mutation', function(assert) { + var Model = Backbone.Model.extend({ + initialize: function(attrs, options) { + if (attrs.child) { + this.set('child', new Model(attrs.child, options), options); + } + } + }); + var Collection = Backbone.Collection.extend({model: Model}); + var data = [{id: 1, child: {id: 2}}]; + var collection = new Collection(data); + assert.equal(collection.first().id, 1); + collection.set(data); + assert.equal(collection.first().id, 1); + collection.set([{id: 2, child: {id: 2}}].concat(data)); + assert.deepEqual(collection.pluck('id'), [2, 1]); + }); + + QUnit.test('`set` and model level `parse`', function(assert) { + var Model = Backbone.Model.extend({}); + var Collection = Backbone.Collection.extend({ + model: Model, + parse: function(res) { return _.map(res.models, 'model'); } + }); + var model = new Model({id: 1}); + var collection = new Collection(model); + collection.set({models: [ + {model: {id: 1}}, + {model: {id: 2}} + ]}, {parse: true}); + assert.equal(collection.first(), model); + }); + + QUnit.test('`set` data is only parsed once', function(assert) { + var collection = new Backbone.Collection(); + collection.model = Backbone.Model.extend({ + parse: function(data) { + assert.equal(data.parsed, void 0); + data.parsed = true; + return data; + } + }); + collection.set({}, {parse: true}); + }); + + QUnit.test('`set` matches input order in the absence of a comparator', function(assert) { + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.set([{id: 3}, {id: 2}, {id: 1}]); + assert.deepEqual(collection.models, [three, two, one]); + collection.set([{id: 1}, {id: 2}]); + assert.deepEqual(collection.models, [one, two]); + collection.set([two, three, one]); + assert.deepEqual(collection.models, [two, three, one]); + collection.set([{id: 1}, {id: 2}], {remove: false}); + assert.deepEqual(collection.models, [two, three, one]); + collection.set([{id: 1}, {id: 2}, {id: 3}], {merge: false}); + assert.deepEqual(collection.models, [one, two, three]); + collection.set([three, two, one, {id: 4}], {add: false}); + assert.deepEqual(collection.models, [one, two, three]); + }); + + QUnit.test('#1894 - Push should not trigger a sort', function(assert) { + assert.expect(0); + var Collection = Backbone.Collection.extend({ + comparator: 'id', + sort: function() { assert.ok(false); } + }); + new Collection().push({id: 1}); + }); + + QUnit.test('#2428 - push duplicate models, return the correct one', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + var model1 = collection.push({id: 101}); + var model2 = collection.push({id: 101}); + assert.ok(model2.cid === model1.cid); + }); + + QUnit.test('`set` with non-normal id', function(assert) { + var Collection = Backbone.Collection.extend({ + model: Backbone.Model.extend({idAttribute: '_id'}) + }); + var collection = new Collection({_id: 1}); + collection.set([{_id: 1, a: 1}], {add: false}); + assert.equal(collection.first().get('a'), 1); + }); + + QUnit.test('#1894 - `sort` can optionally be turned off', function(assert) { + assert.expect(0); + var Collection = Backbone.Collection.extend({ + comparator: 'id', + sort: function() { assert.ok(false); } + }); + new Collection().add({id: 1}, {sort: false}); + }); + + QUnit.test('#1915 - `parse` data in the right order in `set`', function(assert) { + var collection = new (Backbone.Collection.extend({ + parse: function(data) { + assert.strictEqual(data.status, 'ok'); + return data.data; + } + })); + var res = {status: 'ok', data: [{id: 1}]}; + collection.set(res, {parse: true}); + }); + + QUnit.test('#1939 - `parse` is passed `options`', function(assert) { + var done = assert.async(); + assert.expect(1); + var collection = new (Backbone.Collection.extend({ + url: '/', + parse: function(data, options) { + assert.strictEqual(options.xhr.someHeader, 'headerValue'); + return data; + } + })); + var ajax = Backbone.ajax; + Backbone.ajax = function(params) { + _.defer(params.success, []); + return {someHeader: 'headerValue'}; + }; + collection.fetch({ + success: function() { done(); } + }); + Backbone.ajax = ajax; + }); + + QUnit.test('fetch will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncCollection = Backbone.Collection.extend({ + url: '/test', + sync: function(method, collection, options) { + _.extend(options, {specialSync: true}); + return Backbone.Collection.prototype.sync.call(this, method, collection, options); + } + }); + + var collection = new SpecialSyncCollection(); + + var onSuccess = function(coll, resp, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + collection.fetch({success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('`add` only `sort`s when necessary', function(assert) { + assert.expect(2); + var collection = new (Backbone.Collection.extend({ + comparator: 'a' + }))([{id: 1}, {id: 2}, {id: 3}]); + collection.on('sort', function() { assert.ok(true); }); + collection.add({id: 4}); // do sort, new model + collection.add({id: 1, a: 1}, {merge: true}); // do sort, comparator change + collection.add({id: 1, b: 1}, {merge: true}); // don't sort, no comparator change + collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no comparator change + collection.add(collection.models); // don't sort, nothing new + collection.add(collection.models, {merge: true}); // don't sort + }); + + QUnit.test('`add` only `sort`s when necessary with comparator function', function(assert) { + assert.expect(3); + var collection = new (Backbone.Collection.extend({ + comparator: function(m1, m2) { + return m1.get('a') > m2.get('a') ? 1 : (m1.get('a') < m2.get('a') ? -1 : 0); + } + }))([{id: 1}, {id: 2}, {id: 3}]); + collection.on('sort', function() { assert.ok(true); }); + collection.add({id: 4}); // do sort, new model + collection.add({id: 1, a: 1}, {merge: true}); // do sort, model change + collection.add({id: 1, b: 1}, {merge: true}); // do sort, model change + collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no model change + collection.add(collection.models); // don't sort, nothing new + collection.add(collection.models, {merge: true}); // don't sort + }); + + QUnit.test('Attach options to collection.', function(assert) { + assert.expect(2); + var Model = Backbone.Model; + var comparator = function(){}; + + var collection = new Backbone.Collection([], { + model: Model, + comparator: comparator + }); + + assert.ok(collection.model === Model); + assert.ok(collection.comparator === comparator); + }); + + QUnit.test('Pass falsey for `models` for empty Col with `options`', function(assert) { + assert.expect(9); + var opts = {a: 1, b: 2}; + _.forEach([undefined, null, false], function(falsey) { + var Collection = Backbone.Collection.extend({ + initialize: function(models, options) { + assert.strictEqual(models, falsey); + assert.strictEqual(options, opts); + } + }); + + var collection = new Collection(falsey, opts); + assert.strictEqual(collection.length, 0); + }); + }); + + QUnit.test('`add` overrides `set` flags', function(assert) { + var collection = new Backbone.Collection(); + collection.once('add', function(model, coll, options) { + coll.add({id: 2}, options); + }); + collection.set({id: 1}); + assert.equal(collection.length, 2); + }); + + QUnit.test('#2606 - Collection#create, success arguments', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + collection.url = 'test'; + collection.create({}, { + success: function(model, resp, options) { + assert.strictEqual(resp, 'response'); + } + }); + this.ajaxSettings.success('response'); + }); + + QUnit.test('#2612 - nested `parse` works with `Collection#set`', function(assert) { + + var Job = Backbone.Model.extend({ + constructor: function() { + this.items = new Items(); + Backbone.Model.apply(this, arguments); + }, + parse: function(attrs) { + this.items.set(attrs.items, {parse: true}); + return _.omit(attrs, 'items'); + } + }); + + var Item = Backbone.Model.extend({ + constructor: function() { + this.subItems = new Backbone.Collection(); + Backbone.Model.apply(this, arguments); + }, + parse: function(attrs) { + this.subItems.set(attrs.subItems, {parse: true}); + return _.omit(attrs, 'subItems'); + } + }); + + var Items = Backbone.Collection.extend({ + model: Item + }); + + var data = { + name: 'JobName', + id: 1, + items: [{ + id: 1, + name: 'Sub1', + subItems: [ + {id: 1, subName: 'One'}, + {id: 2, subName: 'Two'} + ] + }, { + id: 2, + name: 'Sub2', + subItems: [ + {id: 3, subName: 'Three'}, + {id: 4, subName: 'Four'} + ] + }] + }; + + var newData = { + name: 'NewJobName', + id: 1, + items: [{ + id: 1, + name: 'NewSub1', + subItems: [ + {id: 1, subName: 'NewOne'}, + {id: 2, subName: 'NewTwo'} + ] + }, { + id: 2, + name: 'NewSub2', + subItems: [ + {id: 3, subName: 'NewThree'}, + {id: 4, subName: 'NewFour'} + ] + }] + }; + + var job = new Job(data, {parse: true}); + assert.equal(job.get('name'), 'JobName'); + assert.equal(job.items.at(0).get('name'), 'Sub1'); + assert.equal(job.items.length, 2); + assert.equal(job.items.get(1).subItems.get(1).get('subName'), 'One'); + assert.equal(job.items.get(2).subItems.get(3).get('subName'), 'Three'); + job.set(job.parse(newData, {parse: true})); + assert.equal(job.get('name'), 'NewJobName'); + assert.equal(job.items.at(0).get('name'), 'NewSub1'); + assert.equal(job.items.length, 2); + assert.equal(job.items.get(1).subItems.get(1).get('subName'), 'NewOne'); + assert.equal(job.items.get(2).subItems.get(3).get('subName'), 'NewThree'); + }); + + QUnit.test('_addReference binds all collection events & adds to the lookup hashes', function(assert) { + assert.expect(8); + + var calls = {add: 0, remove: 0}; + + var Collection = Backbone.Collection.extend({ + + _addReference: function(model) { + Backbone.Collection.prototype._addReference.apply(this, arguments); + calls.add++; + assert.equal(model, this._byId[model.id]); + assert.equal(model, this._byId[model.cid]); + assert.equal(model._events.all.length, 1); + }, + + _removeReference: function(model) { + Backbone.Collection.prototype._removeReference.apply(this, arguments); + calls.remove++; + assert.equal(this._byId[model.id], void 0); + assert.equal(this._byId[model.cid], void 0); + assert.equal(model.collection, void 0); + } + + }); + + var collection = new Collection(); + var model = collection.add({id: 1}); + collection.remove(model); + + assert.equal(calls.add, 1); + assert.equal(calls.remove, 1); + }); + + QUnit.test('Do not allow duplicate models to be `add`ed or `set`', function(assert) { + var collection = new Backbone.Collection(); + + collection.add([{id: 1}, {id: 1}]); + assert.equal(collection.length, 1); + assert.equal(collection.models.length, 1); + + collection.set([{id: 1}, {id: 1}]); + assert.equal(collection.length, 1); + assert.equal(collection.models.length, 1); + }); + + QUnit.test('#3020: #set with {add: false} should not throw.', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection; + collection.set([{id: 1}], {add: false}); + assert.strictEqual(collection.length, 0); + assert.strictEqual(collection.models.length, 0); + }); + + QUnit.test('create with wait, model instance, #3028', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection(); + var model = new Backbone.Model({id: 1}); + model.sync = function(){ + assert.equal(this.collection, collection); + }; + collection.create(model, {wait: true}); + }); + + QUnit.test('modelId', function(assert) { + var Stooge = Backbone.Model.extend(); + var StoogeCollection = Backbone.Collection.extend({model: Stooge}); + + // Default to using `Collection::model::idAttribute`. + assert.equal(StoogeCollection.prototype.modelId({id: 1}), 1); + Stooge.prototype.idAttribute = '_id'; + assert.equal(StoogeCollection.prototype.modelId({_id: 1}), 1); + }); + + QUnit.test('Polymorphic models work with "simple" constructors', function(assert) { + var A = Backbone.Model.extend(); + var B = Backbone.Model.extend(); + var C = Backbone.Collection.extend({ + model: function(attrs) { + return attrs.type === 'a' ? new A(attrs) : new B(attrs); + } + }); + var collection = new C([{id: 1, type: 'a'}, {id: 2, type: 'b'}]); + assert.equal(collection.length, 2); + assert.ok(collection.at(0) instanceof A); + assert.equal(collection.at(0).id, 1); + assert.ok(collection.at(1) instanceof B); + assert.equal(collection.at(1).id, 2); + }); + + QUnit.test('Polymorphic models work with "advanced" constructors', function(assert) { + var A = Backbone.Model.extend({idAttribute: '_id'}); + var B = Backbone.Model.extend({idAttribute: '_id'}); + var C = Backbone.Collection.extend({ + model: Backbone.Model.extend({ + constructor: function(attrs) { + return attrs.type === 'a' ? new A(attrs) : new B(attrs); + }, + + idAttribute: '_id' + }) + }); + var collection = new C([{_id: 1, type: 'a'}, {_id: 2, type: 'b'}]); + assert.equal(collection.length, 2); + assert.ok(collection.at(0) instanceof A); + assert.equal(collection.at(0), collection.get(1)); + assert.ok(collection.at(1) instanceof B); + assert.equal(collection.at(1), collection.get(2)); + + C = Backbone.Collection.extend({ + model: function(attrs) { + return attrs.type === 'a' ? new A(attrs) : new B(attrs); + }, + + modelId: function(attrs) { + return attrs.type + '-' + attrs.id; + } + }); + collection = new C([{id: 1, type: 'a'}, {id: 1, type: 'b'}]); + assert.equal(collection.length, 2); + assert.ok(collection.at(0) instanceof A); + assert.equal(collection.at(0), collection.get('a-1')); + assert.ok(collection.at(1) instanceof B); + assert.equal(collection.at(1), collection.get('b-1')); + }); + + QUnit.test('Collection with polymorphic models receives default id from modelId', function(assert) { + assert.expect(6); + // When the polymorphic models use 'id' for the idAttribute, all is fine. + var C1 = Backbone.Collection.extend({ + model: function(attrs) { + return new Backbone.Model(attrs); + } + }); + var c1 = new C1({id: 1}); + assert.equal(c1.get(1).id, 1); + assert.equal(c1.modelId({id: 1}), 1); + + // If the polymorphic models define their own idAttribute, + // the modelId method should be overridden, for the reason below. + var M = Backbone.Model.extend({ + idAttribute: '_id' + }); + var C2 = Backbone.Collection.extend({ + model: function(attrs) { + return new M(attrs); + } + }); + var c2 = new C2({_id: 1}); + assert.equal(c2.get(1), void 0); + assert.equal(c2.modelId(c2.at(0).attributes), void 0); + var m = new M({_id: 2}); + c2.add(m); + assert.equal(c2.get(2), void 0); + assert.equal(c2.modelId(m.attributes), void 0); + }); + + QUnit.test('#3039 #3951: adding at index fires with correct at', function(assert) { + assert.expect(4); + var collection = new Backbone.Collection([{val: 0}, {val: 4}]); + collection.on('add', function(model, coll, options) { + assert.equal(model.get('val'), options.index); + }); + collection.add([{val: 1}, {val: 2}, {val: 3}], {at: 1}); + collection.add({val: 5}, {at: 10}); + }); + + QUnit.test('#3039: index is not sent when at is not specified', function(assert) { + assert.expect(2); + var collection = new Backbone.Collection([{at: 0}]); + collection.on('add', function(model, coll, options) { + assert.equal(undefined, options.index); + }); + collection.add([{at: 1}, {at: 2}]); + }); + + QUnit.test('#3199 - Order changing should trigger a sort', function(assert) { + assert.expect(1); + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.on('sort', function() { + assert.ok(true); + }); + collection.set([{id: 3}, {id: 2}, {id: 1}]); + }); + + QUnit.test('#3199 - Adding a model should trigger a sort', function(assert) { + assert.expect(1); + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.on('sort', function() { + assert.ok(true); + }); + collection.set([{id: 1}, {id: 2}, {id: 3}, {id: 0}]); + }); + + QUnit.test('#3199 - Order not changing should not trigger a sort', function(assert) { + assert.expect(0); + var one = new Backbone.Model({id: 1}); + var two = new Backbone.Model({id: 2}); + var three = new Backbone.Model({id: 3}); + var collection = new Backbone.Collection([one, two, three]); + collection.on('sort', function() { + assert.ok(false); + }); + collection.set([{id: 1}, {id: 2}, {id: 3}]); + }); + + QUnit.test('add supports negative indexes', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}]); + collection.add([{id: 2}, {id: 3}], {at: -1}); + collection.add([{id: 2.5}], {at: -2}); + collection.add([{id: 0.5}], {at: -6}); + assert.equal(collection.pluck('id').join(','), '0.5,1,2,2.5,3'); + }); + + QUnit.test('#set accepts options.at as a string', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.add([{id: 3}], {at: '1'}); + assert.deepEqual(collection.pluck('id'), [1, 3, 2]); + }); + + QUnit.test('adding multiple models triggers `update` event once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection; + collection.on('update', function() { assert.ok(true); }); + collection.add([{id: 1}, {id: 2}, {id: 3}]); + }); + + QUnit.test('removing models triggers `update` event once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}]); + collection.on('update', function() { assert.ok(true); }); + collection.remove([{id: 1}, {id: 2}]); + }); + + QUnit.test('remove does not trigger `update` when nothing removed', function(assert) { + assert.expect(0); + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.on('update', function() { assert.ok(false); }); + collection.remove([{id: 3}]); + }); + + QUnit.test('set triggers `set` event once', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.on('update', function() { assert.ok(true); }); + collection.set([{id: 1}, {id: 3}]); + }); + + QUnit.test('set does not trigger `update` event when nothing added nor removed', function(assert) { + var collection = new Backbone.Collection([{id: 1}, {id: 2}]); + collection.on('update', function(coll, options) { + assert.equal(options.changes.added.length, 0); + assert.equal(options.changes.removed.length, 0); + assert.equal(options.changes.merged.length, 2); + }); + collection.set([{id: 1}, {id: 2}]); + }); + + QUnit.test('#3610 - invoke collects arguments', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend({ + method: function(x, y, z) { + assert.equal(x, 1); + assert.equal(y, 2); + assert.equal(z, 3); + } + }); + var Collection = Backbone.Collection.extend({ + model: Model + }); + var collection = new Collection([{id: 1}]); + collection.invoke('method', 1, 2, 3); + }); + + QUnit.test('#3662 - triggering change without model will not error', function(assert) { + assert.expect(1); + var collection = new Backbone.Collection([{id: 1}]); + var model = collection.first(); + collection.on('change', function(m) { + assert.equal(m, undefined); + }); + model.trigger('change'); + }); + + QUnit.test('#3871 - falsy parse result creates empty collection', function(assert) { + var collection = new (Backbone.Collection.extend({ + parse: function(data, options) {} + })); + collection.set('', {parse: true}); + assert.equal(collection.length, 0); + }); + + QUnit.test("#3711 - remove's `update` event returns one removed model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.deepEqual(changed.added, []); + assert.deepEqual(changed.merged, []); + assert.strictEqual(changed.removed[0], model); + }); + collection.remove(model); + }); + + QUnit.test("#3711 - remove's `update` event returns multiple removed models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.deepEqual(changed.added, []); + assert.deepEqual(changed.merged, []); + assert.ok(changed.removed.length === 2); + + assert.ok(_.indexOf(changed.removed, model) > -1 && _.indexOf(changed.removed, model2) > -1); + }); + collection.remove([model, model2]); + }); + + QUnit.test("#3711 - set's `update` event returns one added model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection(); + collection.on('update', function(context, options) { + var addedModels = options.changes.added; + assert.ok(addedModels.length === 1); + assert.strictEqual(addedModels[0], model); + }); + collection.set(model); + }); + + QUnit.test("#3711 - set's `update` event returns multiple added models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var collection = new Backbone.Collection(); + collection.on('update', function(context, options) { + var addedModels = options.changes.added; + assert.ok(addedModels.length === 2); + assert.strictEqual(addedModels[0], model); + assert.strictEqual(addedModels[1], model2); + }); + collection.set([model, model2]); + }); + + QUnit.test("#3711 - set's `update` event returns one removed model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model3 = new Backbone.Model({id: 3, title: 'My Last Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + var changed = options.changes; + assert.equal(changed.added.length, 2); + assert.equal(changed.merged.length, 0); + assert.ok(changed.removed.length === 1); + assert.strictEqual(changed.removed[0], model); + }); + collection.set([model2, model3]); + }); + + QUnit.test("#3711 - set's `update` event returns multiple removed models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model3 = new Backbone.Model({id: 3, title: 'My Last Post'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var removedModels = options.changes.removed; + assert.ok(removedModels.length === 2); + assert.strictEqual(removedModels[0], model); + assert.strictEqual(removedModels[1], model2); + }); + collection.set([model3]); + }); + + QUnit.test("#3711 - set's `update` event returns one merged model", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var mergedModels = options.changes.merged; + assert.ok(mergedModels.length === 1); + assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title')); + }); + collection.set([model2Update]); + }); + + QUnit.test("#3711 - set's `update` event returns multiple merged models", function(assert) { + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var modelUpdate = new Backbone.Model({id: 1, title: 'First Post V2'}); + var model2 = new Backbone.Model({id: 2, title: 'Second Post'}); + var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'}); + var collection = new Backbone.Collection([model, model2]); + collection.on('update', function(context, options) { + var mergedModels = options.changes.merged; + assert.ok(mergedModels.length === 2); + assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title')); + assert.strictEqual(mergedModels[1].get('title'), modelUpdate.get('title')); + }); + collection.set([model2Update, modelUpdate]); + }); + + QUnit.test("#3711 - set's `update` event should not be triggered adding a model which already exists exactly alike", function(assert) { + var fired = false; + var model = new Backbone.Model({id: 1, title: 'First Post'}); + var collection = new Backbone.Collection([model]); + collection.on('update', function(context, options) { + fired = true; + }); + collection.set([model]); + assert.equal(fired, false); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/events.js b/public/vendor/lodash/vendor/backbone/test/events.js new file mode 100644 index 00000000000..ec1e5474fa5 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/events.js @@ -0,0 +1,706 @@ +(function(QUnit) { + + QUnit.module('Backbone.Events'); + + QUnit.test('on and trigger', function(assert) { + assert.expect(2); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + obj.on('event', function() { obj.counter += 1; }); + obj.trigger('event'); + assert.equal(obj.counter, 1, 'counter should be incremented.'); + obj.trigger('event'); + obj.trigger('event'); + obj.trigger('event'); + obj.trigger('event'); + assert.equal(obj.counter, 5, 'counter should be incremented five times.'); + }); + + QUnit.test('binding and triggering multiple events', function(assert) { + assert.expect(4); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + obj.on('a b c', function() { obj.counter += 1; }); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 3); + + obj.trigger('c'); + assert.equal(obj.counter, 4); + + obj.off('a c'); + obj.trigger('a b c'); + assert.equal(obj.counter, 5); + }); + + QUnit.test('binding and triggering with event maps', function(assert) { + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + var increment = function() { + this.counter += 1; + }; + + obj.on({ + a: increment, + b: increment, + c: increment + }, obj); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 3); + + obj.trigger('c'); + assert.equal(obj.counter, 4); + + obj.off({ + a: increment, + c: increment + }, obj); + obj.trigger('a b c'); + assert.equal(obj.counter, 5); + }); + + QUnit.test('binding and triggering multiple event names with event maps', function(assert) { + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + var increment = function() { + this.counter += 1; + }; + + obj.on({ + 'a b c': increment + }); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 3); + + obj.trigger('c'); + assert.equal(obj.counter, 4); + + obj.off({ + 'a c': increment + }); + obj.trigger('a b c'); + assert.equal(obj.counter, 5); + }); + + QUnit.test('binding and trigger with event maps context', function(assert) { + assert.expect(2); + var obj = {counter: 0}; + var context = {}; + _.extend(obj, Backbone.Events); + + obj.on({ + a: function() { + assert.strictEqual(this, context, 'defaults `context` to `callback` param'); + } + }, context).trigger('a'); + + obj.off().on({ + a: function() { + assert.strictEqual(this, context, 'will not override explicit `context` param'); + } + }, this, context).trigger('a'); + }); + + QUnit.test('listenTo and stopListening', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenTo(b, 'all', function(){ assert.ok(true); }); + b.trigger('anything'); + a.listenTo(b, 'all', function(){ assert.ok(false); }); + a.stopListening(); + b.trigger('anything'); + }); + + QUnit.test('listenTo and stopListening with event maps', function(assert) { + assert.expect(4); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var cb = function(){ assert.ok(true); }; + a.listenTo(b, {event: cb}); + b.trigger('event'); + a.listenTo(b, {event2: cb}); + b.on('event2', cb); + a.stopListening(b, {event2: cb}); + b.trigger('event event2'); + a.stopListening(); + b.trigger('event event2'); + }); + + QUnit.test('stopListening with omitted args', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var cb = function() { assert.ok(true); }; + a.listenTo(b, 'event', cb); + b.on('event', cb); + a.listenTo(b, 'event2', cb); + a.stopListening(null, {event: cb}); + b.trigger('event event2'); + b.off(); + a.listenTo(b, 'event event2', cb); + a.stopListening(null, 'event'); + a.stopListening(); + b.trigger('event2'); + }); + + QUnit.test('listenToOnce', function(assert) { + assert.expect(2); + // Same as the previous test, but we use once rather than having to explicitly unbind + var obj = {counterA: 0, counterB: 0}; + _.extend(obj, Backbone.Events); + var incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; + var incrB = function(){ obj.counterB += 1; }; + obj.listenToOnce(obj, 'event', incrA); + obj.listenToOnce(obj, 'event', incrB); + obj.trigger('event'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + }); + + QUnit.test('listenToOnce and stopListening', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, 'all', function() { assert.ok(true); }); + b.trigger('anything'); + b.trigger('anything'); + a.listenToOnce(b, 'all', function() { assert.ok(false); }); + a.stopListening(); + b.trigger('anything'); + }); + + QUnit.test('listenTo, listenToOnce and stopListening', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, 'all', function() { assert.ok(true); }); + b.trigger('anything'); + b.trigger('anything'); + a.listenTo(b, 'all', function() { assert.ok(false); }); + a.stopListening(); + b.trigger('anything'); + }); + + QUnit.test('listenTo and stopListening with event maps', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenTo(b, {change: function(){ assert.ok(true); }}); + b.trigger('change'); + a.listenTo(b, {change: function(){ assert.ok(false); }}); + a.stopListening(); + b.trigger('change'); + }); + + QUnit.test('listenTo yourself', function(assert) { + assert.expect(1); + var e = _.extend({}, Backbone.Events); + e.listenTo(e, 'foo', function(){ assert.ok(true); }); + e.trigger('foo'); + }); + + QUnit.test('listenTo yourself cleans yourself up with stopListening', function(assert) { + assert.expect(1); + var e = _.extend({}, Backbone.Events); + e.listenTo(e, 'foo', function(){ assert.ok(true); }); + e.trigger('foo'); + e.stopListening(); + e.trigger('foo'); + }); + + QUnit.test('stopListening cleans up references', function(assert) { + assert.expect(12); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var fn = function() {}; + b.on('event', fn); + a.listenTo(b, 'event', fn).stopListening(); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn).stopListening(b); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn).stopListening(b, 'event'); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn).stopListening(b, 'event', fn); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + }); + + QUnit.test('stopListening cleans up references from listenToOnce', function(assert) { + assert.expect(12); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var fn = function() {}; + b.on('event', fn); + a.listenToOnce(b, 'event', fn).stopListening(); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenToOnce(b, 'event', fn).stopListening(b); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenToOnce(b, 'event', fn).stopListening(b, 'event'); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + a.listenToOnce(b, 'event', fn).stopListening(b, 'event', fn); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._events.event), 1); + assert.equal(_.size(b._listeners), 0); + }); + + QUnit.test('listenTo and off cleaning up references', function(assert) { + assert.expect(8); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + var fn = function() {}; + a.listenTo(b, 'event', fn); + b.off(); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn); + b.off('event'); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn); + b.off(null, fn); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + a.listenTo(b, 'event', fn); + b.off(null, null, a); + assert.equal(_.size(a._listeningTo), 0); + assert.equal(_.size(b._listeners), 0); + }); + + QUnit.test('listenTo and stopListening cleaning up references', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenTo(b, 'all', function(){ assert.ok(true); }); + b.trigger('anything'); + a.listenTo(b, 'other', function(){ assert.ok(false); }); + a.stopListening(b, 'other'); + a.stopListening(b, 'all'); + assert.equal(_.size(a._listeningTo), 0); + }); + + QUnit.test('listenToOnce without context cleans up references after the event has fired', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, 'all', function(){ assert.ok(true); }); + b.trigger('anything'); + assert.equal(_.size(a._listeningTo), 0); + }); + + QUnit.test('listenToOnce with event maps cleans up references', function(assert) { + assert.expect(2); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, { + one: function() { assert.ok(true); }, + two: function() { assert.ok(false); } + }); + b.trigger('one'); + assert.equal(_.size(a._listeningTo), 1); + }); + + QUnit.test('listenToOnce with event maps binds the correct `this`', function(assert) { + assert.expect(1); + var a = _.extend({}, Backbone.Events); + var b = _.extend({}, Backbone.Events); + a.listenToOnce(b, { + one: function() { assert.ok(this === a); }, + two: function() { assert.ok(false); } + }); + b.trigger('one'); + }); + + QUnit.test("listenTo with empty callback doesn't throw an error", function(assert) { + assert.expect(1); + var e = _.extend({}, Backbone.Events); + e.listenTo(e, 'foo', null); + e.trigger('foo'); + assert.ok(true); + }); + + QUnit.test('trigger all for each event', function(assert) { + assert.expect(3); + var a, b, obj = {counter: 0}; + _.extend(obj, Backbone.Events); + obj.on('all', function(event) { + obj.counter++; + if (event === 'a') a = true; + if (event === 'b') b = true; + }) + .trigger('a b'); + assert.ok(a); + assert.ok(b); + assert.equal(obj.counter, 2); + }); + + QUnit.test('on, then unbind all functions', function(assert) { + assert.expect(1); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + var callback = function() { obj.counter += 1; }; + obj.on('event', callback); + obj.trigger('event'); + obj.off('event'); + obj.trigger('event'); + assert.equal(obj.counter, 1, 'counter should have only been incremented once.'); + }); + + QUnit.test('bind two callbacks, unbind only one', function(assert) { + assert.expect(2); + var obj = {counterA: 0, counterB: 0}; + _.extend(obj, Backbone.Events); + var callback = function() { obj.counterA += 1; }; + obj.on('event', callback); + obj.on('event', function() { obj.counterB += 1; }); + obj.trigger('event'); + obj.off('event', callback); + obj.trigger('event'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 2, 'counterB should have been incremented twice.'); + }); + + QUnit.test('unbind a callback in the midst of it firing', function(assert) { + assert.expect(1); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + var callback = function() { + obj.counter += 1; + obj.off('event', callback); + }; + obj.on('event', callback); + obj.trigger('event'); + obj.trigger('event'); + obj.trigger('event'); + assert.equal(obj.counter, 1, 'the callback should have been unbound.'); + }); + + QUnit.test('two binds that unbind themeselves', function(assert) { + assert.expect(2); + var obj = {counterA: 0, counterB: 0}; + _.extend(obj, Backbone.Events); + var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); }; + var incrB = function(){ obj.counterB += 1; obj.off('event', incrB); }; + obj.on('event', incrA); + obj.on('event', incrB); + obj.trigger('event'); + obj.trigger('event'); + obj.trigger('event'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + }); + + QUnit.test('bind a callback with a default context when none supplied', function(assert) { + assert.expect(1); + var obj = _.extend({ + assertTrue: function() { + assert.equal(this, obj, '`this` was bound to the callback'); + } + }, Backbone.Events); + + obj.once('event', obj.assertTrue); + obj.trigger('event'); + }); + + QUnit.test('bind a callback with a supplied context', function(assert) { + assert.expect(1); + var TestClass = function() { + return this; + }; + TestClass.prototype.assertTrue = function() { + assert.ok(true, '`this` was bound to the callback'); + }; + + var obj = _.extend({}, Backbone.Events); + obj.on('event', function() { this.assertTrue(); }, new TestClass); + obj.trigger('event'); + }); + + QUnit.test('nested trigger with unbind', function(assert) { + assert.expect(1); + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); }; + var incr2 = function(){ obj.counter += 1; }; + obj.on('event', incr1); + obj.on('event', incr2); + obj.trigger('event'); + assert.equal(obj.counter, 3, 'counter should have been incremented three times'); + }); + + QUnit.test('callback list is not altered during trigger', function(assert) { + assert.expect(2); + var counter = 0, obj = _.extend({}, Backbone.Events); + var incr = function(){ counter++; }; + var incrOn = function(){ obj.on('event all', incr); }; + var incrOff = function(){ obj.off('event all', incr); }; + + obj.on('event all', incrOn).trigger('event'); + assert.equal(counter, 0, 'on does not alter callback list'); + + obj.off().on('event', incrOff).on('event all', incr).trigger('event'); + assert.equal(counter, 2, 'off does not alter callback list'); + }); + + QUnit.test("#1282 - 'all' callback list is retrieved after each event.", function(assert) { + assert.expect(1); + var counter = 0; + var obj = _.extend({}, Backbone.Events); + var incr = function(){ counter++; }; + obj.on('x', function() { + obj.on('y', incr).on('all', incr); + }) + .trigger('x y'); + assert.strictEqual(counter, 2); + }); + + QUnit.test('if no callback is provided, `on` is a noop', function(assert) { + assert.expect(0); + _.extend({}, Backbone.Events).on('test').trigger('test'); + }); + + QUnit.test('if callback is truthy but not a function, `on` should throw an error just like jQuery', function(assert) { + assert.expect(1); + var view = _.extend({}, Backbone.Events).on('test', 'noop'); + assert.raises(function() { + view.trigger('test'); + }); + }); + + QUnit.test('remove all events for a specific context', function(assert) { + assert.expect(4); + var obj = _.extend({}, Backbone.Events); + obj.on('x y all', function() { assert.ok(true); }); + obj.on('x y all', function() { assert.ok(false); }, obj); + obj.off(null, null, obj); + obj.trigger('x y'); + }); + + QUnit.test('remove all events for a specific callback', function(assert) { + assert.expect(4); + var obj = _.extend({}, Backbone.Events); + var success = function() { assert.ok(true); }; + var fail = function() { assert.ok(false); }; + obj.on('x y all', success); + obj.on('x y all', fail); + obj.off(null, fail); + obj.trigger('x y'); + }); + + QUnit.test('#1310 - off does not skip consecutive events', function(assert) { + assert.expect(0); + var obj = _.extend({}, Backbone.Events); + obj.on('event', function() { assert.ok(false); }, obj); + obj.on('event', function() { assert.ok(false); }, obj); + obj.off(null, null, obj); + obj.trigger('event'); + }); + + QUnit.test('once', function(assert) { + assert.expect(2); + // Same as the previous test, but we use once rather than having to explicitly unbind + var obj = {counterA: 0, counterB: 0}; + _.extend(obj, Backbone.Events); + var incrA = function(){ obj.counterA += 1; obj.trigger('event'); }; + var incrB = function(){ obj.counterB += 1; }; + obj.once('event', incrA); + obj.once('event', incrB); + obj.trigger('event'); + assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.'); + assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.'); + }); + + QUnit.test('once variant one', function(assert) { + assert.expect(3); + var f = function(){ assert.ok(true); }; + + var a = _.extend({}, Backbone.Events).once('event', f); + var b = _.extend({}, Backbone.Events).on('event', f); + + a.trigger('event'); + + b.trigger('event'); + b.trigger('event'); + }); + + QUnit.test('once variant two', function(assert) { + assert.expect(3); + var f = function(){ assert.ok(true); }; + var obj = _.extend({}, Backbone.Events); + + obj + .once('event', f) + .on('event', f) + .trigger('event') + .trigger('event'); + }); + + QUnit.test('once with off', function(assert) { + assert.expect(0); + var f = function(){ assert.ok(true); }; + var obj = _.extend({}, Backbone.Events); + + obj.once('event', f); + obj.off('event', f); + obj.trigger('event'); + }); + + QUnit.test('once with event maps', function(assert) { + var obj = {counter: 0}; + _.extend(obj, Backbone.Events); + + var increment = function() { + this.counter += 1; + }; + + obj.once({ + a: increment, + b: increment, + c: increment + }, obj); + + obj.trigger('a'); + assert.equal(obj.counter, 1); + + obj.trigger('a b'); + assert.equal(obj.counter, 2); + + obj.trigger('c'); + assert.equal(obj.counter, 3); + + obj.trigger('a b c'); + assert.equal(obj.counter, 3); + }); + + QUnit.test('bind a callback with a supplied context using once with object notation', function(assert) { + assert.expect(1); + var obj = {counter: 0}; + var context = {}; + _.extend(obj, Backbone.Events); + + obj.once({ + a: function() { + assert.strictEqual(this, context, 'defaults `context` to `callback` param'); + } + }, context).trigger('a'); + }); + + QUnit.test('once with off only by context', function(assert) { + assert.expect(0); + var context = {}; + var obj = _.extend({}, Backbone.Events); + obj.once('event', function(){ assert.ok(false); }, context); + obj.off(null, null, context); + obj.trigger('event'); + }); + + QUnit.test('Backbone object inherits Events', function(assert) { + assert.ok(Backbone.on === Backbone.Events.on); + }); + + QUnit.test('once with asynchronous events', function(assert) { + var done = assert.async(); + assert.expect(1); + var func = _.debounce(function() { assert.ok(true); done(); }, 50); + var obj = _.extend({}, Backbone.Events).once('async', func); + + obj.trigger('async'); + obj.trigger('async'); + }); + + QUnit.test('once with multiple events.', function(assert) { + assert.expect(2); + var obj = _.extend({}, Backbone.Events); + obj.once('x y', function() { assert.ok(true); }); + obj.trigger('x y'); + }); + + QUnit.test('Off during iteration with once.', function(assert) { + assert.expect(2); + var obj = _.extend({}, Backbone.Events); + var f = function(){ this.off('event', f); }; + obj.on('event', f); + obj.once('event', function(){}); + obj.on('event', function(){ assert.ok(true); }); + + obj.trigger('event'); + obj.trigger('event'); + }); + + QUnit.test('`once` on `all` should work as expected', function(assert) { + assert.expect(1); + Backbone.once('all', function() { + assert.ok(true); + Backbone.trigger('all'); + }); + Backbone.trigger('all'); + }); + + QUnit.test('once without a callback is a noop', function(assert) { + assert.expect(0); + _.extend({}, Backbone.Events).once('event').trigger('event'); + }); + + QUnit.test('listenToOnce without a callback is a noop', function(assert) { + assert.expect(0); + var obj = _.extend({}, Backbone.Events); + obj.listenToOnce(obj, 'event').trigger('event'); + }); + + QUnit.test('event functions are chainable', function(assert) { + var obj = _.extend({}, Backbone.Events); + var obj2 = _.extend({}, Backbone.Events); + var fn = function() {}; + assert.equal(obj, obj.trigger('noeventssetyet')); + assert.equal(obj, obj.off('noeventssetyet')); + assert.equal(obj, obj.stopListening('noeventssetyet')); + assert.equal(obj, obj.on('a', fn)); + assert.equal(obj, obj.once('c', fn)); + assert.equal(obj, obj.trigger('a')); + assert.equal(obj, obj.listenTo(obj2, 'a', fn)); + assert.equal(obj, obj.listenToOnce(obj2, 'b', fn)); + assert.equal(obj, obj.off('a c')); + assert.equal(obj, obj.stopListening(obj2, 'a')); + assert.equal(obj, obj.stopListening()); + }); + + QUnit.test('#3448 - listenToOnce with space-separated events', function(assert) { + assert.expect(2); + var one = _.extend({}, Backbone.Events); + var two = _.extend({}, Backbone.Events); + var count = 1; + one.listenToOnce(two, 'x y', function(n) { assert.ok(n === count++); }); + two.trigger('x', 1); + two.trigger('x', 1); + two.trigger('y', 2); + two.trigger('y', 2); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/model.js b/public/vendor/lodash/vendor/backbone/test/model.js new file mode 100644 index 00000000000..440047fef65 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/model.js @@ -0,0 +1,1448 @@ +(function(QUnit) { + + var ProxyModel = Backbone.Model.extend(); + var Klass = Backbone.Collection.extend({ + url: function() { return '/collection'; } + }); + var doc, collection; + + QUnit.module('Backbone.Model', { + + beforeEach: function(assert) { + doc = new ProxyModel({ + id: '1-the-tempest', + title: 'The Tempest', + author: 'Bill Shakespeare', + length: 123 + }); + collection = new Klass(); + collection.add(doc); + } + + }); + + QUnit.test('initialize', function(assert) { + assert.expect(3); + var Model = Backbone.Model.extend({ + initialize: function() { + this.one = 1; + assert.equal(this.collection, collection); + } + }); + var model = new Model({}, {collection: collection}); + assert.equal(model.one, 1); + assert.equal(model.collection, collection); + }); + + QUnit.test('Object.prototype properties are overridden by attributes', function(assert) { + assert.expect(1); + var model = new Backbone.Model({hasOwnProperty: true}); + assert.equal(model.get('hasOwnProperty'), true); + }); + + QUnit.test('initialize with attributes and options', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + initialize: function(attributes, options) { + this.one = options.one; + } + }); + var model = new Model({}, {one: 1}); + assert.equal(model.one, 1); + }); + + QUnit.test('initialize with parsed attributes', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + parse: function(attrs) { + attrs.value += 1; + return attrs; + } + }); + var model = new Model({value: 1}, {parse: true}); + assert.equal(model.get('value'), 2); + }); + + + QUnit.test('preinitialize', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({ + + preinitialize: function() { + this.one = 1; + } + }); + var model = new Model({}, {collection: collection}); + assert.equal(model.one, 1); + assert.equal(model.collection, collection); + }); + + QUnit.test('preinitialize occurs before the model is set up', function(assert) { + assert.expect(6); + var Model = Backbone.Model.extend({ + + preinitialize: function() { + assert.equal(this.collection, undefined); + assert.equal(this.cid, undefined); + assert.equal(this.id, undefined); + } + }); + var model = new Model({id: 'foo'}, {collection: collection}); + assert.equal(model.collection, collection); + assert.equal(model.id, 'foo'); + assert.notEqual(model.cid, undefined); + }); + + QUnit.test('parse can return null', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + parse: function(attrs) { + attrs.value += 1; + return null; + } + }); + var model = new Model({value: 1}, {parse: true}); + assert.equal(JSON.stringify(model.toJSON()), '{}'); + }); + + QUnit.test('url', function(assert) { + assert.expect(3); + doc.urlRoot = null; + assert.equal(doc.url(), '/collection/1-the-tempest'); + doc.collection.url = '/collection/'; + assert.equal(doc.url(), '/collection/1-the-tempest'); + doc.collection = null; + assert.raises(function() { doc.url(); }); + doc.collection = collection; + }); + + QUnit.test('url when using urlRoot, and uri encoding', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({ + urlRoot: '/collection' + }); + var model = new Model(); + assert.equal(model.url(), '/collection'); + model.set({id: '+1+'}); + assert.equal(model.url(), '/collection/%2B1%2B'); + }); + + QUnit.test('url when using urlRoot as a function to determine urlRoot at runtime', function(assert) { + assert.expect(2); + var Model = Backbone.Model.extend({ + urlRoot: function() { + return '/nested/' + this.get('parentId') + '/collection'; + } + }); + + var model = new Model({parentId: 1}); + assert.equal(model.url(), '/nested/1/collection'); + model.set({id: 2}); + assert.equal(model.url(), '/nested/1/collection/2'); + }); + + QUnit.test('underscore methods', function(assert) { + assert.expect(5); + var model = new Backbone.Model({foo: 'a', bar: 'b', baz: 'c'}); + var model2 = model.clone(); + assert.deepEqual(model.keys(), ['foo', 'bar', 'baz']); + assert.deepEqual(model.values(), ['a', 'b', 'c']); + assert.deepEqual(model.invert(), {a: 'foo', b: 'bar', c: 'baz'}); + assert.deepEqual(model.pick('foo', 'baz'), {foo: 'a', baz: 'c'}); + assert.deepEqual(model.omit('foo', 'bar'), {baz: 'c'}); + }); + + QUnit.test('chain', function(assert) { + var model = new Backbone.Model({a: 0, b: 1, c: 2}); + assert.deepEqual(model.chain().pick('a', 'b', 'c').values().compact().value(), [1, 2]); + }); + + QUnit.test('clone', function(assert) { + assert.expect(10); + var a = new Backbone.Model({foo: 1, bar: 2, baz: 3}); + var b = a.clone(); + assert.equal(a.get('foo'), 1); + assert.equal(a.get('bar'), 2); + assert.equal(a.get('baz'), 3); + assert.equal(b.get('foo'), a.get('foo'), 'Foo should be the same on the clone.'); + assert.equal(b.get('bar'), a.get('bar'), 'Bar should be the same on the clone.'); + assert.equal(b.get('baz'), a.get('baz'), 'Baz should be the same on the clone.'); + a.set({foo: 100}); + assert.equal(a.get('foo'), 100); + assert.equal(b.get('foo'), 1, 'Changing a parent attribute does not change the clone.'); + + var foo = new Backbone.Model({p: 1}); + var bar = new Backbone.Model({p: 2}); + bar.set(foo.clone().attributes, {unset: true}); + assert.equal(foo.get('p'), 1); + assert.equal(bar.get('p'), undefined); + }); + + QUnit.test('isNew', function(assert) { + assert.expect(6); + var a = new Backbone.Model({foo: 1, bar: 2, baz: 3}); + assert.ok(a.isNew(), 'it should be new'); + a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: -5}); + assert.ok(!a.isNew(), 'any defined ID is legal, negative or positive'); + a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: 0}); + assert.ok(!a.isNew(), 'any defined ID is legal, including zero'); + assert.ok(new Backbone.Model().isNew(), 'is true when there is no id'); + assert.ok(!new Backbone.Model({id: 2}).isNew(), 'is false for a positive integer'); + assert.ok(!new Backbone.Model({id: -5}).isNew(), 'is false for a negative integer'); + }); + + QUnit.test('get', function(assert) { + assert.expect(2); + assert.equal(doc.get('title'), 'The Tempest'); + assert.equal(doc.get('author'), 'Bill Shakespeare'); + }); + + QUnit.test('escape', function(assert) { + assert.expect(5); + assert.equal(doc.escape('title'), 'The Tempest'); + doc.set({audience: 'Bill & Bob'}); + assert.equal(doc.escape('audience'), 'Bill & Bob'); + doc.set({audience: 'Tim > Joan'}); + assert.equal(doc.escape('audience'), 'Tim > Joan'); + doc.set({audience: 10101}); + assert.equal(doc.escape('audience'), '10101'); + doc.unset('audience'); + assert.equal(doc.escape('audience'), ''); + }); + + QUnit.test('has', function(assert) { + assert.expect(10); + var model = new Backbone.Model(); + + assert.strictEqual(model.has('name'), false); + + model.set({ + '0': 0, + '1': 1, + 'true': true, + 'false': false, + 'empty': '', + 'name': 'name', + 'null': null, + 'undefined': undefined + }); + + assert.strictEqual(model.has('0'), true); + assert.strictEqual(model.has('1'), true); + assert.strictEqual(model.has('true'), true); + assert.strictEqual(model.has('false'), true); + assert.strictEqual(model.has('empty'), true); + assert.strictEqual(model.has('name'), true); + + model.unset('name'); + + assert.strictEqual(model.has('name'), false); + assert.strictEqual(model.has('null'), false); + assert.strictEqual(model.has('undefined'), false); + }); + + QUnit.test('matches', function(assert) { + assert.expect(4); + var model = new Backbone.Model(); + + assert.strictEqual(model.matches({name: 'Jonas', cool: true}), false); + + model.set({name: 'Jonas', cool: true}); + + assert.strictEqual(model.matches({name: 'Jonas'}), true); + assert.strictEqual(model.matches({name: 'Jonas', cool: true}), true); + assert.strictEqual(model.matches({name: 'Jonas', cool: false}), false); + }); + + QUnit.test('matches with predicate', function(assert) { + var model = new Backbone.Model({a: 0}); + + assert.strictEqual(model.matches(function(attr) { + return attr.a > 1 && attr.b != null; + }), false); + + model.set({a: 3, b: true}); + + assert.strictEqual(model.matches(function(attr) { + return attr.a > 1 && attr.b != null; + }), true); + }); + + QUnit.test('set and unset', function(assert) { + assert.expect(8); + var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3}); + var changeCount = 0; + a.on('change:foo', function() { changeCount += 1; }); + a.set({foo: 2}); + assert.equal(a.get('foo'), 2, 'Foo should have changed.'); + assert.equal(changeCount, 1, 'Change count should have incremented.'); + // set with value that is not new shouldn't fire change event + a.set({foo: 2}); + assert.equal(a.get('foo'), 2, 'Foo should NOT have changed, still 2'); + assert.equal(changeCount, 1, 'Change count should NOT have incremented.'); + + a.validate = function(attrs) { + assert.equal(attrs.foo, void 0, 'validate:true passed while unsetting'); + }; + a.unset('foo', {validate: true}); + assert.equal(a.get('foo'), void 0, 'Foo should have changed'); + delete a.validate; + assert.equal(changeCount, 2, 'Change count should have incremented for unset.'); + + a.unset('id'); + assert.equal(a.id, undefined, 'Unsetting the id should remove the id property.'); + }); + + QUnit.test('#2030 - set with failed validate, followed by another set triggers change', function(assert) { + var attr = 0, main = 0, error = 0; + var Model = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.x > 1) { + error++; + return 'this is an error'; + } + } + }); + var model = new Model({x: 0}); + model.on('change:x', function() { attr++; }); + model.on('change', function() { main++; }); + model.set({x: 2}, {validate: true}); + model.set({x: 1}, {validate: true}); + assert.deepEqual([attr, main, error], [1, 1, 1]); + }); + + QUnit.test('set triggers changes in the correct order', function(assert) { + var value = null; + var model = new Backbone.Model; + model.on('last', function(){ value = 'last'; }); + model.on('first', function(){ value = 'first'; }); + model.trigger('first'); + model.trigger('last'); + assert.equal(value, 'last'); + }); + + QUnit.test('set falsy values in the correct order', function(assert) { + assert.expect(2); + var model = new Backbone.Model({result: 'result'}); + model.on('change', function() { + assert.equal(model.changed.result, void 0); + assert.equal(model.previous('result'), false); + }); + model.set({result: void 0}, {silent: true}); + model.set({result: null}, {silent: true}); + model.set({result: false}, {silent: true}); + model.set({result: void 0}); + }); + + QUnit.test('nested set triggers with the correct options', function(assert) { + var model = new Backbone.Model(); + var o1 = {}; + var o2 = {}; + var o3 = {}; + model.on('change', function(__, options) { + switch (model.get('a')) { + case 1: + assert.equal(options, o1); + return model.set('a', 2, o2); + case 2: + assert.equal(options, o2); + return model.set('a', 3, o3); + case 3: + assert.equal(options, o3); + } + }); + model.set('a', 1, o1); + }); + + QUnit.test('multiple unsets', function(assert) { + assert.expect(1); + var i = 0; + var counter = function(){ i++; }; + var model = new Backbone.Model({a: 1}); + model.on('change:a', counter); + model.set({a: 2}); + model.unset('a'); + model.unset('a'); + assert.equal(i, 2, 'Unset does not fire an event for missing attributes.'); + }); + + QUnit.test('unset and changedAttributes', function(assert) { + assert.expect(1); + var model = new Backbone.Model({a: 1}); + model.on('change', function() { + assert.ok('a' in model.changedAttributes(), 'changedAttributes should contain unset properties'); + }); + model.unset('a'); + }); + + QUnit.test('using a non-default id attribute.', function(assert) { + assert.expect(5); + var MongoModel = Backbone.Model.extend({idAttribute: '_id'}); + var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'}); + assert.equal(model.get('id'), 'eye-dee'); + assert.equal(model.id, 25); + assert.equal(model.isNew(), false); + model.unset('_id'); + assert.equal(model.id, undefined); + assert.equal(model.isNew(), true); + }); + + QUnit.test('setting an alternative cid prefix', function(assert) { + assert.expect(4); + var Model = Backbone.Model.extend({ + cidPrefix: 'm' + }); + var model = new Model(); + + assert.equal(model.cid.charAt(0), 'm'); + + model = new Backbone.Model(); + assert.equal(model.cid.charAt(0), 'c'); + + var Collection = Backbone.Collection.extend({ + model: Model + }); + var col = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]); + + assert.equal(col.get('c6').cid.charAt(0), 'm'); + col.set([{id: 'c6', value: 'test'}], { + merge: true, + add: true, + remove: false + }); + assert.ok(col.get('c6').has('value')); + }); + + QUnit.test('set an empty string', function(assert) { + assert.expect(1); + var model = new Backbone.Model({name: 'Model'}); + model.set({name: ''}); + assert.equal(model.get('name'), ''); + }); + + QUnit.test('setting an object', function(assert) { + assert.expect(1); + var model = new Backbone.Model({ + custom: {foo: 1} + }); + model.on('change', function() { + assert.ok(1); + }); + model.set({ + custom: {foo: 1} // no change should be fired + }); + model.set({ + custom: {foo: 2} // change event should be fired + }); + }); + + QUnit.test('clear', function(assert) { + assert.expect(3); + var changed; + var model = new Backbone.Model({id: 1, name: 'Model'}); + model.on('change:name', function(){ changed = true; }); + model.on('change', function() { + var changedAttrs = model.changedAttributes(); + assert.ok('name' in changedAttrs); + }); + model.clear(); + assert.equal(changed, true); + assert.equal(model.get('name'), undefined); + }); + + QUnit.test('defaults', function(assert) { + assert.expect(9); + var Defaulted = Backbone.Model.extend({ + defaults: { + one: 1, + two: 2 + } + }); + var model = new Defaulted({two: undefined}); + assert.equal(model.get('one'), 1); + assert.equal(model.get('two'), 2); + model = new Defaulted({two: 3}); + assert.equal(model.get('one'), 1); + assert.equal(model.get('two'), 3); + Defaulted = Backbone.Model.extend({ + defaults: function() { + return { + one: 3, + two: 4 + }; + } + }); + model = new Defaulted({two: undefined}); + assert.equal(model.get('one'), 3); + assert.equal(model.get('two'), 4); + Defaulted = Backbone.Model.extend({ + defaults: {hasOwnProperty: true} + }); + model = new Defaulted(); + assert.equal(model.get('hasOwnProperty'), true); + model = new Defaulted({hasOwnProperty: undefined}); + assert.equal(model.get('hasOwnProperty'), true); + model = new Defaulted({hasOwnProperty: false}); + assert.equal(model.get('hasOwnProperty'), false); + }); + + QUnit.test('change, hasChanged, changedAttributes, previous, previousAttributes', function(assert) { + assert.expect(9); + var model = new Backbone.Model({name: 'Tim', age: 10}); + assert.deepEqual(model.changedAttributes(), false); + model.on('change', function() { + assert.ok(model.hasChanged('name'), 'name changed'); + assert.ok(!model.hasChanged('age'), 'age did not'); + assert.ok(_.isEqual(model.changedAttributes(), {name: 'Rob'}), 'changedAttributes returns the changed attrs'); + assert.equal(model.previous('name'), 'Tim'); + assert.ok(_.isEqual(model.previousAttributes(), {name: 'Tim', age: 10}), 'previousAttributes is correct'); + }); + assert.equal(model.hasChanged(), false); + assert.equal(model.hasChanged(undefined), false); + model.set({name: 'Rob'}); + assert.equal(model.get('name'), 'Rob'); + }); + + QUnit.test('changedAttributes', function(assert) { + assert.expect(3); + var model = new Backbone.Model({a: 'a', b: 'b'}); + assert.deepEqual(model.changedAttributes(), false); + assert.equal(model.changedAttributes({a: 'a'}), false); + assert.equal(model.changedAttributes({a: 'b'}).a, 'b'); + }); + + QUnit.test('change with options', function(assert) { + assert.expect(2); + var value; + var model = new Backbone.Model({name: 'Rob'}); + model.on('change', function(m, options) { + value = options.prefix + m.get('name'); + }); + model.set({name: 'Bob'}, {prefix: 'Mr. '}); + assert.equal(value, 'Mr. Bob'); + model.set({name: 'Sue'}, {prefix: 'Ms. '}); + assert.equal(value, 'Ms. Sue'); + }); + + QUnit.test('change after initialize', function(assert) { + assert.expect(1); + var changed = 0; + var attrs = {id: 1, label: 'c'}; + var obj = new Backbone.Model(attrs); + obj.on('change', function() { changed += 1; }); + obj.set(attrs); + assert.equal(changed, 0); + }); + + QUnit.test('save within change event', function(assert) { + assert.expect(1); + var env = this; + var model = new Backbone.Model({firstName: 'Taylor', lastName: 'Swift'}); + model.url = '/test'; + model.on('change', function() { + model.save(); + assert.ok(_.isEqual(env.syncArgs.model, model)); + }); + model.set({lastName: 'Hicks'}); + }); + + QUnit.test('validate after save', function(assert) { + assert.expect(2); + var lastError, model = new Backbone.Model(); + model.validate = function(attrs) { + if (attrs.admin) return "Can't change admin status."; + }; + model.sync = function(method, m, options) { + options.success.call(this, {admin: true}); + }; + model.on('invalid', function(m, error) { + lastError = error; + }); + model.save(null); + + assert.equal(lastError, "Can't change admin status."); + assert.equal(model.validationError, "Can't change admin status."); + }); + + QUnit.test('save', function(assert) { + assert.expect(2); + doc.save({title: 'Henry V'}); + assert.equal(this.syncArgs.method, 'update'); + assert.ok(_.isEqual(this.syncArgs.model, doc)); + }); + + QUnit.test('save, fetch, destroy triggers error event when an error occurs', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + model.on('error', function() { + assert.ok(true); + }); + model.sync = function(method, m, options) { + options.error(); + }; + model.save({data: 2, id: 1}); + model.fetch(); + model.destroy(); + }); + + QUnit.test('#3283 - save, fetch, destroy calls success with context', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + var obj = {}; + var options = { + context: obj, + success: function() { + assert.equal(this, obj); + } + }; + model.sync = function(method, m, opts) { + opts.success.call(opts.context); + }; + model.save({data: 2, id: 1}, options); + model.fetch(options); + model.destroy(options); + }); + + QUnit.test('#3283 - save, fetch, destroy calls error with context', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + var obj = {}; + var options = { + context: obj, + error: function() { + assert.equal(this, obj); + } + }; + model.sync = function(method, m, opts) { + opts.error.call(opts.context); + }; + model.save({data: 2, id: 1}, options); + model.fetch(options); + model.destroy(options); + }); + + QUnit.test('#3470 - save and fetch with parse false', function(assert) { + assert.expect(2); + var i = 0; + var model = new Backbone.Model(); + model.parse = function() { + assert.ok(false); + }; + model.sync = function(method, m, options) { + options.success({i: ++i}); + }; + model.fetch({parse: false}); + assert.equal(model.get('i'), i); + model.save(null, {parse: false}); + assert.equal(model.get('i'), i); + }); + + QUnit.test('save with PATCH', function(assert) { + doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4}); + doc.save(); + assert.equal(this.syncArgs.method, 'update'); + assert.equal(this.syncArgs.options.attrs, undefined); + + doc.save({b: 2, d: 4}, {patch: true}); + assert.equal(this.syncArgs.method, 'patch'); + assert.equal(_.size(this.syncArgs.options.attrs), 2); + assert.equal(this.syncArgs.options.attrs.d, 4); + assert.equal(this.syncArgs.options.attrs.a, undefined); + assert.equal(this.ajaxSettings.data, '{"b":2,"d":4}'); + }); + + QUnit.test('save with PATCH and different attrs', function(assert) { + doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}}); + assert.equal(this.syncArgs.options.attrs.D, 3); + assert.equal(this.syncArgs.options.attrs.d, undefined); + assert.equal(this.ajaxSettings.data, '{"B":1,"D":3}'); + assert.deepEqual(doc.attributes, {b: 2, d: 4}); + }); + + QUnit.test('save in positional style', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.sync = function(method, m, options) { + options.success(); + }; + model.save('title', 'Twelfth Night'); + assert.equal(model.get('title'), 'Twelfth Night'); + }); + + QUnit.test('save with non-object success response', function(assert) { + assert.expect(2); + var model = new Backbone.Model(); + model.sync = function(method, m, options) { + options.success('', options); + options.success(null, options); + }; + model.save({testing: 'empty'}, { + success: function(m) { + assert.deepEqual(m.attributes, {testing: 'empty'}); + } + }); + }); + + QUnit.test('save with wait and supplied id', function(assert) { + var Model = Backbone.Model.extend({ + urlRoot: '/collection' + }); + var model = new Model(); + model.save({id: 42}, {wait: true}); + assert.equal(this.ajaxSettings.url, '/collection/42'); + }); + + QUnit.test('save will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncModel = Backbone.Model.extend({ + sync: function(method, m, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, m, options); + }, + urlRoot: '/test' + }); + + var model = new SpecialSyncModel(); + + var onSuccess = function(m, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + model.save(null, {success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('fetch', function(assert) { + assert.expect(2); + doc.fetch(); + assert.equal(this.syncArgs.method, 'read'); + assert.ok(_.isEqual(this.syncArgs.model, doc)); + }); + + QUnit.test('fetch will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncModel = Backbone.Model.extend({ + sync: function(method, m, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, m, options); + }, + urlRoot: '/test' + }); + + var model = new SpecialSyncModel(); + + var onSuccess = function(m, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + model.fetch({success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('destroy', function(assert) { + assert.expect(3); + doc.destroy(); + assert.equal(this.syncArgs.method, 'delete'); + assert.ok(_.isEqual(this.syncArgs.model, doc)); + + var newModel = new Backbone.Model; + assert.equal(newModel.destroy(), false); + }); + + QUnit.test('destroy will pass extra options to success callback', function(assert) { + assert.expect(1); + var SpecialSyncModel = Backbone.Model.extend({ + sync: function(method, m, options) { + _.extend(options, {specialSync: true}); + return Backbone.Model.prototype.sync.call(this, method, m, options); + }, + urlRoot: '/test' + }); + + var model = new SpecialSyncModel({id: 'id'}); + + var onSuccess = function(m, response, options) { + assert.ok(options.specialSync, 'Options were passed correctly to callback'); + }; + + model.destroy({success: onSuccess}); + this.ajaxSettings.success(); + }); + + QUnit.test('non-persisted destroy', function(assert) { + assert.expect(1); + var a = new Backbone.Model({foo: 1, bar: 2, baz: 3}); + a.sync = function() { throw 'should not be called'; }; + a.destroy(); + assert.ok(true, 'non-persisted model should not call sync'); + }); + + QUnit.test('validate', function(assert) { + var lastError; + var model = new Backbone.Model(); + model.validate = function(attrs) { + if (attrs.admin !== this.get('admin')) return "Can't change admin status."; + }; + model.on('invalid', function(m, error) { + lastError = error; + }); + var result = model.set({a: 100}); + assert.equal(result, model); + assert.equal(model.get('a'), 100); + assert.equal(lastError, undefined); + result = model.set({admin: true}); + assert.equal(model.get('admin'), true); + result = model.set({a: 200, admin: false}, {validate: true}); + assert.equal(lastError, "Can't change admin status."); + assert.equal(result, false); + assert.equal(model.get('a'), 100); + }); + + QUnit.test('validate on unset and clear', function(assert) { + assert.expect(6); + var error; + var model = new Backbone.Model({name: 'One'}); + model.validate = function(attrs) { + if (!attrs.name) { + error = true; + return 'No thanks.'; + } + }; + model.set({name: 'Two'}); + assert.equal(model.get('name'), 'Two'); + assert.equal(error, undefined); + model.unset('name', {validate: true}); + assert.equal(error, true); + assert.equal(model.get('name'), 'Two'); + model.clear({validate: true}); + assert.equal(model.get('name'), 'Two'); + delete model.validate; + model.clear(); + assert.equal(model.get('name'), undefined); + }); + + QUnit.test('validate with error callback', function(assert) { + assert.expect(8); + var lastError, boundError; + var model = new Backbone.Model(); + model.validate = function(attrs) { + if (attrs.admin) return "Can't change admin status."; + }; + model.on('invalid', function(m, error) { + boundError = true; + }); + var result = model.set({a: 100}, {validate: true}); + assert.equal(result, model); + assert.equal(model.get('a'), 100); + assert.equal(model.validationError, null); + assert.equal(boundError, undefined); + result = model.set({a: 200, admin: true}, {validate: true}); + assert.equal(result, false); + assert.equal(model.get('a'), 100); + assert.equal(model.validationError, "Can't change admin status."); + assert.equal(boundError, true); + }); + + QUnit.test('defaults always extend attrs (#459)', function(assert) { + assert.expect(2); + var Defaulted = Backbone.Model.extend({ + defaults: {one: 1}, + initialize: function(attrs, opts) { + assert.equal(this.attributes.one, 1); + } + }); + var providedattrs = new Defaulted({}); + var emptyattrs = new Defaulted(); + }); + + QUnit.test('Inherit class properties', function(assert) { + assert.expect(6); + var Parent = Backbone.Model.extend({ + instancePropSame: function() {}, + instancePropDiff: function() {} + }, { + classProp: function() {} + }); + var Child = Parent.extend({ + instancePropDiff: function() {} + }); + + var adult = new Parent; + var kid = new Child; + + assert.equal(Child.classProp, Parent.classProp); + assert.notEqual(Child.classProp, undefined); + + assert.equal(kid.instancePropSame, adult.instancePropSame); + assert.notEqual(kid.instancePropSame, undefined); + + assert.notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff); + assert.notEqual(Child.prototype.instancePropDiff, undefined); + }); + + QUnit.test("Nested change events don't clobber previous attributes", function(assert) { + assert.expect(4); + new Backbone.Model() + .on('change:state', function(m, newState) { + assert.equal(m.previous('state'), undefined); + assert.equal(newState, 'hello'); + // Fire a nested change event. + m.set({other: 'whatever'}); + }) + .on('change:state', function(m, newState) { + assert.equal(m.previous('state'), undefined); + assert.equal(newState, 'hello'); + }) + .set({state: 'hello'}); + }); + + QUnit.test('hasChanged/set should use same comparison', function(assert) { + assert.expect(2); + var changed = 0, model = new Backbone.Model({a: null}); + model.on('change', function() { + assert.ok(this.hasChanged('a')); + }) + .on('change:a', function() { + changed++; + }) + .set({a: undefined}); + assert.equal(changed, 1); + }); + + QUnit.test('#582, #425, change:attribute callbacks should fire after all changes have occurred', function(assert) { + assert.expect(9); + var model = new Backbone.Model; + + var assertion = function() { + assert.equal(model.get('a'), 'a'); + assert.equal(model.get('b'), 'b'); + assert.equal(model.get('c'), 'c'); + }; + + model.on('change:a', assertion); + model.on('change:b', assertion); + model.on('change:c', assertion); + + model.set({a: 'a', b: 'b', c: 'c'}); + }); + + QUnit.test('#871, set with attributes property', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.set({attributes: true}); + assert.ok(model.has('attributes')); + }); + + QUnit.test('set value regardless of equality/change', function(assert) { + assert.expect(1); + var model = new Backbone.Model({x: []}); + var a = []; + model.set({x: a}); + assert.ok(model.get('x') === a); + }); + + QUnit.test('set same value does not trigger change', function(assert) { + assert.expect(0); + var model = new Backbone.Model({x: 1}); + model.on('change change:x', function() { assert.ok(false); }); + model.set({x: 1}); + model.set({x: 1}); + }); + + QUnit.test('unset does not fire a change for undefined attributes', function(assert) { + assert.expect(0); + var model = new Backbone.Model({x: undefined}); + model.on('change:x', function(){ assert.ok(false); }); + model.unset('x'); + }); + + QUnit.test('set: undefined values', function(assert) { + assert.expect(1); + var model = new Backbone.Model({x: undefined}); + assert.ok('x' in model.attributes); + }); + + QUnit.test('hasChanged works outside of change events, and true within', function(assert) { + assert.expect(6); + var model = new Backbone.Model({x: 1}); + model.on('change:x', function() { + assert.ok(model.hasChanged('x')); + assert.equal(model.get('x'), 1); + }); + model.set({x: 2}, {silent: true}); + assert.ok(model.hasChanged()); + assert.equal(model.hasChanged('x'), true); + model.set({x: 1}); + assert.ok(model.hasChanged()); + assert.equal(model.hasChanged('x'), true); + }); + + QUnit.test('hasChanged gets cleared on the following set', function(assert) { + assert.expect(4); + var model = new Backbone.Model; + model.set({x: 1}); + assert.ok(model.hasChanged()); + model.set({x: 1}); + assert.ok(!model.hasChanged()); + model.set({x: 2}); + assert.ok(model.hasChanged()); + model.set({}); + assert.ok(!model.hasChanged()); + }); + + QUnit.test('save with `wait` succeeds without `validate`', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.url = '/test'; + model.save({x: 1}, {wait: true}); + assert.ok(this.syncArgs.model === model); + }); + + QUnit.test("save without `wait` doesn't set invalid attributes", function(assert) { + var model = new Backbone.Model(); + model.validate = function() { return 1; }; + model.save({a: 1}); + assert.equal(model.get('a'), void 0); + }); + + QUnit.test("save doesn't validate twice", function(assert) { + var model = new Backbone.Model(); + var times = 0; + model.sync = function() {}; + model.validate = function() { ++times; }; + model.save({}); + assert.equal(times, 1); + }); + + QUnit.test('`hasChanged` for falsey keys', function(assert) { + assert.expect(2); + var model = new Backbone.Model(); + model.set({x: true}, {silent: true}); + assert.ok(!model.hasChanged(0)); + assert.ok(!model.hasChanged('')); + }); + + QUnit.test('`previous` for falsey keys', function(assert) { + assert.expect(2); + var model = new Backbone.Model({'0': true, '': true}); + model.set({'0': false, '': false}, {silent: true}); + assert.equal(model.previous(0), true); + assert.equal(model.previous(''), true); + }); + + QUnit.test('`save` with `wait` sends correct attributes', function(assert) { + assert.expect(5); + var changed = 0; + var model = new Backbone.Model({x: 1, y: 2}); + model.url = '/test'; + model.on('change:x', function() { changed++; }); + model.save({x: 3}, {wait: true}); + assert.deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2}); + assert.equal(model.get('x'), 1); + assert.equal(changed, 0); + this.syncArgs.options.success({}); + assert.equal(model.get('x'), 3); + assert.equal(changed, 1); + }); + + QUnit.test("a failed `save` with `wait` doesn't leave attributes behind", function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.url = '/test'; + model.save({x: 1}, {wait: true}); + assert.equal(model.get('x'), void 0); + }); + + QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) { + assert.expect(2); + var model = new Backbone.Model({x: 1, y: 2}); + model.sync = function(method, m, options) { + options.success(); + }; + model.on('change:x', function() { assert.ok(true); }); + model.save({x: 3}, {wait: true}); + assert.equal(model.get('x'), 3); + }); + + QUnit.test('save with wait validates attributes', function(assert) { + var model = new Backbone.Model(); + model.url = '/test'; + model.validate = function() { assert.ok(true); }; + model.save({x: 1}, {wait: true}); + }); + + QUnit.test('save turns on parse flag', function(assert) { + var Model = Backbone.Model.extend({ + sync: function(method, m, options) { assert.ok(options.parse); } + }); + new Model().save(); + }); + + QUnit.test("nested `set` during `'change:attr'`", function(assert) { + assert.expect(2); + var events = []; + var model = new Backbone.Model(); + model.on('all', function(event) { events.push(event); }); + model.on('change', function() { + model.set({z: true}, {silent: true}); + }); + model.on('change:x', function() { + model.set({y: true}); + }); + model.set({x: true}); + assert.deepEqual(events, ['change:y', 'change:x', 'change']); + events = []; + model.set({z: true}); + assert.deepEqual(events, []); + }); + + QUnit.test('nested `change` only fires once', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change', function() { + assert.ok(true); + model.set({x: true}); + }); + model.set({x: true}); + }); + + QUnit.test("nested `set` during `'change'`", function(assert) { + assert.expect(6); + var count = 0; + var model = new Backbone.Model(); + model.on('change', function() { + switch (count++) { + case 0: + assert.deepEqual(this.changedAttributes(), {x: true}); + assert.equal(model.previous('x'), undefined); + model.set({y: true}); + break; + case 1: + assert.deepEqual(this.changedAttributes(), {x: true, y: true}); + assert.equal(model.previous('x'), undefined); + model.set({z: true}); + break; + case 2: + assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true}); + assert.equal(model.previous('y'), undefined); + break; + default: + assert.ok(false); + } + }); + model.set({x: true}); + }); + + QUnit.test('nested `change` with silent', function(assert) { + assert.expect(3); + var count = 0; + var model = new Backbone.Model(); + model.on('change:y', function() { assert.ok(false); }); + model.on('change', function() { + switch (count++) { + case 0: + assert.deepEqual(this.changedAttributes(), {x: true}); + model.set({y: true}, {silent: true}); + model.set({z: true}); + break; + case 1: + assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true}); + break; + case 2: + assert.deepEqual(this.changedAttributes(), {z: false}); + break; + default: + assert.ok(false); + } + }); + model.set({x: true}); + model.set({z: false}); + }); + + QUnit.test('nested `change:attr` with silent', function(assert) { + assert.expect(0); + var model = new Backbone.Model(); + model.on('change:y', function(){ assert.ok(false); }); + model.on('change', function() { + model.set({y: true}, {silent: true}); + model.set({z: true}); + }); + model.set({x: true}); + }); + + QUnit.test('multiple nested changes with silent', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change:x', function() { + model.set({y: 1}, {silent: true}); + model.set({y: 2}); + }); + model.on('change:y', function(m, val) { + assert.equal(val, 2); + }); + model.set({x: true}); + }); + + QUnit.test('multiple nested changes with silent', function(assert) { + assert.expect(1); + var changes = []; + var model = new Backbone.Model(); + model.on('change:b', function(m, val) { changes.push(val); }); + model.on('change', function() { + model.set({b: 1}); + }); + model.set({b: 0}); + assert.deepEqual(changes, [0, 1]); + }); + + QUnit.test('basic silent change semantics', function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.set({x: 1}); + model.on('change', function(){ assert.ok(true); }); + model.set({x: 2}, {silent: true}); + model.set({x: 1}); + }); + + QUnit.test('nested set multiple times', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change:b', function() { + assert.ok(true); + }); + model.on('change:a', function() { + model.set({b: true}); + model.set({b: true}); + }); + model.set({a: true}); + }); + + QUnit.test('#1122 - clear does not alter options.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + var options = {}; + model.clear(options); + assert.ok(!options.unset); + }); + + QUnit.test('#1122 - unset does not alter options.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + var options = {}; + model.unset('x', options); + assert.ok(!options.unset); + }); + + QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) { + assert.expect(3); + var model = new Backbone.Model(); + var opts = { + success: function( m, resp, options ) { + assert.ok(options); + } + }; + model.sync = function(method, m, options) { + options.success(); + }; + model.save({id: 1}, opts); + model.fetch(opts); + model.destroy(opts); + }); + + QUnit.test("#1412 - Trigger 'sync' event.", function(assert) { + assert.expect(3); + var model = new Backbone.Model({id: 1}); + model.sync = function(method, m, options) { options.success(); }; + model.on('sync', function(){ assert.ok(true); }); + model.fetch(); + model.save(); + model.destroy(); + }); + + QUnit.test('#1365 - Destroy: New models execute success callback.', function(assert) { + var done = assert.async(); + assert.expect(2); + new Backbone.Model() + .on('sync', function() { assert.ok(false); }) + .on('destroy', function(){ assert.ok(true); }) + .destroy({success: function(){ + assert.ok(true); + done(); + }}); + }); + + QUnit.test('#1433 - Save: An invalid model cannot be persisted.', function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.validate = function(){ return 'invalid'; }; + model.sync = function(){ assert.ok(false); }; + assert.strictEqual(model.save(), false); + }); + + QUnit.test("#1377 - Save without attrs triggers 'error'.", function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + url: '/test/', + sync: function(method, m, options){ options.success(); }, + validate: function(){ return 'invalid'; } + }); + var model = new Model({id: 1}); + model.on('invalid', function(){ assert.ok(true); }); + model.save(); + }); + + QUnit.test('#1545 - `undefined` can be passed to a model constructor without coersion', function(assert) { + var Model = Backbone.Model.extend({ + defaults: {one: 1}, + initialize: function(attrs, opts) { + assert.equal(attrs, undefined); + } + }); + var emptyattrs = new Model(); + var undefinedattrs = new Model(undefined); + }); + + QUnit.test('#1478 - Model `save` does not trigger change on unchanged attributes', function(assert) { + var done = assert.async(); + assert.expect(0); + var Model = Backbone.Model.extend({ + sync: function(method, m, options) { + setTimeout(function(){ + options.success(); + done(); + }, 0); + } + }); + new Model({x: true}) + .on('change:x', function(){ assert.ok(false); }) + .save(null, {wait: true}); + }); + + QUnit.test('#1664 - Changing from one value, silently to another, back to original triggers a change.', function(assert) { + assert.expect(1); + var model = new Backbone.Model({x: 1}); + model.on('change:x', function() { assert.ok(true); }); + model.set({x: 2}, {silent: true}); + model.set({x: 3}, {silent: true}); + model.set({x: 1}); + }); + + QUnit.test('#1664 - multiple silent changes nested inside a change event', function(assert) { + assert.expect(2); + var changes = []; + var model = new Backbone.Model(); + model.on('change', function() { + model.set({a: 'c'}, {silent: true}); + model.set({b: 2}, {silent: true}); + model.unset('c', {silent: true}); + }); + model.on('change:a change:b change:c', function(m, val) { changes.push(val); }); + model.set({a: 'a', b: 1, c: 'item'}); + assert.deepEqual(changes, ['a', 1, 'item']); + assert.deepEqual(model.attributes, {a: 'c', b: 2}); + }); + + QUnit.test('#1791 - `attributes` is available for `parse`', function(assert) { + var Model = Backbone.Model.extend({ + parse: function() { this.has('a'); } // shouldn't throw an error + }); + var model = new Model(null, {parse: true}); + assert.expect(0); + }); + + QUnit.test('silent changes in last `change` event back to original triggers change', function(assert) { + assert.expect(2); + var changes = []; + var model = new Backbone.Model(); + model.on('change:a change:b change:c', function(m, val) { changes.push(val); }); + model.on('change', function() { + model.set({a: 'c'}, {silent: true}); + }); + model.set({a: 'a'}); + assert.deepEqual(changes, ['a']); + model.set({a: 'a'}); + assert.deepEqual(changes, ['a', 'a']); + }); + + QUnit.test('#1943 change calculations should use _.isEqual', function(assert) { + var model = new Backbone.Model({a: {key: 'value'}}); + model.set('a', {key: 'value'}, {silent: true}); + assert.equal(model.changedAttributes(), false); + }); + + QUnit.test('#1964 - final `change` event is always fired, regardless of interim changes', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change:property', function() { + model.set('property', 'bar'); + }); + model.on('change', function() { + assert.ok(true); + }); + model.set('property', 'foo'); + }); + + QUnit.test('isValid', function(assert) { + var model = new Backbone.Model({valid: true}); + model.validate = function(attrs) { + if (!attrs.valid) return 'invalid'; + }; + assert.equal(model.isValid(), true); + assert.equal(model.set({valid: false}, {validate: true}), false); + assert.equal(model.isValid(), true); + model.set({valid: false}); + assert.equal(model.isValid(), false); + assert.ok(!model.set('valid', false, {validate: true})); + }); + + QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.validate = null; + assert.ok(model.isValid()); + }); + + QUnit.test('#1961 - Creating a model with {validate:true} will call validate and use the error callback', function(assert) { + var Model = Backbone.Model.extend({ + validate: function(attrs) { + if (attrs.id === 1) return "This shouldn't happen"; + } + }); + var model = new Model({id: 1}, {validate: true}); + assert.equal(model.validationError, "This shouldn't happen"); + }); + + QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) { + assert.expect(1); + var Model = Backbone.Model.extend({ + url: '/test', + toJSON: function() { + assert.strictEqual(this.attributes.x, 1); + return _.clone(this.attributes); + } + }); + var model = new Model; + model.save({x: 1}, {wait: true}); + }); + + QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) { + assert.expect(1); + var model = new Backbone.Model(); + model.on('change', function() { + model.set({b: true}, {silent: true}); + assert.ok(true); + }); + model.set({a: true}); + }); + + QUnit.test('#3778 - id will only be updated if it is set', function(assert) { + assert.expect(2); + var model = new Backbone.Model({id: 1}); + model.id = 2; + model.set({foo: 'bar'}); + assert.equal(model.id, 2); + model.set({id: 3}); + assert.equal(model.id, 3); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/noconflict.js b/public/vendor/lodash/vendor/backbone/test/noconflict.js new file mode 100644 index 00000000000..ab5d05f1652 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/noconflict.js @@ -0,0 +1,13 @@ +(function(QUnit) { + + QUnit.module('Backbone.noConflict'); + + QUnit.test('noConflict', function(assert) { + assert.expect(2); + var noconflictBackbone = Backbone.noConflict(); + assert.equal(window.Backbone, undefined, 'Returned window.Backbone'); + window.Backbone = noconflictBackbone; + assert.equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone'); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/router.js b/public/vendor/lodash/vendor/backbone/test/router.js new file mode 100644 index 00000000000..5bcd7677d70 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/router.js @@ -0,0 +1,1081 @@ +(function(QUnit) { + + var router = null; + var location = null; + var lastRoute = null; + var lastArgs = []; + + var onRoute = function(routerParam, route, args) { + lastRoute = route; + lastArgs = args; + }; + + var Location = function(href) { + this.replace(href); + }; + + _.extend(Location.prototype, { + + parser: document.createElement('a'), + + replace: function(href) { + this.parser.href = href; + _.extend(this, _.pick(this.parser, + 'href', + 'hash', + 'host', + 'search', + 'fragment', + 'pathname', + 'protocol' + )); + + // In IE, anchor.pathname does not contain a leading slash though + // window.location.pathname does. + if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname; + }, + + toString: function() { + return this.href; + } + + }); + + QUnit.module('Backbone.Router', { + + beforeEach: function() { + location = new Location('http://example.com'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + router = new Router({testing: 101}); + Backbone.history.interval = 9; + Backbone.history.start({pushState: false}); + lastRoute = null; + lastArgs = []; + Backbone.history.on('route', onRoute); + }, + + afterEach: function() { + Backbone.history.stop(); + Backbone.history.off('route', onRoute); + } + + }); + + var ExternalObject = { + value: 'unset', + + routingFunction: function(value) { + this.value = value; + } + }; + ExternalObject.routingFunction = _.bind(ExternalObject.routingFunction, ExternalObject); + + var Router = Backbone.Router.extend({ + + count: 0, + + routes: { + 'noCallback': 'noCallback', + 'counter': 'counter', + 'search/:query': 'search', + 'search/:query/p:page': 'search', + 'charñ': 'charUTF', + 'char%C3%B1': 'charEscaped', + 'contacts': 'contacts', + 'contacts/new': 'newContact', + 'contacts/:id': 'loadContact', + 'route-event/:arg': 'routeEvent', + 'optional(/:item)': 'optionalItem', + 'named/optional/(y:z)': 'namedOptional', + 'splat/*args/end': 'splat', + ':repo/compare/*from...*to': 'github', + 'decode/:named/*splat': 'decode', + '*first/complex-*part/*rest': 'complex', + 'query/:entity': 'query', + 'function/:value': ExternalObject.routingFunction, + '*anything': 'anything' + }, + + preinitialize: function(options) { + this.testpreinit = 'foo'; + }, + + initialize: function(options) { + this.testing = options.testing; + this.route('implicit', 'implicit'); + }, + + counter: function() { + this.count++; + }, + + implicit: function() { + this.count++; + }, + + search: function(query, page) { + this.query = query; + this.page = page; + }, + + charUTF: function() { + this.charType = 'UTF'; + }, + + charEscaped: function() { + this.charType = 'escaped'; + }, + + contacts: function() { + this.contact = 'index'; + }, + + newContact: function() { + this.contact = 'new'; + }, + + loadContact: function() { + this.contact = 'load'; + }, + + optionalItem: function(arg) { + this.arg = arg !== void 0 ? arg : null; + }, + + splat: function(args) { + this.args = args; + }, + + github: function(repo, from, to) { + this.repo = repo; + this.from = from; + this.to = to; + }, + + complex: function(first, part, rest) { + this.first = first; + this.part = part; + this.rest = rest; + }, + + query: function(entity, args) { + this.entity = entity; + this.queryArgs = args; + }, + + anything: function(whatever) { + this.anything = whatever; + }, + + namedOptional: function(z) { + this.z = z; + }, + + decode: function(named, path) { + this.named = named; + this.path = path; + }, + + routeEvent: function(arg) { + } + + }); + + QUnit.test('initialize', function(assert) { + assert.expect(1); + assert.equal(router.testing, 101); + }); + + QUnit.test('preinitialize', function(assert) { + assert.expect(1); + assert.equal(router.testpreinit, 'foo'); + }); + + QUnit.test('routes (simple)', function(assert) { + assert.expect(4); + location.replace('http://example.com#search/news'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'news'); + assert.equal(router.page, void 0); + assert.equal(lastRoute, 'search'); + assert.equal(lastArgs[0], 'news'); + }); + + QUnit.test('routes (simple, but unicode)', function(assert) { + assert.expect(4); + location.replace('http://example.com#search/тест'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'тест'); + assert.equal(router.page, void 0); + assert.equal(lastRoute, 'search'); + assert.equal(lastArgs[0], 'тест'); + }); + + QUnit.test('routes (two part)', function(assert) { + assert.expect(2); + location.replace('http://example.com#search/nyc/p10'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'nyc'); + assert.equal(router.page, '10'); + }); + + QUnit.test('routes via navigate', function(assert) { + assert.expect(2); + Backbone.history.navigate('search/manhattan/p20', {trigger: true}); + assert.equal(router.query, 'manhattan'); + assert.equal(router.page, '20'); + }); + + QUnit.test('routes via navigate with params', function(assert) { + assert.expect(1); + Backbone.history.navigate('query/test?a=b', {trigger: true}); + assert.equal(router.queryArgs, 'a=b'); + }); + + QUnit.test('routes via navigate for backwards-compatibility', function(assert) { + assert.expect(2); + Backbone.history.navigate('search/manhattan/p20', true); + assert.equal(router.query, 'manhattan'); + assert.equal(router.page, '20'); + }); + + QUnit.test('reports matched route via nagivate', function(assert) { + assert.expect(1); + assert.ok(Backbone.history.navigate('search/manhattan/p20', true)); + }); + + QUnit.test('route precedence via navigate', function(assert) { + assert.expect(6); + + // Check both 0.9.x and backwards-compatibility options + _.each([{trigger: true}, true], function(options) { + Backbone.history.navigate('contacts', options); + assert.equal(router.contact, 'index'); + Backbone.history.navigate('contacts/new', options); + assert.equal(router.contact, 'new'); + Backbone.history.navigate('contacts/foo', options); + assert.equal(router.contact, 'load'); + }); + }); + + QUnit.test('loadUrl is not called for identical routes.', function(assert) { + assert.expect(0); + Backbone.history.loadUrl = function() { assert.ok(false); }; + location.replace('http://example.com#route'); + Backbone.history.navigate('route'); + Backbone.history.navigate('/route'); + Backbone.history.navigate('/route'); + }); + + QUnit.test('use implicit callback if none provided', function(assert) { + assert.expect(1); + router.count = 0; + router.navigate('implicit', {trigger: true}); + assert.equal(router.count, 1); + }); + + QUnit.test('routes via navigate with {replace: true}', function(assert) { + assert.expect(1); + location.replace('http://example.com#start_here'); + Backbone.history.checkUrl(); + location.replace = function(href) { + assert.strictEqual(href, new Location('http://example.com#end_here').href); + }; + Backbone.history.navigate('end_here', {replace: true}); + }); + + QUnit.test('routes (splats)', function(assert) { + assert.expect(1); + location.replace('http://example.com#splat/long-list/of/splatted_99args/end'); + Backbone.history.checkUrl(); + assert.equal(router.args, 'long-list/of/splatted_99args'); + }); + + QUnit.test('routes (github)', function(assert) { + assert.expect(3); + location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash'); + Backbone.history.checkUrl(); + assert.equal(router.repo, 'backbone'); + assert.equal(router.from, '1.0'); + assert.equal(router.to, 'braddunbar:with/slash'); + }); + + QUnit.test('routes (optional)', function(assert) { + assert.expect(2); + location.replace('http://example.com#optional'); + Backbone.history.checkUrl(); + assert.ok(!router.arg); + location.replace('http://example.com#optional/thing'); + Backbone.history.checkUrl(); + assert.equal(router.arg, 'thing'); + }); + + QUnit.test('routes (complex)', function(assert) { + assert.expect(3); + location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven'); + Backbone.history.checkUrl(); + assert.equal(router.first, 'one/two/three'); + assert.equal(router.part, 'part'); + assert.equal(router.rest, 'four/five/six/seven'); + }); + + QUnit.test('routes (query)', function(assert) { + assert.expect(5); + location.replace('http://example.com#query/mandel?a=b&c=d'); + Backbone.history.checkUrl(); + assert.equal(router.entity, 'mandel'); + assert.equal(router.queryArgs, 'a=b&c=d'); + assert.equal(lastRoute, 'query'); + assert.equal(lastArgs[0], 'mandel'); + assert.equal(lastArgs[1], 'a=b&c=d'); + }); + + QUnit.test('routes (anything)', function(assert) { + assert.expect(1); + location.replace('http://example.com#doesnt-match-a-route'); + Backbone.history.checkUrl(); + assert.equal(router.anything, 'doesnt-match-a-route'); + }); + + QUnit.test('routes (function)', function(assert) { + assert.expect(3); + router.on('route', function(name) { + assert.ok(name === ''); + }); + assert.equal(ExternalObject.value, 'unset'); + location.replace('http://example.com#function/set'); + Backbone.history.checkUrl(); + assert.equal(ExternalObject.value, 'set'); + }); + + QUnit.test('Decode named parameters, not splats.', function(assert) { + assert.expect(2); + location.replace('http://example.com#decode/a%2Fb/c%2Fd/e'); + Backbone.history.checkUrl(); + assert.strictEqual(router.named, 'a/b'); + assert.strictEqual(router.path, 'c/d/e'); + }); + + QUnit.test('fires event when router doesn\'t have callback on it', function(assert) { + assert.expect(1); + router.on('route:noCallback', function() { assert.ok(true); }); + location.replace('http://example.com#noCallback'); + Backbone.history.checkUrl(); + }); + + QUnit.test('No events are triggered if #execute returns false.', function(assert) { + assert.expect(1); + var MyRouter = Backbone.Router.extend({ + + routes: { + foo: function() { + assert.ok(true); + } + }, + + execute: function(callback, args) { + callback.apply(this, args); + return false; + } + + }); + + var myRouter = new MyRouter; + + myRouter.on('route route:foo', function() { + assert.ok(false); + }); + + Backbone.history.on('route', function() { + assert.ok(false); + }); + + location.replace('http://example.com#foo'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#933, #908 - leading slash', function(assert) { + assert.expect(2); + location.replace('http://example.com/root/foo'); + + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({root: '/root', hashChange: false, silent: true}); + assert.strictEqual(Backbone.history.getFragment(), 'foo'); + + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({root: '/root/', hashChange: false, silent: true}); + assert.strictEqual(Backbone.history.getFragment(), 'foo'); + }); + + QUnit.test('#967 - Route callback gets passed encoded values.', function(assert) { + assert.expect(3); + var route = 'has%2Fslash/complex-has%23hash/has%20space'; + Backbone.history.navigate(route, {trigger: true}); + assert.strictEqual(router.first, 'has/slash'); + assert.strictEqual(router.part, 'has#hash'); + assert.strictEqual(router.rest, 'has space'); + }); + + QUnit.test('correctly handles URLs with % (#868)', function(assert) { + assert.expect(3); + location.replace('http://example.com#search/fat%3A1.5%25'); + Backbone.history.checkUrl(); + location.replace('http://example.com#search/fat'); + Backbone.history.checkUrl(); + assert.equal(router.query, 'fat'); + assert.equal(router.page, void 0); + assert.equal(lastRoute, 'search'); + }); + + QUnit.test('#2666 - Hashes with UTF8 in them.', function(assert) { + assert.expect(2); + Backbone.history.navigate('charñ', {trigger: true}); + assert.equal(router.charType, 'UTF'); + Backbone.history.navigate('char%C3%B1', {trigger: true}); + assert.equal(router.charType, 'UTF'); + }); + + QUnit.test('#1185 - Use pathname when hashChange is not wanted.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/path/name#hash'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({hashChange: false}); + var fragment = Backbone.history.getFragment(); + assert.strictEqual(fragment, location.pathname.replace(/^\//, '')); + }); + + QUnit.test('#1206 - Strip leading slash before location.assign.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root/'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({hashChange: false, root: '/root/'}); + location.assign = function(pathname) { + assert.strictEqual(pathname, '/root/fragment'); + }; + Backbone.history.navigate('/fragment'); + }); + + QUnit.test('#1387 - Root fragment without trailing slash.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({hashChange: false, root: '/root/', silent: true}); + assert.strictEqual(Backbone.history.getFragment(), ''); + }); + + QUnit.test('#1366 - History does not prepend root to fragment.', function(assert) { + assert.expect(2); + Backbone.history.stop(); + location.replace('http://example.com/root/'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/root/x'); + } + } + }); + Backbone.history.start({ + root: '/root/', + pushState: true, + hashChange: false + }); + Backbone.history.navigate('x'); + assert.strictEqual(Backbone.history.fragment, 'x'); + }); + + QUnit.test('Normalize root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/root/fragment'); + } + } + }); + Backbone.history.start({ + pushState: true, + root: '/root', + hashChange: false + }); + Backbone.history.navigate('fragment'); + }); + + QUnit.test('Normalize root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root#fragment'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) {}, + replaceState: function(state, title, url) { + assert.strictEqual(url, '/root/fragment'); + } + } + }); + Backbone.history.start({ + pushState: true, + root: '/root' + }); + }); + + QUnit.test('Normalize root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.loadUrl = function() { assert.ok(true); }; + Backbone.history.start({ + pushState: true, + root: '/root' + }); + }); + + QUnit.test('Normalize root - leading slash.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() {}, + replaceState: function() {} + } + }); + Backbone.history.start({root: 'root'}); + assert.strictEqual(Backbone.history.root, '/root/'); + }); + + QUnit.test('Transition from hashChange to pushState.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root#x/y'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() {}, + replaceState: function(state, title, url) { + assert.strictEqual(url, '/root/x/y'); + } + } + }); + Backbone.history.start({ + root: 'root', + pushState: true + }); + }); + + QUnit.test('#1619: Router: Normalize empty root', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() {}, + replaceState: function() {} + } + }); + Backbone.history.start({root: ''}); + assert.strictEqual(Backbone.history.root, '/'); + }); + + QUnit.test('#1619: Router: nagivate with empty root', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/fragment'); + } + } + }); + Backbone.history.start({ + pushState: true, + root: '', + hashChange: false + }); + Backbone.history.navigate('fragment'); + }); + + QUnit.test('Transition from pushState to hashChange.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root/x/y?a=b'); + location.replace = function(url) { + assert.strictEqual(url, '/root#x/y?a=b'); + }; + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: null, + replaceState: null + } + }); + Backbone.history.start({ + root: 'root', + pushState: true + }); + }); + + QUnit.test('#1695 - hashChange to pushState with search.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root#x/y?a=b'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() {}, + replaceState: function(state, title, url) { + assert.strictEqual(url, '/root/x/y?a=b'); + } + } + }); + Backbone.history.start({ + root: 'root', + pushState: true + }); + }); + + QUnit.test('#1746 - Router allows empty route.', function(assert) { + assert.expect(1); + var MyRouter = Backbone.Router.extend({ + routes: {'': 'empty'}, + empty: function() {}, + route: function(route) { + assert.strictEqual(route, ''); + } + }); + new MyRouter; + }); + + QUnit.test('#1794 - Trailing space in fragments.', function(assert) { + assert.expect(1); + var history = new Backbone.History; + assert.strictEqual(history.getFragment('fragment '), 'fragment'); + }); + + QUnit.test('#1820 - Leading slash and trailing space.', function(assert) { + assert.expect(1); + var history = new Backbone.History; + assert.strictEqual(history.getFragment('/fragment '), 'fragment'); + }); + + QUnit.test('#1980 - Optional parameters.', function(assert) { + assert.expect(2); + location.replace('http://example.com#named/optional/y'); + Backbone.history.checkUrl(); + assert.strictEqual(router.z, undefined); + location.replace('http://example.com#named/optional/y123'); + Backbone.history.checkUrl(); + assert.strictEqual(router.z, '123'); + }); + + QUnit.test('#2062 - Trigger "route" event on router instance.', function(assert) { + assert.expect(2); + router.on('route', function(name, args) { + assert.strictEqual(name, 'routeEvent'); + assert.deepEqual(args, ['x', null]); + }); + location.replace('http://example.com#route-event/x'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#2255 - Extend routes by making routes a function.', function(assert) { + assert.expect(1); + var RouterBase = Backbone.Router.extend({ + routes: function() { + return { + home: 'root', + index: 'index.html' + }; + } + }); + + var RouterExtended = RouterBase.extend({ + routes: function() { + var _super = RouterExtended.__super__.routes; + return _.extend(_super(), {show: 'show', search: 'search'}); + } + }); + + var myRouter = new RouterExtended(); + assert.deepEqual({home: 'root', index: 'index.html', show: 'show', search: 'search'}, myRouter.routes); + }); + + QUnit.test('#2538 - hashChange to pushState only if both requested.', function(assert) { + assert.expect(0); + Backbone.history.stop(); + location.replace('http://example.com/root?a=b#x/y'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() {}, + replaceState: function() { assert.ok(false); } + } + }); + Backbone.history.start({ + root: 'root', + pushState: true, + hashChange: false + }); + }); + + QUnit.test('No hash fallback.', function(assert) { + assert.expect(0); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() {}, + replaceState: function() {} + } + }); + + var MyRouter = Backbone.Router.extend({ + routes: { + hash: function() { assert.ok(false); } + } + }); + var myRouter = new MyRouter; + + location.replace('http://example.com/'); + Backbone.history.start({ + pushState: true, + hashChange: false + }); + location.replace('http://example.com/nomatch#hash'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#2656 - No trailing slash on root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/root'); + } + } + }); + location.replace('http://example.com/root/path'); + Backbone.history.start({pushState: true, hashChange: false, root: 'root'}); + Backbone.history.navigate(''); + }); + + QUnit.test('#2656 - No trailing slash on root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/'); + } + } + }); + location.replace('http://example.com/path'); + Backbone.history.start({pushState: true, hashChange: false}); + Backbone.history.navigate(''); + }); + + QUnit.test('#2656 - No trailing slash on root.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/root?x=1'); + } + } + }); + location.replace('http://example.com/root/path'); + Backbone.history.start({pushState: true, hashChange: false, root: 'root'}); + Backbone.history.navigate('?x=1'); + }); + + QUnit.test('#2765 - Fragment matching sans query/hash.', function(assert) { + assert.expect(2); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function(state, title, url) { + assert.strictEqual(url, '/path?query#hash'); + } + } + }); + + var MyRouter = Backbone.Router.extend({ + routes: { + path: function() { assert.ok(true); } + } + }); + var myRouter = new MyRouter; + + location.replace('http://example.com/'); + Backbone.history.start({pushState: true, hashChange: false}); + Backbone.history.navigate('path?query#hash', true); + }); + + QUnit.test('Do not decode the search params.', function(assert) { + assert.expect(1); + var MyRouter = Backbone.Router.extend({ + routes: { + path: function(params) { + assert.strictEqual(params, 'x=y%3Fz'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.navigate('path?x=y%3Fz', true); + }); + + QUnit.test('Navigate to a hash url.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({pushState: true}); + var MyRouter = Backbone.Router.extend({ + routes: { + path: function(params) { + assert.strictEqual(params, 'x=y'); + } + } + }); + var myRouter = new MyRouter; + location.replace('http://example.com/path?x=y#hash'); + Backbone.history.checkUrl(); + }); + + QUnit.test('#navigate to a hash url.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + Backbone.history.start({pushState: true}); + var MyRouter = Backbone.Router.extend({ + routes: { + path: function(params) { + assert.strictEqual(params, 'x=y'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.navigate('path?x=y#hash', true); + }); + + QUnit.test('unicode pathname', function(assert) { + assert.expect(1); + location.replace('http://example.com/myyjä'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + myyjä: function() { + assert.ok(true); + } + } + }); + new MyRouter; + Backbone.history.start({pushState: true}); + }); + + QUnit.test('unicode pathname with % in a parameter', function(assert) { + assert.expect(1); + location.replace('http://example.com/myyjä/foo%20%25%3F%2f%40%25%20bar'); + location.pathname = '/myyj%C3%A4/foo%20%25%3F%2f%40%25%20bar'; + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + 'myyjä/:query': function(query) { + assert.strictEqual(query, 'foo %?/@% bar'); + } + } + }); + new MyRouter; + Backbone.history.start({pushState: true}); + }); + + QUnit.test('newline in route', function(assert) { + assert.expect(1); + location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + 'stuff\nnonsense': function() { + assert.ok(true); + } + } + }); + new MyRouter; + Backbone.history.start({pushState: true}); + }); + + QUnit.test('Router#execute receives callback, args, name.', function(assert) { + assert.expect(3); + location.replace('http://example.com#foo/123/bar?x=y'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {'foo/:id/bar': 'foo'}, + foo: function() {}, + execute: function(callback, args, name) { + assert.strictEqual(callback, this.foo); + assert.deepEqual(args, ['123', 'x=y']); + assert.strictEqual(name, 'foo'); + } + }); + var myRouter = new MyRouter; + Backbone.history.start(); + }); + + QUnit.test('pushState to hashChange with only search params.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com?a=b'); + location.replace = function(url) { + assert.strictEqual(url, '/#?a=b'); + }; + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: null + }); + Backbone.history.start({pushState: true}); + }); + + QUnit.test('#3123 - History#navigate decodes before comparison.', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/shop/search?keyword=short%20dress'); + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: function() { assert.ok(false); }, + replaceState: function() { assert.ok(false); } + } + }); + Backbone.history.start({pushState: true}); + Backbone.history.navigate('shop/search?keyword=short%20dress', true); + assert.strictEqual(Backbone.history.fragment, 'shop/search?keyword=short dress'); + }); + + QUnit.test('#3175 - Urls in the params', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com#login?a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var myRouter = new Backbone.Router; + myRouter.route('login', function(params) { + assert.strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db'); + }); + Backbone.history.start(); + }); + + QUnit.test('#3358 - pushState to hashChange transition with search params', function(assert) { + assert.expect(1); + Backbone.history.stop(); + location.replace('http://example.com/root?foo=bar'); + location.replace = function(url) { + assert.strictEqual(url, '/root#?foo=bar'); + }; + Backbone.history = _.extend(new Backbone.History, { + location: location, + history: { + pushState: undefined, + replaceState: undefined + } + }); + Backbone.history.start({root: '/root', pushState: true}); + }); + + QUnit.test('Paths that don\'t match the root should not match no root', function(assert) { + assert.expect(0); + location.replace('http://example.com/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + foo: function() { + assert.ok(false, 'should not match unless root matches'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.start({root: 'root', pushState: true}); + }); + + QUnit.test('Paths that don\'t match the root should not match roots of the same length', function(assert) { + assert.expect(0); + location.replace('http://example.com/xxxx/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: { + foo: function() { + assert.ok(false, 'should not match unless root matches'); + } + } + }); + var myRouter = new MyRouter; + Backbone.history.start({root: 'root', pushState: true}); + }); + + QUnit.test('roots with regex characters', function(assert) { + assert.expect(1); + location.replace('http://example.com/x+y.z/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {foo: function() { assert.ok(true); }} + }); + var myRouter = new MyRouter; + Backbone.history.start({root: 'x+y.z', pushState: true}); + }); + + QUnit.test('roots with unicode characters', function(assert) { + assert.expect(1); + location.replace('http://example.com/®ooτ/foo'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {foo: function() { assert.ok(true); }} + }); + var myRouter = new MyRouter; + Backbone.history.start({root: '®ooτ', pushState: true}); + }); + + QUnit.test('roots without slash', function(assert) { + assert.expect(1); + location.replace('http://example.com/®ooτ'); + Backbone.history.stop(); + Backbone.history = _.extend(new Backbone.History, {location: location}); + var MyRouter = Backbone.Router.extend({ + routes: {'': function() { assert.ok(true); }} + }); + var myRouter = new MyRouter; + Backbone.history.start({root: '®ooτ', pushState: true}); + }); + + QUnit.test('#4025 - navigate updates URL hash as is', function(assert) { + assert.expect(1); + var route = 'search/has%20space'; + Backbone.history.navigate(route); + assert.strictEqual(location.hash, '#' + route); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/setup/dom-setup.js b/public/vendor/lodash/vendor/backbone/test/setup/dom-setup.js new file mode 100644 index 00000000000..f2242282c6f --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/setup/dom-setup.js @@ -0,0 +1,4 @@ +$('body').append( + '
' + + '
' +); diff --git a/public/vendor/lodash/vendor/backbone/test/setup/environment.js b/public/vendor/lodash/vendor/backbone/test/setup/environment.js new file mode 100644 index 00000000000..6461b5bbdc7 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/setup/environment.js @@ -0,0 +1,45 @@ +(function(QUnit) { + + var sync = Backbone.sync; + var ajax = Backbone.ajax; + var emulateHTTP = Backbone.emulateHTTP; + var emulateJSON = Backbone.emulateJSON; + var history = window.history; + var pushState = history.pushState; + var replaceState = history.replaceState; + + QUnit.config.noglobals = true; + + QUnit.testStart(function() { + var env = QUnit.config.current.testEnvironment; + + // We never want to actually call these during tests. + history.pushState = history.replaceState = function() {}; + + // Capture ajax settings for comparison. + Backbone.ajax = function(settings) { + env.ajaxSettings = settings; + }; + + // Capture the arguments to Backbone.sync for comparison. + Backbone.sync = function(method, model, options) { + env.syncArgs = { + method: method, + model: model, + options: options + }; + sync.apply(this, arguments); + }; + + }); + + QUnit.testDone(function() { + Backbone.sync = sync; + Backbone.ajax = ajax; + Backbone.emulateHTTP = emulateHTTP; + Backbone.emulateJSON = emulateJSON; + history.pushState = pushState; + history.replaceState = replaceState; + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/sync.js b/public/vendor/lodash/vendor/backbone/test/sync.js new file mode 100644 index 00000000000..cd314a18559 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/sync.js @@ -0,0 +1,239 @@ +(function(QUnit) { + + var Library = Backbone.Collection.extend({ + url: function() { return '/library'; } + }); + var library; + + var attrs = { + title: 'The Tempest', + author: 'Bill Shakespeare', + length: 123 + }; + + QUnit.module('Backbone.sync', { + + beforeEach: function(assert) { + library = new Library; + library.create(attrs, {wait: false}); + }, + + afterEach: function(assert) { + Backbone.emulateHTTP = false; + } + + }); + + QUnit.test('read', function(assert) { + assert.expect(4); + library.fetch(); + assert.equal(this.ajaxSettings.url, '/library'); + assert.equal(this.ajaxSettings.type, 'GET'); + assert.equal(this.ajaxSettings.dataType, 'json'); + assert.ok(_.isEmpty(this.ajaxSettings.data)); + }); + + QUnit.test('passing data', function(assert) { + assert.expect(3); + library.fetch({data: {a: 'a', one: 1}}); + assert.equal(this.ajaxSettings.url, '/library'); + assert.equal(this.ajaxSettings.data.a, 'a'); + assert.equal(this.ajaxSettings.data.one, 1); + }); + + QUnit.test('create', function(assert) { + assert.expect(6); + assert.equal(this.ajaxSettings.url, '/library'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(this.ajaxSettings.dataType, 'json'); + var data = JSON.parse(this.ajaxSettings.data); + assert.equal(data.title, 'The Tempest'); + assert.equal(data.author, 'Bill Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update', function(assert) { + assert.expect(7); + library.first().save({id: '1-the-tempest', author: 'William Shakespeare'}); + assert.equal(this.ajaxSettings.url, '/library/1-the-tempest'); + assert.equal(this.ajaxSettings.type, 'PUT'); + assert.equal(this.ajaxSettings.dataType, 'json'); + var data = JSON.parse(this.ajaxSettings.data); + assert.equal(data.id, '1-the-tempest'); + assert.equal(data.title, 'The Tempest'); + assert.equal(data.author, 'William Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update with emulateHTTP and emulateJSON', function(assert) { + assert.expect(7); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { + emulateHTTP: true, + emulateJSON: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(this.ajaxSettings.dataType, 'json'); + assert.equal(this.ajaxSettings.data._method, 'PUT'); + var data = JSON.parse(this.ajaxSettings.data.model); + assert.equal(data.id, '2-the-tempest'); + assert.equal(data.author, 'Tim Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update with just emulateHTTP', function(assert) { + assert.expect(6); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { + emulateHTTP: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(this.ajaxSettings.contentType, 'application/json'); + var data = JSON.parse(this.ajaxSettings.data); + assert.equal(data.id, '2-the-tempest'); + assert.equal(data.author, 'Tim Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('update with just emulateJSON', function(assert) { + assert.expect(6); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, { + emulateJSON: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'PUT'); + assert.equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded'); + var data = JSON.parse(this.ajaxSettings.data.model); + assert.equal(data.id, '2-the-tempest'); + assert.equal(data.author, 'Tim Shakespeare'); + assert.equal(data.length, 123); + }); + + QUnit.test('read model', function(assert) { + assert.expect(3); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); + library.first().fetch(); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'GET'); + assert.ok(_.isEmpty(this.ajaxSettings.data)); + }); + + QUnit.test('destroy', function(assert) { + assert.expect(3); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); + library.first().destroy({wait: true}); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'DELETE'); + assert.equal(this.ajaxSettings.data, null); + }); + + QUnit.test('destroy with emulateHTTP', function(assert) { + assert.expect(3); + library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}); + library.first().destroy({ + emulateHTTP: true, + emulateJSON: true + }); + assert.equal(this.ajaxSettings.url, '/library/2-the-tempest'); + assert.equal(this.ajaxSettings.type, 'POST'); + assert.equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}'); + }); + + QUnit.test('urlError', function(assert) { + assert.expect(2); + var model = new Backbone.Model(); + assert.raises(function() { + model.fetch(); + }); + model.fetch({url: '/one/two'}); + assert.equal(this.ajaxSettings.url, '/one/two'); + }); + + QUnit.test('#1052 - `options` is optional.', function(assert) { + assert.expect(0); + var model = new Backbone.Model(); + model.url = '/test'; + Backbone.sync('create', model); + }); + + QUnit.test('Backbone.ajax', function(assert) { + assert.expect(1); + Backbone.ajax = function(settings) { + assert.strictEqual(settings.url, '/test'); + }; + var model = new Backbone.Model(); + model.url = '/test'; + Backbone.sync('create', model); + }); + + QUnit.test('Call provided error callback on error.', function(assert) { + assert.expect(1); + var model = new Backbone.Model; + model.url = '/test'; + Backbone.sync('read', model, { + error: function() { assert.ok(true); } + }); + this.ajaxSettings.error(); + }); + + QUnit.test('Use Backbone.emulateHTTP as default.', function(assert) { + assert.expect(2); + var model = new Backbone.Model; + model.url = '/test'; + + Backbone.emulateHTTP = true; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateHTTP, true); + + Backbone.emulateHTTP = false; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateHTTP, false); + }); + + QUnit.test('Use Backbone.emulateJSON as default.', function(assert) { + assert.expect(2); + var model = new Backbone.Model; + model.url = '/test'; + + Backbone.emulateJSON = true; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateJSON, true); + + Backbone.emulateJSON = false; + model.sync('create', model); + assert.strictEqual(this.ajaxSettings.emulateJSON, false); + }); + + QUnit.test('#1756 - Call user provided beforeSend function.', function(assert) { + assert.expect(4); + Backbone.emulateHTTP = true; + var model = new Backbone.Model; + model.url = '/test'; + var xhr = { + setRequestHeader: function(header, value) { + assert.strictEqual(header, 'X-HTTP-Method-Override'); + assert.strictEqual(value, 'DELETE'); + } + }; + model.sync('delete', model, { + beforeSend: function(_xhr) { + assert.ok(_xhr === xhr); + return false; + } + }); + assert.strictEqual(this.ajaxSettings.beforeSend(xhr), false); + }); + + QUnit.test('#2928 - Pass along `textStatus` and `errorThrown`.', function(assert) { + assert.expect(2); + var model = new Backbone.Model; + model.url = '/test'; + model.on('error', function(m, xhr, options) { + assert.strictEqual(options.textStatus, 'textStatus'); + assert.strictEqual(options.errorThrown, 'errorThrown'); + }); + model.fetch(); + this.ajaxSettings.error({}, 'textStatus', 'errorThrown'); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/backbone/test/view.js b/public/vendor/lodash/vendor/backbone/test/view.js new file mode 100644 index 00000000000..13270c84004 --- /dev/null +++ b/public/vendor/lodash/vendor/backbone/test/view.js @@ -0,0 +1,516 @@ +(function(QUnit) { + + var view; + + QUnit.module('Backbone.View', { + + beforeEach: function() { + $('#qunit-fixture').append( + '

Test

' + ); + + view = new Backbone.View({ + id: 'test-view', + className: 'test-view', + other: 'non-special-option' + }); + }, + + afterEach: function() { + $('#testElement').remove(); + $('#test-view').remove(); + } + + }); + + QUnit.test('constructor', function(assert) { + assert.expect(3); + assert.equal(view.el.id, 'test-view'); + assert.equal(view.el.className, 'test-view'); + assert.equal(view.el.other, void 0); + }); + + QUnit.test('$', function(assert) { + assert.expect(2); + var myView = new Backbone.View; + myView.setElement('

test

'); + var result = myView.$('a b'); + + assert.strictEqual(result[0].innerHTML, 'test'); + assert.ok(result.length === +result.length); + }); + + QUnit.test('$el', function(assert) { + assert.expect(3); + var myView = new Backbone.View; + myView.setElement('

test

'); + assert.strictEqual(myView.el.nodeType, 1); + + assert.ok(myView.$el instanceof Backbone.$); + assert.strictEqual(myView.$el[0], myView.el); + }); + + QUnit.test('initialize', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + initialize: function() { + this.one = 1; + } + }); + + assert.strictEqual(new View().one, 1); + }); + + QUnit.test('preinitialize', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + preinitialize: function() { + this.one = 1; + } + }); + + assert.strictEqual(new View().one, 1); + }); + + QUnit.test('preinitialize occurs before the view is set up', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + preinitialize: function() { + assert.equal(this.el, undefined); + } + }); + var _view = new View({}); + assert.notEqual(_view.el, undefined); + }); + + QUnit.test('render', function(assert) { + assert.expect(1); + var myView = new Backbone.View; + assert.equal(myView.render(), myView, '#render returns the view instance'); + }); + + QUnit.test('delegateEvents', function(assert) { + assert.expect(6); + var counter1 = 0, counter2 = 0; + + var myView = new Backbone.View({el: '#testElement'}); + myView.increment = function() { counter1++; }; + myView.$el.on('click', function() { counter2++; }); + + var events = {'click h1': 'increment'}; + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 1); + assert.equal(counter2, 1); + + myView.$('h1').trigger('click'); + assert.equal(counter1, 2); + assert.equal(counter2, 2); + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 3); + assert.equal(counter2, 3); + }); + + QUnit.test('delegate', function(assert) { + assert.expect(3); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', 'h1', function() { + assert.ok(true); + }); + myView.delegate('click', function() { + assert.ok(true); + }); + myView.$('h1').trigger('click'); + + assert.equal(myView.delegate(), myView, '#delegate returns the view instance'); + }); + + QUnit.test('delegateEvents allows functions for callbacks', function(assert) { + assert.expect(3); + var myView = new Backbone.View({el: '

'}); + myView.counter = 0; + + var events = { + click: function() { + this.counter++; + } + }; + + myView.delegateEvents(events); + myView.$el.trigger('click'); + assert.equal(myView.counter, 1); + + myView.$el.trigger('click'); + assert.equal(myView.counter, 2); + + myView.delegateEvents(events); + myView.$el.trigger('click'); + assert.equal(myView.counter, 3); + }); + + QUnit.test('delegateEvents ignore undefined methods', function(assert) { + assert.expect(0); + var myView = new Backbone.View({el: '

'}); + myView.delegateEvents({click: 'undefinedMethod'}); + myView.$el.trigger('click'); + }); + + QUnit.test('undelegateEvents', function(assert) { + assert.expect(7); + var counter1 = 0, counter2 = 0; + + var myView = new Backbone.View({el: '#testElement'}); + myView.increment = function() { counter1++; }; + myView.$el.on('click', function() { counter2++; }); + + var events = {'click h1': 'increment'}; + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 1); + assert.equal(counter2, 1); + + myView.undelegateEvents(); + myView.$('h1').trigger('click'); + assert.equal(counter1, 1); + assert.equal(counter2, 2); + + myView.delegateEvents(events); + myView.$('h1').trigger('click'); + assert.equal(counter1, 2); + assert.equal(counter2, 3); + + assert.equal(myView.undelegateEvents(), myView, '#undelegateEvents returns the view instance'); + }); + + QUnit.test('undelegate', function(assert) { + assert.expect(1); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(false); }); + myView.delegate('click', 'h1', function() { assert.ok(false); }); + + myView.undelegate('click'); + + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); + + assert.equal(myView.undelegate(), myView, '#undelegate returns the view instance'); + }); + + QUnit.test('undelegate with passed handler', function(assert) { + assert.expect(1); + var myView = new Backbone.View({el: '#testElement'}); + var listener = function() { assert.ok(false); }; + myView.delegate('click', listener); + myView.delegate('click', function() { assert.ok(true); }); + myView.undelegate('click', listener); + myView.$el.trigger('click'); + }); + + QUnit.test('undelegate with selector', function(assert) { + assert.expect(2); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(true); }); + myView.delegate('click', 'h1', function() { assert.ok(false); }); + myView.undelegate('click', 'h1'); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); + }); + + QUnit.test('undelegate with handler and selector', function(assert) { + assert.expect(2); + var myView = new Backbone.View({el: '#testElement'}); + myView.delegate('click', function() { assert.ok(true); }); + var handler = function() { assert.ok(false); }; + myView.delegate('click', 'h1', handler); + myView.undelegate('click', 'h1', handler); + myView.$('h1').trigger('click'); + myView.$el.trigger('click'); + }); + + QUnit.test('tagName can be provided as a string', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + tagName: 'span' + }); + + assert.equal(new View().el.tagName, 'SPAN'); + }); + + QUnit.test('tagName can be provided as a function', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + tagName: function() { + return 'p'; + } + }); + + assert.ok(new View().$el.is('p')); + }); + + QUnit.test('_ensureElement with DOM node el', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + el: document.body + }); + + assert.equal(new View().el, document.body); + }); + + QUnit.test('_ensureElement with string el', function(assert) { + assert.expect(3); + var View = Backbone.View.extend({ + el: 'body' + }); + assert.strictEqual(new View().el, document.body); + + View = Backbone.View.extend({ + el: '#testElement > h1' + }); + assert.strictEqual(new View().el, $('#testElement > h1').get(0)); + + View = Backbone.View.extend({ + el: '#nonexistent' + }); + assert.ok(!new View().el); + }); + + QUnit.test('with className and id functions', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + className: function() { + return 'className'; + }, + id: function() { + return 'id'; + } + }); + + assert.strictEqual(new View().el.className, 'className'); + assert.strictEqual(new View().el.id, 'id'); + }); + + QUnit.test('with attributes', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + attributes: { + 'id': 'id', + 'class': 'class' + } + }); + + assert.strictEqual(new View().el.className, 'class'); + assert.strictEqual(new View().el.id, 'id'); + }); + + QUnit.test('with attributes as a function', function(assert) { + assert.expect(1); + var View = Backbone.View.extend({ + attributes: function() { + return {'class': 'dynamic'}; + } + }); + + assert.strictEqual(new View().el.className, 'dynamic'); + }); + + QUnit.test('should default to className/id properties', function(assert) { + assert.expect(4); + var View = Backbone.View.extend({ + className: 'backboneClass', + id: 'backboneId', + attributes: { + 'class': 'attributeClass', + 'id': 'attributeId' + } + }); + + var myView = new View; + assert.strictEqual(myView.el.className, 'backboneClass'); + assert.strictEqual(myView.el.id, 'backboneId'); + assert.strictEqual(myView.$el.attr('class'), 'backboneClass'); + assert.strictEqual(myView.$el.attr('id'), 'backboneId'); + }); + + QUnit.test('multiple views per element', function(assert) { + assert.expect(3); + var count = 0; + var $el = $('

'); + + var View = Backbone.View.extend({ + el: $el, + events: { + click: function() { + count++; + } + } + }); + + var view1 = new View; + $el.trigger('click'); + assert.equal(1, count); + + var view2 = new View; + $el.trigger('click'); + assert.equal(3, count); + + view1.delegateEvents(); + $el.trigger('click'); + assert.equal(5, count); + }); + + QUnit.test('custom events', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + el: $('body'), + events: { + fake$event: function() { assert.ok(true); } + } + }); + + var myView = new View; + $('body').trigger('fake$event').trigger('fake$event'); + + $('body').off('fake$event'); + $('body').trigger('fake$event'); + }); + + QUnit.test('#1048 - setElement uses provided object.', function(assert) { + assert.expect(2); + var $el = $('body'); + + var myView = new Backbone.View({el: $el}); + assert.ok(myView.$el === $el); + + myView.setElement($el = $($el)); + assert.ok(myView.$el === $el); + }); + + QUnit.test('#986 - Undelegate before changing element.', function(assert) { + assert.expect(1); + var button1 = $(''); + var button2 = $(''); + + var View = Backbone.View.extend({ + events: { + click: function(e) { + assert.ok(myView.el === e.target); + } + } + }); + + var myView = new View({el: button1}); + myView.setElement(button2); + + button1.trigger('click'); + button2.trigger('click'); + }); + + QUnit.test('#1172 - Clone attributes object', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + attributes: {foo: 'bar'} + }); + + var view1 = new View({id: 'foo'}); + assert.strictEqual(view1.el.id, 'foo'); + + var view2 = new View(); + assert.ok(!view2.el.id); + }); + + QUnit.test('views stopListening', function(assert) { + assert.expect(0); + var View = Backbone.View.extend({ + initialize: function() { + this.listenTo(this.model, 'all x', function() { assert.ok(false); }); + this.listenTo(this.collection, 'all x', function() { assert.ok(false); }); + } + }); + + var myView = new View({ + model: new Backbone.Model, + collection: new Backbone.Collection + }); + + myView.stopListening(); + myView.model.trigger('x'); + myView.collection.trigger('x'); + }); + + QUnit.test('Provide function for el.', function(assert) { + assert.expect(2); + var View = Backbone.View.extend({ + el: function() { + return '

'; + } + }); + + var myView = new View; + assert.ok(myView.$el.is('p')); + assert.ok(myView.$el.has('a')); + }); + + QUnit.test('events passed in options', function(assert) { + assert.expect(1); + var counter = 0; + + var View = Backbone.View.extend({ + el: '#testElement', + increment: function() { + counter++; + } + }); + + var myView = new View({ + events: { + 'click h1': 'increment' + } + }); + + myView.$('h1').trigger('click').trigger('click'); + assert.equal(counter, 2); + }); + + QUnit.test('remove', function(assert) { + assert.expect(2); + var myView = new Backbone.View; + document.body.appendChild(view.el); + + myView.delegate('click', function() { assert.ok(false); }); + myView.listenTo(myView, 'all x', function() { assert.ok(false); }); + + assert.equal(myView.remove(), myView, '#remove returns the view instance'); + myView.$el.trigger('click'); + myView.trigger('x'); + + // In IE8 and below, parentNode still exists but is not document.body. + assert.notEqual(myView.el.parentNode, document.body); + }); + + QUnit.test('setElement', function(assert) { + assert.expect(3); + var myView = new Backbone.View({ + events: { + click: function() { assert.ok(false); } + } + }); + myView.events = { + click: function() { assert.ok(true); } + }; + var oldEl = myView.el; + var $oldEl = myView.$el; + + myView.setElement(document.createElement('div')); + + $oldEl.click(); + myView.$el.click(); + + assert.notEqual(oldEl, myView.el); + assert.notEqual($oldEl, myView.$el); + }); + +})(QUnit); diff --git a/public/vendor/lodash/vendor/firebug-lite/license.txt b/public/vendor/lodash/vendor/firebug-lite/license.txt new file mode 100644 index 00000000000..ba43b7514b3 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/license.txt @@ -0,0 +1,30 @@ +Software License Agreement (BSD License) + +Copyright (c) 2007, Parakey Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Parakey Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Parakey Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/blank.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/blank.gif new file mode 100644 index 0000000000000000000000000000000000000000..6865c960497cdd5663acb5548596d00dba342063 GIT binary patch literal 43 vcmZ?wbhEHbWMp7uXkcL2!@$Lm%Aoj@g;j)sfk6j|f#Qq|3`|Tej11NQl}80X literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBg.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBg.png new file mode 100644 index 0000000000000000000000000000000000000000..f367b427e60e67272b9a2bec37f71054a3269f74 GIT binary patch literal 167 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{R!3HD)xzi;<0>we@P7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-pp~bKV+hCf(36II4Gug<9Mb>)4|i}V@624c zaHEOQ-P127%)EC~hDl(;irH2!_0HWPp3B55=Xc+Eevp069PP;+tLL2nn!@1e>gTe~ HDWM4fu+KD3 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png new file mode 100644 index 0000000000000000000000000000000000000000..cd37a0d52de85c54f3e502c9a4fb6c93c4ebcc46 GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{R!3HD)xzi;<0>we@P7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85p>QL70(Y)*K0-pq;0SV+hA}+fy5P8w^B_1ROu*f8^ifi%MaN z?t+e)KMUj2=R7_T$DElU6!ZEZgVO|-$@brW6qqs2=3ULdX4am+55(@39-C|hG>O5} L)z4*}Q$iB}Zf-R* literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css b/public/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css new file mode 100644 index 00000000000..ba55c7ea8c8 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css @@ -0,0 +1,331 @@ +/* See license.txt for terms of usage */ + +.panelNode-script { + overflow: hidden; + font-family: monospace; +} + +/************************************************************************************************/ + +.scriptTooltip { + position: fixed; + z-index: 2147483647; + padding: 2px 3px; + border: 1px solid #CBE087; + background: LightYellow; + font-family: monospace; + color: #000000; +} + +/************************************************************************************************/ + +.sourceBox { + /* TODO: xxxpedro problem with sourceBox and scrolling elements */ + /*overflow: scroll; /* see issue 1479 */ + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.sourceRow { + white-space: nowrap; + -moz-user-select: text; +} + +.sourceRow.hovered { + background-color: #EEEEEE; +} + +/************************************************************************************************/ + +.sourceLine { + -moz-user-select: none; + margin-right: 10px; + border-right: 1px solid #CCCCCC; + padding: 0px 4px 0 20px; + background: #EEEEEE no-repeat 2px 0px; + color: #888888; + white-space: pre; + font-family: monospace; /* see issue 2953 */ +} + +.noteInToolTip { /* below sourceLine, so it overrides it */ + background-color: #FFD472; +} + +.useA11y .sourceBox .sourceViewport:focus .sourceLine { + background-color: #FFFFC0; + color: navy; + border-right: 1px solid black; +} + +.useA11y .sourceBox .sourceViewport:focus { + outline: none; +} + +.a11y1emSize { + width: 1em; + height: 1em; + position: absolute; +} + +.useA11y .panelStatusLabel:focus { + outline-offset: -2px !important; + } + +.sourceBox > .sourceRow > .sourceLine { + cursor: pointer; +} + +.sourceLine:hover { + text-decoration: none; +} + +.sourceRowText { + white-space: pre; +} + +.sourceRow[exe_line="true"] { + outline: 1px solid #D9D9B6; + margin-right: 1px; + background-color: lightgoldenrodyellow; +} + +.sourceRow[executable="true"] > .sourceLine { + content: "-"; + color: #4AA02C; /* Spring Green */ + font-weight: bold; +} + +.sourceRow[exe_line="true"] > .sourceLine { + background-image: url(chrome://firebug/skin/exe.png); + color: #000000; +} + +.sourceRow[breakpoint="true"] > .sourceLine { + background-image: url(chrome://firebug/skin/breakpoint.png); +} + +.sourceRow[breakpoint="true"][condition="true"] > .sourceLine { + background-image: url(chrome://firebug/skin/breakpointCondition.png); +} + +.sourceRow[breakpoint="true"][disabledBreakpoint="true"] > .sourceLine { + background-image: url(chrome://firebug/skin/breakpointDisabled.png); +} + +.sourceRow[breakpoint="true"][exe_line="true"] > .sourceLine { + background-image: url(chrome://firebug/skin/breakpointExe.png); +} + +.sourceRow[breakpoint="true"][exe_line="true"][disabledBreakpoint="true"] > .sourceLine { + background-image: url(chrome://firebug/skin/breakpointDisabledExe.png); +} + +.sourceLine.editing { + background-image: url(chrome://firebug/skin/breakpoint.png); +} + +/************************************************************************************************/ + +.conditionEditor { + z-index: 2147483647; + position: absolute; + margin-top: 0; + left: 2px; + width: 90%; +} + +.conditionEditorInner { + position: relative; + top: -26px; + height: 0; +} + +.conditionCaption { + margin-bottom: 2px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + font-size: 11px; + color: #226679; +} + +.conditionInput { + width: 100%; + border: 1px solid #0096C0; + font-family: monospace; + font-size: inherit; +} + +.conditionEditorInner1 { + padding-left: 37px; + background: url(condBorders.png) repeat-y; +} + +.conditionEditorInner2 { + padding-right: 25px; + background: url(condBorders.png) repeat-y 100% 0; +} + +.conditionEditorTop1 { + background: url(condCorners.png) no-repeat 100% 0; + margin-left: 37px; + height: 35px; +} + +.conditionEditorTop2 { + position: relative; + left: -37px; + width: 37px; + height: 35px; + background: url(condCorners.png) no-repeat; +} + +.conditionEditorBottom1 { + background: url(condCorners.png) no-repeat 100% 100%; + margin-left: 37px; + height: 33px; +} + +.conditionEditorBottom2 { + position: relative; left: -37px; + width: 37px; + height: 33px; + background: url(condCorners.png) no-repeat 0 100%; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.upsideDown { + margin-top: 2px; +} + +.upsideDown .conditionEditorInner { + top: -8px; +} + +.upsideDown .conditionEditorInner1 { + padding-left: 33px; + background: url(condBordersUps.png) repeat-y; +} + +.upsideDown .conditionEditorInner2 { + padding-right: 25px; + background: url(condBordersUps.png) repeat-y 100% 0; +} + +.upsideDown .conditionEditorTop1 { + background: url(condCornersUps.png) no-repeat 100% 0; + margin-left: 33px; + height: 25px; +} + +.upsideDown .conditionEditorTop2 { + position: relative; + left: -33px; + width: 33px; + height: 25px; + background: url(condCornersUps.png) no-repeat; +} + +.upsideDown .conditionEditorBottom1 { + background: url(condCornersUps.png) no-repeat 100% 100%; + margin-left: 33px; + height: 43px; +} + +.upsideDown .conditionEditorBottom2 { + position: relative; + left: -33px; + width: 33px; + height: 43px; + background: url(condCornersUps.png) no-repeat 0 100%; +} + +/************************************************************************************************/ + +.breakpointsGroupListBox { + overflow: hidden; +} + +.breakpointBlockHead { + position: relative; + padding-top: 4px; +} + +.breakpointBlockHead > .checkbox { + margin-right: 4px; +} + +.breakpointBlockHead > .objectLink-sourceLink { + top: 4px; + right: 20px; + background-color: #FFFFFF; /* issue 3308 */ +} + +.breakpointBlockHead > .closeButton { + position: absolute; + top: 2px; + right: 2px; +} + +.breakpointCheckbox { + margin-top: 0; + vertical-align: top; +} + +.breakpointName { + margin-left: 4px; + font-weight: bold; +} + +.breakpointRow[aria-checked="false"] > .breakpointBlockHead > *, +.breakpointRow[aria-checked="false"] > .breakpointCode { + opacity: 0.5; +} + +.breakpointRow[aria-checked="false"] .breakpointCheckbox, +.breakpointRow[aria-checked="false"] .objectLink-sourceLink, +.breakpointRow[aria-checked="false"] .closeButton, +.breakpointRow[aria-checked="false"] .breakpointMutationType { + opacity: 1.0 !important; +} + +.breakpointCode { + overflow: hidden; + white-space: nowrap; + padding-left: 24px; + padding-bottom: 2px; + border-bottom: 1px solid #D7D7D7; + font-family: monospace; + color: DarkGreen; +} + +.breakpointCondition { + white-space: nowrap; + padding-left: 24px; + padding-bottom: 2px; + border-bottom: 1px solid #D7D7D7; + font-family: monospace; + color: Gray; +} + +.breakpointBlock-breakpoints > .groupHeader { + display: none; +} + +.breakpointBlock-monitors > .breakpointCode { + padding: 0; +} + +.breakpointBlock-errorBreakpoints .breakpointCheckbox, +.breakpointBlock-monitors .breakpointCheckbox { + display: none; +} + +.breakpointHeader { + margin: 0 !important; + border-top: none !important; +} diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/detach.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/detach.png new file mode 100644 index 0000000000000000000000000000000000000000..0ddb9a1764a7244e13fb3ae0ed1ae7a286e9e138 GIT binary patch literal 655 zcmV;A0&x9_P)vmZ_X00HYsL_t(I zjiuASixWW@g>ab5{?6{qvzRs9VNlU&_RX;G^Jd=ee{7lWMJ%eR z3{AF20Ep)y!mj|M5oBktri!{UFOVt#7H?iRbJqo>rvWZx)o?vXGay^Lw{)KBp1m~6 z`yibH#KZ5%*nGxgcWzCZ*UX-Er8%I%2}0r^*bN8_KZM_Ehl)0XcAG?Nik4;0p2ZEv zp<>5C%zNKM%r9+#a^70sjNEQv#^&Ju9OMMhlU>*rIK>E&n2*DR%_ydvx7H!M3jnuU pr&fUue|MSIFK;|s+XejV_yNX*_HvmZ_X00E{+L_t(I zjir;nOIuMC$3G|U#kBgKiL^m1)>#S;QgEwd9Wr!^UE86Xp#Oo-|3IOegHA##f`vL% zHz^%l{n0?s!A_+^L*7foyk|~_+#8-6sOW{uIh=dG-}C)m?iE$#A-oX+cONLulyE)2Co+jb{(Rfv*ZL|+6b2>LB~evAiToXV{iZNH zHGL=9zYq2~-~Cd1t2*QYgiy$PK1ubsGpXI?#pku2;FH<+$gzq>+rq?PUsUDL6FQc$Ktz0?m=3D&(09>5@hnaP5s*8v+9YvBLK{>@_+VFn)YR16+}zUA(%RbE(b3V_+1b_A)!p6Q z+uPgM*EeCpgozU;PMS1n^5n@=rc9YSb?S^6GiJ`5IeYf(`Sa&5Sg>HxqD4!VELpZ} z+4AMfSFBjEe*OAQn>PKYF;M&`=v}?EMTFVmhmRH-h=@!%H(`U1mWjL~(r(9$jZjy>BwLW0Bd-< AumAu6 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/disable.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/disable.png new file mode 100644 index 0000000000000000000000000000000000000000..c28bcdf24a88d4d1266486af283b3557011e916f GIT binary patch literal 543 zcmV+)0^t3LP)x3Se~ z6`Rdw>Ucah*6THwB#AIg5(ME2(GQ@(Ktm}Oiyw-jOuF4}$mMdq0C<3425O0ZzyB?w zG!F)YEE0)iG)?PM7Ep{MxC7835;9bI9SjCUm5V5UB+D}KZ98BrZv^)Fd<+#H0nARP zQ)U>(v8{m|K_IKw>w7Q>#(lF;D3rirvolE&A?Rx~8nO9&p2b7wKRBPy-|iA#kVa_f zNy%i=1n?4ep5s9#8zHu-s;UjH)oO9r-65TzcW3vZTrNK$vxY`l(P*>{?$@ZuCX

zSS&c+6eN1i<#IPJ%7UN@%1Hq6;c$2c$9~94N=7cBH!TRR>^toYUDxfe0SXBMg4!q6 hR;g6le~mu@1^}m-%vu#A_1FLa002ovPDHLkV1iC4;#>d# literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.gif new file mode 100644 index 0000000000000000000000000000000000000000..70565a83cce61a6ce84ced32713fe11dfe32ad32 GIT binary patch literal 344 zcmb8qy-Gr100!XKJeKL_&p{0{mG9UuD279lD4bHr&LBv10SQS(TL?8ow74`xPC-j8 z&2q0GiEI6k05_T4}da2s#{eI_YvzRq652>!r`fK!Cv@Lm_G!BN0ZU zjK!EpFqvd3#dMmP40WA_0_W%Xe_BV)t(8la@}gW{sVvL9Q7A%dD<&Is#PI7citdH8 z!{(A&o4Y7AUW+m;uj}%BNE_SVs~Pd|!PQB3-vKr2s6vs%_Gnj+LH>~wJ-A1;nr&k!$NdEx3Rg!1` literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.png new file mode 100644 index 0000000000000000000000000000000000000000..26fe37542f865a5fc668ceaaa597261351416abf GIT binary patch literal 512 zcmV+b0{{JqP)B9UvD<_Rt@Fa9A3cn2&8 zsB&(OLveLQ9g(&fjgs5jRpxS*W_>#78#q)z2})fFv@41YM6bY}(`dWTB8p3#fBpflR0K9xPy7!y|#6zbl^UQ?6C> ziLs8A6~MOL#(GM{<*3a92_qDO&+k9_0E*B0XQ#8__5i~GMk5TTVbcWs{xXKcgCD>d zPss3n0XJwgfR~q|ccusdH{^Eisu~qG{#@uF){aM55v@S-*L}h!u=!w0000vmZ_X00G%aL_t(I zjir-6Xj4%XhritS^4b@Z=1IwrrO=KdrKp>WpbkMn+#K8l16@p^xL9l&?2^Se*4bSI zZ3h(s5_C`}$I=d>gHQ~KukZgo4ljuYOAUIadoTC*oqNykoQ#O@KVc3RpRQi719t%D zFPbj!==Afab%2bB@czoRb!FT2*-Mw1Idc|e+ea$3=h6H4frD2s(Vka7_u|=x2@r^` z%`PrsmCE>^zaE?3DwWACE^+X#Qv*_fFWSk?Eue#NqJQV+7Wne^jRS)k_#&u`LZWyg zfXXQRLGe?7)<_%?CkgYnZyqb&fBXc~Fz~hh4ImoaL&7kiy|&4P2lv>yd!MY4niM55 zm1cwX+9qY^B&Ic$Y5+9^NUu-U^{~T;?d4S}%?5=;Q%E$GW`phJRqQY#>v~A9KLY%E zL9d4il(b+egHCmY^IH!A=u}rIXDy0aFkP3CW*pET9Tc^OAf$cc768SmX^NsS_CY+V%VO=|af7lf^PJM_L#r!hSbm*AK`Wss)2t0NAQrTnB1QHcYMR&g+daz`w>% X_aN9B#@k{#00000NkvXXu0mjfMjaKM literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/downActive.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/downActive.png new file mode 100644 index 0000000000000000000000000000000000000000..f4312b2ffbc485366e5b3eb2d52fac976597b256 GIT binary patch literal 543 zcmV+)0^t3LP)vmZ_X00DbRL_t(I zjir;lN&`UU}0fnZKwDGo-ZJ=v$nCY@Ck$jQ3OS~ z!%8ev5NyplSR@yfwM_ljM z8jB*`Hb4wcNo$%HXKe{eS`(X6ZGZ^i>XM`_{4FB0rx}o(pOF9q2M0fbrz0a+hWRA0 zo}YLDc0HG|AY|Kf-&@oKDnZDu=h6%0TLc}cAO~om1k^p3N)Y1kR%j&%se3LZb0?RT zL9S(KAoN_kAOv&nH}W*AOaU#Ddjn#?atr)T5CMsK8B=2(NWW9(7x;lMU7%2C99vCi z!un`rT1r;zB^D%h<6WT3xz>HE$zyj?J hQ*}dR`IF$U@d_-*f~iPPaE<@~002ovPDHLkV1gf_)cF7a literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/downHover.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/downHover.png new file mode 100644 index 0000000000000000000000000000000000000000..8144e63787d3015e5ab2219bf24fab87b9fe1141 GIT binary patch literal 526 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ za0`Jja>QWe}Xi&D$;i?WOTH_Q7mFfe9$x;Tbd^e&xj@9h*QajbrQoamc4Q>{6x z_ytW`lXX-V5t7R4YFksyd&xV$`T4iwda7R! zJhXjYP{)71@w;f~Q~OPae7%pbmNneb_u;>xynx1mHSr=7#+7eGwA%h*Z*MZ+XTsH-c1$v(;2R~YOE~MaXMrp z#grQQO6f^sMM#s10JG77PL9;jC?y7w5KF;Cwi!kjwzI7CKh1VaNyKOJR@VrYO*JNd zDNH`T4E=0J8X}A3Pf43g%&J)E`RTHyE!+3wyh#^!drZ_;wU=v+?+hwt*eG80o^5kL zHecaeo;4{7vfXU+;}`yAIs31J(U_azwvuSV^pkBXx2=!*oAe=kFC#Z!&8|0*agxB8 OVeoYIb6Mw<&;$T_rpeI& literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..0c377e307e54c7d1ac78b316b6b949085aa368a6 GIT binary patch literal 447 zcmV;w0YLtVP)z^Q>-8KIssI20AY({UO#lFGm;eBCjsO7aNB{ta0001Y z?*IVM@BjcZ_W%GKbhgrL3jhECHAzH4RCwBKl08eqP!xurR1gFQcfld}1BAN@f~4SF z@*4vFk1iRU6beo*g41nrsg!_|n{jZexJXq{gcLysKj`al6LV8~;J`hX^Kjnt5lF(f zeh};dF|Y-E06B2nfxG`CNl3!Nj)k);&;w`+dIQ~qMj#6Z4ama!0Q3mb0{;c&4m|Xs z?ii#fk|ZffYA8u0NwX-@r31IIu+#^AffR;PQPf}ws3@c`)WkujSlGDmC5>cn7<1bWUJj$ZS+iPe7jBcSDR!8x$w6_L3u)yphaIGl zNwmvu6p_Y-3q=&oV#+9g^BMelzQE&f+IxEaa^$28Y{53{z%Ha=5BA{z4j~Iia0=&e z30H6pH*g2{@BokS1kdmSd3c2ayumvZ;R8OQ1YhtCLOG2_Fd7A`RR{)!cwES2gnVB3 zAN7yI5()UDfpJxQA{tUToNf>5QiVv0nQqGdMh7IZ?3$-qFHTs`LebF;-)7Dmi7pLa z1ac;?Oq5u(71vFY<5qEca;3d;EHtvv=W?ASZ#z>3?OfaJkVw{`RMtx!7IAYp>@(A( p)p5Ui~H0X8YDC6$Gxpb|xtYf@N==`2Jc z7Ah8ji(kR7VtwakC$}D$g(o{ZGjC>QZ;f+~bR8H|#%4hcOnLVm*j;w6MZq#Egs5(a zc4lEugK?iQ0QcYtDcH902T2xS;XcvY8O)TA|L_Qo0dkkJVlXBT;dmV(QXWLO$HEed zt}ftbg3DK{l45N>4BPBOtRiE?7^^l0JFaL_w@cLT6E&N;kZtyH3~NT_jk(q5@35sP zaxP8XaIwukAMZ)K?GhDNr$f|e@AO4;e+3u-uo^Lr*!;%I00000NkvXXu0mjfj3mDh literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css new file mode 100644 index 00000000000..42f9faf5d58 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css @@ -0,0 +1,817 @@ +.fbBtnPressed { + background: #ECEBE3; + padding: 3px 6px 2px 7px !important; + margin: 1px 0 0 1px; + _margin: 1px -1px 0 1px; + border: 1px solid #ACA899 !important; + border-color: #ACA899 #ECEBE3 #ECEBE3 #ACA899 !important; +} + +.fbToolbarButtons { + display: none; +} + +#fbStatusBarBox { + display: none; +} + +/************************************************************************************************ + Error Popup +*************************************************************************************************/ +#fbErrorPopup { + position: absolute; + right: 0; + bottom: 0; + height: 19px; + width: 75px; + background: url(sprite.png) #f1f2ee 0 0; + z-index: 999; +} + +#fbErrorPopupContent { + position: absolute; + right: 0; + top: 1px; + height: 18px; + width: 75px; + _width: 74px; + border-left: 1px solid #aca899; +} + +#fbErrorIndicator { + position: absolute; + top: 2px; + right: 5px; +} + + + + + + + + + + +.fbBtnInspectActive { + background: #aaa; + color: #fff !important; +} + +/************************************************************************************************ + General +*************************************************************************************************/ +html, body { + margin: 0; + padding: 0; + overflow: hidden; +} + +body { + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + background: #fff; +} + +.clear { + clear: both; +} + +/************************************************************************************************ + Mini Chrome +*************************************************************************************************/ +#fbMiniChrome { + display: none; + right: 0; + height: 27px; + background: url(sprite.png) #f1f2ee 0 0; + margin-left: 1px; +} + +#fbMiniContent { + display: block; + position: relative; + left: -1px; + right: 0; + top: 1px; + height: 25px; + border-left: 1px solid #aca899; +} + +#fbToolbarSearch { + float: right; + border: 1px solid #ccc; + margin: 0 5px 0 0; + background: #fff url(search.png) no-repeat 4px 2px; + padding-left: 20px; + font-size: 11px; +} + +#fbToolbarErrors { + float: right; + margin: 1px 4px 0 0; + font-size: 11px; +} + +#fbLeftToolbarErrors { + float: left; + margin: 7px 0px 0 5px; + font-size: 11px; +} + +.fbErrors { + padding-left: 20px; + height: 14px; + background: url(errorIcon.png) no-repeat; + color: #f00; + font-weight: bold; +} + +#fbMiniErrors { + display: inline; + display: none; + float: right; + margin: 5px 2px 0 5px; +} + +#fbMiniIcon { + float: right; + margin: 3px 4px 0; + height: 20px; + width: 20px; + float: right; + background: url(sprite.png) 0 -135px; + cursor: pointer; +} + + +/************************************************************************************************ + Master Layout +*************************************************************************************************/ +#fbChrome { + position: fixed; + overflow: hidden; + height: 100%; + width: 100%; + border-collapse: collapse; + background: #fff; +} + +#fbTop { + height: 49px; +} + +#fbToolbar { + position: absolute; + z-index: 5; + width: 100%; + top: 0; + background: url(sprite.png) #f1f2ee 0 0; + height: 27px; + font-size: 11px; + overflow: hidden; +} + +#fbPanelBarBox { + top: 27px; + position: absolute; + z-index: 8; + width: 100%; + background: url(sprite.png) #dbd9c9 0 -27px; + height: 22px; +} + +#fbContent { + height: 100%; + vertical-align: top; +} + +#fbBottom { + height: 18px; + background: #fff; +} + +/************************************************************************************************ + Sub-Layout +*************************************************************************************************/ + +/* fbToolbar +*************************************************************************************************/ +#fbToolbarIcon { + float: left; + padding: 4px 5px 0; +} + +#fbToolbarIcon a { + display: block; + height: 20px; + width: 20px; + background: url(sprite.png) 0 -135px; + text-decoration: none; + cursor: default; +} + +#fbToolbarButtons { + float: left; + padding: 4px 2px 0 5px; +} + +#fbToolbarButtons a { + text-decoration: none; + display: block; + float: left; + color: #000; + padding: 4px 8px 4px; + cursor: default; +} + +#fbToolbarButtons a:hover { + color: #333; + padding: 3px 7px 3px; + border: 1px solid #fff; + border-bottom: 1px solid #bbb; + border-right: 1px solid #bbb; +} + +#fbStatusBarBox { + position: relative; + top: 5px; + line-height: 19px; + cursor: default; +} + +.fbToolbarSeparator{ + overflow: hidden; + border: 1px solid; + border-color: transparent #fff transparent #777; + _border-color: #eee #fff #eee #777; + height: 7px; + margin: 10px 6px 0 0; + float: left; +} + +.fbStatusBar span { + color: #808080; + padding: 0 4px 0 0; +} + +.fbStatusBar span a { + text-decoration: none; + color: black; +} + +.fbStatusBar span a:hover { + color: blue; + cursor: pointer; +} + + +#fbWindowButtons { + position: absolute; + white-space: nowrap; + right: 0; + top: 0; + height: 17px; + _width: 50px; + padding: 5px 0 5px 5px; + z-index: 6; + background: url(sprite.png) #f1f2ee 0 0; +} + +/* fbPanelBarBox +*************************************************************************************************/ + +#fbPanelBar1 { + width: 255px; /* fixed width to avoid tabs breaking line */ + z-index: 8; + left: 0; + white-space: nowrap; + background: url(sprite.png) #dbd9c9 0 -27px; + position: absolute; + left: 4px; +} + +#fbPanelBar2Box { + background: url(sprite.png) #dbd9c9 0 -27px; + position: absolute; + height: 22px; + width: 300px; /* fixed width to avoid tabs breaking line */ + z-index: 9; + right: 0; +} + +#fbPanelBar2 { + position: absolute; + width: 290px; /* fixed width to avoid tabs breaking line */ + height: 22px; + padding-left: 10px; +} + +/* body +*************************************************************************************************/ +.fbPanel { + display: none; +} + +#fbPanelBox1, #fbPanelBox2 { + max-height: inherit; + height: 100%; + font-size: 11px; +} + +#fbPanelBox2 { + background: #fff; +} + +#fbPanelBox2 { + width: 300px; + background: #fff; +} + +#fbPanel2 { + padding-left: 6px; + background: #fff; +} + +.hide { + overflow: hidden !important; + position: fixed !important; + display: none !important; + visibility: hidden !important; +} + +/* fbBottom +*************************************************************************************************/ + +#fbCommand { + height: 18px; +} + +#fbCommandBox { + position: absolute; + width: 100%; + height: 18px; + bottom: 0; + overflow: hidden; + z-index: 9; + background: #fff; + border: 0; + border-top: 1px solid #ccc; +} + +#fbCommandIcon { + position: absolute; + color: #00f; + top: 2px; + left: 7px; + display: inline; + font: 11px Monaco, monospace; + z-index: 10; +} + +#fbCommandLine { + position: absolute; + width: 100%; + top: 0; + left: 0; + border: 0; + margin: 0; + padding: 2px 0 2px 32px; + font: 11px Monaco, monospace; + z-index: 9; +} + +div.fbFitHeight { + overflow: auto; + _position: absolute; +} + + +/************************************************************************************************ + Layout Controls +*************************************************************************************************/ + +/* fbToolbar buttons +*************************************************************************************************/ +#fbWindowButtons a { + font-size: 1px; + width: 16px; + height: 16px; + display: block; + float: right; + margin-right: 4px; + text-decoration: none; + cursor: default; +} + +#fbWindow_btClose { + background: url(sprite.png) 0 -119px; +} + +#fbWindow_btClose:hover { + background: url(sprite.png) -16px -119px; +} + +#fbWindow_btDetach { + background: url(sprite.png) -32px -119px; +} + +#fbWindow_btDetach:hover { + background: url(sprite.png) -48px -119px; +} + +/* fbPanelBarBox tabs +*************************************************************************************************/ +.fbTab { + text-decoration: none; + display: none; + float: left; + width: auto; + float: left; + cursor: default; + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + font-weight: bold; + height: 22px; + color: #565656; +} + +.fbPanelBar span { + display: block; + float: left; +} + +.fbPanelBar .fbTabL,.fbPanelBar .fbTabR { + height: 22px; + width: 8px; +} + +.fbPanelBar .fbTabText { + padding: 4px 1px 0; +} + +a.fbTab:hover { + background: url(sprite.png) 0 -73px; +} + +a.fbTab:hover .fbTabL { + background: url(sprite.png) -16px -96px; +} + +a.fbTab:hover .fbTabR { + background: url(sprite.png) -24px -96px; +} + +.fbSelectedTab { + background: url(sprite.png) #f1f2ee 0 -50px !important; + color: #000; +} + +.fbSelectedTab .fbTabL { + background: url(sprite.png) 0 -96px !important; +} + +.fbSelectedTab .fbTabR { + background: url(sprite.png) -8px -96px !important; +} + +/* splitters +*************************************************************************************************/ +#fbHSplitter { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 5px; + overflow: hidden; + cursor: n-resize !important; + background: url(pixel_transparent.gif); + z-index: 9; +} + +#fbHSplitter.fbOnMovingHSplitter { + height: 100%; + z-index: 100; +} + +.fbVSplitter { + background: #ece9d8; + color: #000; + border: 1px solid #716f64; + border-width: 0 1px; + border-left-color: #aca899; + width: 4px; + cursor: e-resize; + overflow: hidden; + right: 294px; + text-decoration: none; + z-index: 9; + position: absolute; + height: 100%; + top: 27px; + _width: 6px; +} + +/************************************************************************************************/ +div.lineNo { + font: 11px Monaco, monospace; + float: left; + display: inline; + position: relative; + margin: 0; + padding: 0 5px 0 20px; + background: #eee; + color: #888; + border-right: 1px solid #ccc; + text-align: right; +} + +pre.nodeCode { + font: 11px Monaco, monospace; + margin: 0; + padding-left: 10px; + overflow: hidden; + /* + _width: 100%; + /**/ +} + +/************************************************************************************************/ +.nodeControl { + margin-top: 3px; + margin-left: -14px; + float: left; + width: 9px; + height: 9px; + overflow: hidden; + cursor: default; + background: url(tree_open.gif); + _float: none; + _display: inline; + _position: absolute; +} + +div.nodeMaximized { + background: url(tree_close.gif); +} + +div.objectBox-element { + padding: 1px 3px; +} +.objectBox-selector{ + cursor: default; +} + +.selectedElement{ + background: highlight; + /* background: url(roundCorner.svg); Opera */ + color: #fff !important; +} +.selectedElement span{ + color: #fff !important; +} + +/* Webkit CSS Hack - bug in "highlight" named color */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + .selectedElement{ + background: #316AC5; + color: #fff !important; + } +} + +/************************************************************************************************/ +/************************************************************************************************/ +.logRow * { + font-size: 11px; +} + +.logRow { + position: relative; + border-bottom: 1px solid #D7D7D7; + padding: 2px 4px 1px 6px; + background-color: #FFFFFF; +} + +.logRow-command { + font-family: Monaco, monospace; + color: blue; +} + +.objectBox-string, +.objectBox-text, +.objectBox-number, +.objectBox-function, +.objectLink-element, +.objectLink-textNode, +.objectLink-function, +.objectBox-stackTrace, +.objectLink-profile { + font-family: Monaco, monospace; +} + +.objectBox-null { + padding: 0 2px; + border: 1px solid #666666; + background-color: #888888; + color: #FFFFFF; +} + +.objectBox-string { + color: red; + white-space: pre; +} + +.objectBox-number { + color: #000088; +} + +.objectBox-function { + color: DarkGreen; +} + +.objectBox-object { + color: DarkGreen; + font-weight: bold; + font-family: Lucida Grande, sans-serif; +} + +.objectBox-array { + color: #000; +} + +/************************************************************************************************/ +.logRow-info,.logRow-error,.logRow-warning { + background: #fff no-repeat 2px 2px; + padding-left: 20px; + padding-bottom: 3px; +} + +.logRow-info { + background-image: url(infoIcon.png); +} + +.logRow-warning { + background-color: cyan; + background-image: url(warningIcon.png); +} + +.logRow-error { + background-color: LightYellow; + background-image: url(errorIcon.png); + color: #f00; +} + +.errorMessage { + vertical-align: top; + color: #f00; +} + +.objectBox-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + color: #0000FF; +} + +/************************************************************************************************/ +.logRow-group { + background: #EEEEEE; + border-bottom: none; +} + +.logGroup { + background: #EEEEEE; +} + +.logGroupBox { + margin-left: 24px; + border-top: 1px solid #D7D7D7; + border-left: 1px solid #D7D7D7; +} + +/************************************************************************************************/ +.selectorTag,.selectorId,.selectorClass { + font-family: Monaco, monospace; + font-weight: normal; +} + +.selectorTag { + color: #0000FF; +} + +.selectorId { + color: DarkBlue; +} + +.selectorClass { + color: red; +} + +/************************************************************************************************/ +.objectBox-element { + font-family: Monaco, monospace; + color: #000088; +} + +.nodeChildren { + padding-left: 26px; +} + +.nodeTag { + color: blue; + cursor: pointer; +} + +.nodeValue { + color: #FF0000; + font-weight: normal; +} + +.nodeText,.nodeComment { + margin: 0 2px; + vertical-align: top; +} + +.nodeText { + color: #333333; + font-family: Monaco, monospace; +} + +.nodeComment { + color: DarkGreen; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeHidden, .nodeHidden * { + color: #888888; +} + +.nodeHidden .nodeTag { + color: #5F82D9; +} + +.nodeHidden .nodeValue { + color: #D86060; +} + +.selectedElement .nodeHidden, .selectedElement .nodeHidden * { + color: SkyBlue !important; +} + + +/************************************************************************************************/ +.log-object { + /* + _position: relative; + _height: 100%; + /**/ +} + +.property { + position: relative; + clear: both; + height: 15px; +} + +.propertyNameCell { + vertical-align: top; + float: left; + width: 28%; + position: absolute; + left: 0; + z-index: 0; +} + +.propertyValueCell { + float: right; + width: 68%; + background: #fff; + position: absolute; + padding-left: 5px; + display: table-cell; + right: 0; + z-index: 1; + /* + _position: relative; + /**/ +} + +.propertyName { + font-weight: bold; +} + +.FirebugPopup { + height: 100% !important; +} + +.FirebugPopup #fbWindowButtons { + display: none !important; +} + +.FirebugPopup #fbHSplitter { + display: none !important; +} diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css new file mode 100644 index 00000000000..13a41d284b8 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css @@ -0,0 +1,20 @@ +/************************************************************************************************/ +#fbToolbarSearch { + background-image: url(search.gif) !important; +} +/************************************************************************************************/ +.fbErrors { + background-image: url(errorIcon.gif) !important; +} +/************************************************************************************************/ +.logRow-info { + background-image: url(infoIcon.gif) !important; +} + +.logRow-warning { + background-image: url(warningIcon.gif) !important; +} + +.logRow-error { + background-image: url(errorIcon.gif) !important; +} diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css new file mode 100644 index 00000000000..a1465ec5f35 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css @@ -0,0 +1,3147 @@ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* Loose */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* +.netInfoResponseHeadersTitle, netInfoResponseHeadersBody { + display: none; +} +/**/ + +.obscured { + left: -999999px !important; +} + +/* IE6 need a separated rule, otherwise it will not recognize it */ +.collapsed { + display: none; +} + +[collapsed="true"] { + display: none; +} + +#fbCSS { + padding: 0 !important; +} + +.cssPropDisable { + float: left; + display: block; + width: 2em; + cursor: default; +} + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* panelBase */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ + +/************************************************************************************************/ + +.infoTip { + z-index: 2147483647; + position: fixed; + padding: 2px 3px; + border: 1px solid #CBE087; + background: LightYellow; + font-family: Monaco, monospace; + color: #000000; + display: none; + white-space: nowrap; + pointer-events: none; +} + +.infoTip[active="true"] { + display: block; +} + +.infoTipLoading { + width: 16px; + height: 16px; + background: url(chrome://firebug/skin/loading_16.gif) no-repeat; +} + +.infoTipImageBox { + font-size: 11px; + min-width: 100px; + text-align: center; +} + +.infoTipCaption { + font-size: 11px; + font: Monaco, monospace; +} + +.infoTipLoading > .infoTipImage, +.infoTipLoading > .infoTipCaption { + display: none; +} + +/************************************************************************************************/ + +h1.groupHeader { + padding: 2px 4px; + margin: 0 0 4px 0; + border-top: 1px solid #CCCCCC; + border-bottom: 1px solid #CCCCCC; + background: #eee url(group.gif) repeat-x; + font-size: 11px; + font-weight: bold; + _position: relative; +} + +/************************************************************************************************/ + +.inlineEditor, +.fixedWidthEditor { + z-index: 2147483647; + position: absolute; + display: none; +} + +.inlineEditor { + margin-left: -6px; + margin-top: -3px; + /* + _margin-left: -7px; + _margin-top: -5px; + /**/ +} + +.textEditorInner, +.fixedWidthEditor { + margin: 0 0 0 0 !important; + padding: 0; + border: none !important; + font: inherit; + text-decoration: inherit; + background-color: #FFFFFF; +} + +.fixedWidthEditor { + border-top: 1px solid #888888 !important; + border-bottom: 1px solid #888888 !important; +} + +.textEditorInner { + position: relative; + top: -7px; + left: -5px; + + outline: none; + resize: none; + + /* + _border: 1px solid #999 !important; + _padding: 1px !important; + _filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color="#55404040"); + /**/ +} + +.textEditorInner1 { + padding-left: 11px; + background: url(textEditorBorders.png) repeat-y; + _background: url(textEditorBorders.gif) repeat-y; + _overflow: hidden; +} + +.textEditorInner2 { + position: relative; + padding-right: 2px; + background: url(textEditorBorders.png) repeat-y 100% 0; + _background: url(textEditorBorders.gif) repeat-y 100% 0; + _position: fixed; +} + +.textEditorTop1 { + background: url(textEditorCorners.png) no-repeat 100% 0; + margin-left: 11px; + height: 10px; + _background: url(textEditorCorners.gif) no-repeat 100% 0; + _overflow: hidden; +} + +.textEditorTop2 { + position: relative; + left: -11px; + width: 11px; + height: 10px; + background: url(textEditorCorners.png) no-repeat; + _background: url(textEditorCorners.gif) no-repeat; +} + +.textEditorBottom1 { + position: relative; + background: url(textEditorCorners.png) no-repeat 100% 100%; + margin-left: 11px; + height: 12px; + _background: url(textEditorCorners.gif) no-repeat 100% 100%; +} + +.textEditorBottom2 { + position: relative; + left: -11px; + width: 11px; + height: 12px; + background: url(textEditorCorners.png) no-repeat 0 100%; + _background: url(textEditorCorners.gif) no-repeat 0 100%; +} + + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* CSS */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ + +/* See license.txt for terms of usage */ + +.panelNode-css { + overflow-x: hidden; +} + +.cssSheet > .insertBefore { + height: 1.5em; +} + +.cssRule { + position: relative; + margin: 0; + padding: 1em 0 0 6px; + font-family: Monaco, monospace; + color: #000000; +} + +.cssRule:first-child { + padding-top: 6px; +} + +.cssElementRuleContainer { + position: relative; +} + +.cssHead { + padding-right: 150px; +} + +.cssProp { + /*padding-left: 2em;*/ +} + +.cssPropName { + color: DarkGreen; +} + +.cssPropValue { + margin-left: 8px; + color: DarkBlue; +} + +.cssOverridden span { + text-decoration: line-through; +} + +.cssInheritedRule { +} + +.cssInheritLabel { + margin-right: 0.5em; + font-weight: bold; +} + +.cssRule .objectLink-sourceLink { + top: 0; +} + +.cssProp.editGroup:hover { + background: url(disable.png) no-repeat 2px 1px; + _background: url(disable.gif) no-repeat 2px 1px; +} + +.cssProp.editGroup.editing { + background: none; +} + +.cssProp.disabledStyle { + background: url(disableHover.png) no-repeat 2px 1px; + _background: url(disableHover.gif) no-repeat 2px 1px; + opacity: 1; + color: #CCCCCC; +} + +.disabledStyle .cssPropName, +.disabledStyle .cssPropValue { + color: #CCCCCC; +} + +.cssPropValue.editing + .cssSemi, +.inlineExpander + .cssSemi { + display: none; +} + +.cssPropValue.editing { + white-space: nowrap; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.stylePropName { + font-weight: bold; + padding: 0 4px 4px 4px; + width: 50%; +} + +.stylePropValue { + width: 50%; +} +/* +.useA11y .a11yCSSView .focusRow:focus { + outline: none; + background-color: transparent + } + + .useA11y .a11yCSSView .focusRow:focus .cssSelector, + .useA11y .a11yCSSView .focusRow:focus .cssPropName, + .useA11y .a11yCSSView .focusRow:focus .cssPropValue, + .useA11y .a11yCSSView .computedStyleRow:focus, + .useA11y .a11yCSSView .groupHeader:focus { + outline: 2px solid #FF9933; + outline-offset: -2px; + background-color: #FFFFD6; + } + + .useA11y .a11yCSSView .groupHeader:focus { + outline-offset: -2px; + } +/**/ + + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* Net */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ + +/* See license.txt for terms of usage */ + +.panelNode-net { + overflow-x: hidden; +} + +.netTable { + width: 100%; +} + +/************************************************************************************************/ + +.hideCategory-undefined .category-undefined, +.hideCategory-html .category-html, +.hideCategory-css .category-css, +.hideCategory-js .category-js, +.hideCategory-image .category-image, +.hideCategory-xhr .category-xhr, +.hideCategory-flash .category-flash, +.hideCategory-txt .category-txt, +.hideCategory-bin .category-bin { + display: none; +} + +/************************************************************************************************/ + +.netHeadRow { + background: url(chrome://firebug/skin/group.gif) repeat-x #FFFFFF; +} + +.netHeadCol { + border-bottom: 1px solid #CCCCCC; + padding: 2px 4px 2px 18px; + font-weight: bold; +} + +.netHeadLabel { + white-space: nowrap; + overflow: hidden; +} + +/************************************************************************************************/ +/* Header for Net panel table */ + +.netHeaderRow { + height: 16px; +} + +.netHeaderCell { + cursor: pointer; + -moz-user-select: none; + border-bottom: 1px solid #9C9C9C; + padding: 0 !important; + font-weight: bold; + background: #BBBBBB url(chrome://firebug/skin/tableHeader.gif) repeat-x; + white-space: nowrap; +} + +.netHeaderRow > .netHeaderCell:first-child > .netHeaderCellBox { + padding: 2px 14px 2px 18px; +} + +.netHeaderCellBox { + padding: 2px 14px 2px 10px; + border-left: 1px solid #D9D9D9; + border-right: 1px solid #9C9C9C; +} + +.netHeaderCell:hover:active { + background: #959595 url(chrome://firebug/skin/tableHeaderActive.gif) repeat-x; +} + +.netHeaderSorted { + background: #7D93B2 url(chrome://firebug/skin/tableHeaderSorted.gif) repeat-x; +} + +.netHeaderSorted > .netHeaderCellBox { + border-right-color: #6B7C93; + background: url(chrome://firebug/skin/arrowDown.png) no-repeat right; +} + +.netHeaderSorted.sortedAscending > .netHeaderCellBox { + background-image: url(chrome://firebug/skin/arrowUp.png); +} + +.netHeaderSorted:hover:active { + background: #536B90 url(chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x; +} + +/************************************************************************************************/ +/* Breakpoints */ + +.panelNode-net .netRowHeader { + display: block; +} + +.netRowHeader { + cursor: pointer; + display: none; + height: 15px; + margin-right: 0 !important; +} + +/* Display brekpoint disc */ +.netRow .netRowHeader { + background-position: 5px 1px; +} + +.netRow[breakpoint="true"] .netRowHeader { + background-image: url(chrome://firebug/skin/breakpoint.png); +} + +.netRow[breakpoint="true"][disabledBreakpoint="true"] .netRowHeader { + background-image: url(chrome://firebug/skin/breakpointDisabled.png); +} + +.netRow.category-xhr:hover .netRowHeader { + background-color: #F6F6F6; +} + +#netBreakpointBar { + max-width: 38px; +} + +#netHrefCol > .netHeaderCellBox { + border-left: 0px; +} + +.netRow .netRowHeader { + width: 3px; +} + +.netInfoRow .netRowHeader { + display: table-cell; +} + +/************************************************************************************************/ +/* Column visibility */ + +.netTable[hiddenCols~=netHrefCol] TD[id="netHrefCol"], +.netTable[hiddenCols~=netHrefCol] TD.netHrefCol, +.netTable[hiddenCols~=netStatusCol] TD[id="netStatusCol"], +.netTable[hiddenCols~=netStatusCol] TD.netStatusCol, +.netTable[hiddenCols~=netDomainCol] TD[id="netDomainCol"], +.netTable[hiddenCols~=netDomainCol] TD.netDomainCol, +.netTable[hiddenCols~=netSizeCol] TD[id="netSizeCol"], +.netTable[hiddenCols~=netSizeCol] TD.netSizeCol, +.netTable[hiddenCols~=netTimeCol] TD[id="netTimeCol"], +.netTable[hiddenCols~=netTimeCol] TD.netTimeCol { + display: none; +} + +/************************************************************************************************/ + +.netRow { + background: LightYellow; +} + +.netRow.loaded { + background: #FFFFFF; +} + +.netRow.loaded:hover { + background: #EFEFEF; +} + +.netCol { + padding: 0; + vertical-align: top; + border-bottom: 1px solid #EFEFEF; + white-space: nowrap; + height: 17px; +} + +.netLabel { + width: 100%; +} + +.netStatusCol { + padding-left: 10px; + color: rgb(128, 128, 128); +} + +.responseError > .netStatusCol { + color: red; +} + +.netDomainCol { + padding-left: 5px; +} + +.netSizeCol { + text-align: right; + padding-right: 10px; +} + +.netHrefLabel { + -moz-box-sizing: padding-box; + overflow: hidden; + z-index: 10; + position: absolute; + padding-left: 18px; + padding-top: 1px; + max-width: 15%; + font-weight: bold; +} + +.netFullHrefLabel { + display: none; + -moz-user-select: none; + padding-right: 10px; + padding-bottom: 3px; + max-width: 100%; + background: #FFFFFF; + z-index: 200; +} + +.netHrefCol:hover > .netFullHrefLabel { + display: block; +} + +.netRow.loaded:hover .netCol > .netFullHrefLabel { + background-color: #EFEFEF; +} + +.useA11y .a11yShowFullLabel { + display: block; + background-image: none !important; + border: 1px solid #CBE087; + background-color: LightYellow; + font-family: Monaco, monospace; + color: #000000; + font-size: 10px; + z-index: 2147483647; +} + +.netSizeLabel { + padding-left: 6px; +} + +.netStatusLabel, +.netDomainLabel, +.netSizeLabel, +.netBar { + padding: 1px 0 2px 0 !important; +} + +.responseError { + color: red; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.hasHeaders .netHrefLabel:hover { + cursor: pointer; + color: blue; + text-decoration: underline; +} + +/************************************************************************************************/ + +.netLoadingIcon { + position: absolute; + border: 0; + margin-left: 14px; + width: 16px; + height: 16px; + background: transparent no-repeat 0 0; + background-image: url(chrome://firebug/skin/loading_16.gif); + display:inline-block; +} + +.loaded .netLoadingIcon { + display: none; +} + +/************************************************************************************************/ + +.netBar, .netSummaryBar { + position: relative; + border-right: 50px solid transparent; +} + +.netResolvingBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #FFFFFF url(chrome://firebug/skin/netBarResolving.gif) repeat-x; + z-index:60; +} + +.netConnectingBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #FFFFFF url(chrome://firebug/skin/netBarConnecting.gif) repeat-x; + z-index:50; +} + +.netBlockingBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #FFFFFF url(chrome://firebug/skin/netBarWaiting.gif) repeat-x; + z-index:40; +} + +.netSendingBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #FFFFFF url(chrome://firebug/skin/netBarSending.gif) repeat-x; + z-index:30; +} + +.netWaitingBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #FFFFFF url(chrome://firebug/skin/netBarResponded.gif) repeat-x; + z-index:20; + min-width: 1px; +} + +.netReceivingBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #38D63B url(chrome://firebug/skin/netBarLoading.gif) repeat-x; + z-index:10; +} + +.netWindowLoadBar, +.netContentLoadBar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 1px; + background-color: red; + z-index: 70; + opacity: 0.5; + display: none; + margin-bottom:-1px; +} + +.netContentLoadBar { + background-color: Blue; +} + +.netTimeLabel { + -moz-box-sizing: padding-box; + position: absolute; + top: 1px; + left: 100%; + padding-left: 6px; + color: #444444; + min-width: 16px; +} + +/* + * Timing info tip is reusing net timeline styles to display the same + * colors for individual request phases. Notice that the info tip must + * respect also loaded and fromCache styles that also modify the + * actual color. These are used both on the same element in case + * of the tooltip. + */ +.loaded .netReceivingBar, +.loaded.netReceivingBar { + background: #B6B6B6 url(chrome://firebug/skin/netBarLoaded.gif) repeat-x; + border-color: #B6B6B6; +} + +.fromCache .netReceivingBar, +.fromCache.netReceivingBar { + background: #D6D6D6 url(chrome://firebug/skin/netBarCached.gif) repeat-x; + border-color: #D6D6D6; +} + +.netSummaryRow .netTimeLabel, +.loaded .netTimeLabel { + background: transparent; +} + +/************************************************************************************************/ +/* Time Info tip */ + +.timeInfoTip { + width: 150px; + height: 40px +} + +.timeInfoTipBar, +.timeInfoTipEventBar { + position: relative; + display: block; + margin: 0; + opacity: 1; + height: 15px; + width: 4px; +} + +.timeInfoTipEventBar { + width: 1px !important; +} + +.timeInfoTipCell.startTime { + padding-right: 8px; +} + +.timeInfoTipCell.elapsedTime { + text-align: right; + padding-right: 8px; +} + +/************************************************************************************************/ +/* Size Info tip */ + +.sizeInfoLabelCol { + font-weight: bold; + padding-right: 10px; + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; +} + +.sizeInfoSizeCol { + font-weight: bold; +} + +.sizeInfoDetailCol { + color: gray; + text-align: right; +} + +.sizeInfoDescCol { + font-style: italic; +} + +/************************************************************************************************/ +/* Summary */ + +.netSummaryRow .netReceivingBar { + background: #BBBBBB; + border: none; +} + +.netSummaryLabel { + color: #222222; +} + +.netSummaryRow { + background: #BBBBBB !important; + font-weight: bold; +} + +.netSummaryRow .netBar { + border-right-color: #BBBBBB; +} + +.netSummaryRow > .netCol { + border-top: 1px solid #999999; + border-bottom: 2px solid; + -moz-border-bottom-colors: #EFEFEF #999999; + padding-top: 1px; + padding-bottom: 2px; +} + +.netSummaryRow > .netHrefCol:hover { + background: transparent !important; +} + +.netCountLabel { + padding-left: 18px; +} + +.netTotalSizeCol { + text-align: right; + padding-right: 10px; +} + +.netTotalTimeCol { + text-align: right; +} + +.netCacheSizeLabel { + position: absolute; + z-index: 1000; + left: 0; + top: 0; +} + +/************************************************************************************************/ + +.netLimitRow { + background: rgb(255, 255, 225) !important; + font-weight:normal; + color: black; + font-weight:normal; +} + +.netLimitLabel { + padding-left: 18px; +} + +.netLimitRow > .netCol { + border-bottom: 2px solid; + -moz-border-bottom-colors: #EFEFEF #999999; + vertical-align: middle !important; + padding-top: 2px; + padding-bottom: 2px; +} + +.netLimitButton { + font-size: 11px; + padding-top: 1px; + padding-bottom: 1px; +} + +/************************************************************************************************/ + +.netInfoCol { + border-top: 1px solid #EEEEEE; + background: url(chrome://firebug/skin/group.gif) repeat-x #FFFFFF; +} + +.netInfoBody { + margin: 10px 0 4px 10px; +} + +.netInfoTabs { + position: relative; + padding-left: 17px; +} + +.netInfoTab { + position: relative; + top: -3px; + margin-top: 10px; + padding: 4px 6px; + border: 1px solid transparent; + border-bottom: none; + _border: none; + font-weight: bold; + color: #565656; + cursor: pointer; +} + +/*.netInfoTab:hover { + cursor: pointer; +}*/ + +/* replaced by .netInfoTabSelected for IE6 support +.netInfoTab[selected="true"] { + cursor: default !important; + border: 1px solid #D7D7D7 !important; + border-bottom: none !important; + -moz-border-radius: 4px 4px 0 0; + background-color: #FFFFFF; +} +/**/ +.netInfoTabSelected { + cursor: default !important; + border: 1px solid #D7D7D7 !important; + border-bottom: none !important; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + background-color: #FFFFFF; +} + +.logRow-netInfo.error .netInfoTitle { + color: red; +} + +.logRow-netInfo.loading .netInfoResponseText { + font-style: italic; + color: #888888; +} + +.loading .netInfoResponseHeadersTitle { + display: none; +} + +.netInfoResponseSizeLimit { + font-family: Lucida Grande, Tahoma, sans-serif; + padding-top: 10px; + font-size: 11px; +} + +.netInfoText { + display: none; + margin: 0; + border: 1px solid #D7D7D7; + border-right: none; + padding: 8px; + background-color: #FFFFFF; + font-family: Monaco, monospace; + white-space: pre-wrap; + /*overflow-x: auto; HTML is damaged in case of big (2-3MB) responses */ +} + +/* replaced by .netInfoTextSelected for IE6 support +.netInfoText[selected="true"] { + display: block; +} +/**/ +.netInfoTextSelected { + display: block; +} + +.netInfoParamName { + padding-right: 10px; + font-family: Lucida Grande, Tahoma, sans-serif; + font-weight: bold; + vertical-align: top; + text-align: right; + white-space: nowrap; +} + +.netInfoPostText .netInfoParamName { + width: 1px; /* Google Chrome need this otherwise the first column of + the post variables table will be larger than expected */ +} + +.netInfoParamValue { + width: 100%; +} + +.netInfoHeadersText, +.netInfoPostText, +.netInfoPutText { + padding-top: 0; +} + +.netInfoHeadersGroup, +.netInfoPostParams, +.netInfoPostSource { + margin-bottom: 4px; + border-bottom: 1px solid #D7D7D7; + padding-top: 8px; + padding-bottom: 2px; + font-family: Lucida Grande, Tahoma, sans-serif; + font-weight: bold; + color: #565656; +} + +.netInfoPostParamsTable, +.netInfoPostPartsTable, +.netInfoPostJSONTable, +.netInfoPostXMLTable, +.netInfoPostSourceTable { + margin-bottom: 10px; + width: 100%; +} + +.netInfoPostContentType { + color: #bdbdbd; + padding-left: 50px; + font-weight: normal; +} + +.netInfoHtmlPreview { + border: 0; + width: 100%; + height:100%; +} + +/************************************************************************************************/ +/* Request & Response Headers */ + +.netHeadersViewSource { + color: #bdbdbd; + margin-left: 200px; + font-weight: normal; +} + +.netHeadersViewSource:hover { + color: blue; + cursor: pointer; +} + +/************************************************************************************************/ + +.netActivationRow, +.netPageSeparatorRow { + background: rgb(229, 229, 229) !important; + font-weight: normal; + color: black; +} + +.netActivationLabel { + background: url(chrome://firebug/skin/infoIcon.png) no-repeat 3px 2px; + padding-left: 22px; +} + +/************************************************************************************************/ + +.netPageSeparatorRow { + height: 5px !important; +} + +.netPageSeparatorLabel { + padding-left: 22px; + height: 5px !important; +} + +.netPageRow { + background-color: rgb(255, 255, 255); +} + +.netPageRow:hover { + background: #EFEFEF; +} + +.netPageLabel { + padding: 1px 0 2px 18px !important; + font-weight: bold; +} + +/************************************************************************************************/ + +.netActivationRow > .netCol { + border-bottom: 2px solid; + -moz-border-bottom-colors: #EFEFEF #999999; + padding-top: 2px; + padding-bottom: 3px; +} +/* +.useA11y .panelNode-net .a11yFocus:focus, +.useA11y .panelNode-net .focusRow:focus { + outline-offset: -2px; + background-color: #FFFFD6 !important; +} + +.useA11y .panelNode-net .netHeaderCell:focus, +.useA11y .panelNode-net :focus .netHeaderCell, +.useA11y .panelNode-net :focus .netReceivingBar, +.useA11y .netSummaryRow :focus .netBar, +.useA11y .netSummaryRow:focus .netBar { + background-color: #FFFFD6; + background-image: none; + border-color: #FFFFD6; +} +/**/ + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* Windows */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ + + +/************************************************************************************************/ +/* Twisties */ + +.twisty, +.logRow-errorMessage > .hasTwisty > .errorTitle, +.logRow-log > .objectBox-array.hasTwisty, +.logRow-spy .spyHead .spyTitle, +.logGroup > .logRow, +.memberRow.hasChildren > .memberLabelCell > .memberLabel, +.hasHeaders .netHrefLabel, +.netPageRow > .netCol > .netPageTitle { + background-image: url(tree_open.gif); + background-repeat: no-repeat; + background-position: 2px 2px; + min-height: 12px; +} + +.logRow-errorMessage > .hasTwisty.opened > .errorTitle, +.logRow-log > .objectBox-array.hasTwisty.opened, +.logRow-spy.opened .spyHead .spyTitle, +.logGroup.opened > .logRow, +.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel, +.nodeBox.highlightOpen > .nodeLabel > .twisty, +.nodeBox.open > .nodeLabel > .twisty, +.netRow.opened > .netCol > .netHrefLabel, +.netPageRow.opened > .netCol > .netPageTitle { + background-image: url(tree_close.gif); +} + +.twisty { + background-position: 4px 4px; +} + + + +/************************************************************************************************/ +/* Twisties IE6 */ + +/* IE6 has problems with > operator, and multiple classes */ + +* html .logRow-spy .spyHead .spyTitle, +* html .logGroup .logGroupLabel, +* html .hasChildren .memberLabelCell .memberLabel, +* html .hasHeaders .netHrefLabel { + background-image: url(tree_open.gif); + background-repeat: no-repeat; + background-position: 2px 2px; +} + +* html .opened .spyHead .spyTitle, +* html .opened .logGroupLabel, +* html .opened .memberLabelCell .memberLabel { + background-image: url(tree_close.gif); + background-repeat: no-repeat; + background-position: 2px 2px; +} + + + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* Console */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ + + +/* See license.txt for terms of usage */ + +.panelNode-console { + overflow-x: hidden; +} + +.objectLink { + text-decoration: none; +} + +.objectLink:hover { + cursor: pointer; + text-decoration: underline; +} + +.logRow { + position: relative; + margin: 0; + border-bottom: 1px solid #D7D7D7; + padding: 2px 4px 1px 6px; + background-color: #FFFFFF; + overflow: hidden !important; /* IE need this to avoid disappearing bug with collapsed logs */ +} + +.useA11y .logRow:focus { + border-bottom: 1px solid #000000 !important; + outline: none !important; + background-color: #FFFFAD !important; +} + +.useA11y .logRow:focus a.objectLink-sourceLink { + background-color: #FFFFAD; +} + +.useA11y .a11yFocus:focus, .useA11y .objectBox:focus { + outline: 2px solid #FF9933; + background-color: #FFFFAD; +} + +.useA11y .objectBox-null:focus, .useA11y .objectBox-undefined:focus{ + background-color: #888888 !important; +} + +.useA11y .logGroup.opened > .logRow { + border-bottom: 1px solid #ffffff; +} + +.logGroup { + background: url(group.gif) repeat-x #FFFFFF; + padding: 0 !important; + border: none !important; +} + +.logGroupBody { + display: none; + margin-left: 16px; + border-left: 1px solid #D7D7D7; + border-top: 1px solid #D7D7D7; + background: #FFFFFF; +} + +.logGroup > .logRow { + background-color: transparent !important; + font-weight: bold; +} + +.logGroup.opened > .logRow { + border-bottom: none; +} + +.logGroup.opened > .logGroupBody { + display: block; +} + +/*****************************************************************************************/ + +.logRow-command > .objectBox-text { + font-family: Monaco, monospace; + color: #0000FF; + white-space: pre-wrap; +} + +.logRow-info, +.logRow-warn, +.logRow-error, +.logRow-assert, +.logRow-warningMessage, +.logRow-errorMessage { + padding-left: 22px; + background-repeat: no-repeat; + background-position: 4px 2px; +} + +.logRow-assert, +.logRow-warningMessage, +.logRow-errorMessage { + padding-top: 0; + padding-bottom: 0; +} + +.logRow-info, +.logRow-info .objectLink-sourceLink { + background-color: #FFFFFF; +} + +.logRow-warn, +.logRow-warningMessage, +.logRow-warn .objectLink-sourceLink, +.logRow-warningMessage .objectLink-sourceLink { + background-color: cyan; +} + +.logRow-error, +.logRow-assert, +.logRow-errorMessage, +.logRow-error .objectLink-sourceLink, +.logRow-errorMessage .objectLink-sourceLink { + background-color: LightYellow; +} + +.logRow-error, +.logRow-assert, +.logRow-errorMessage { + color: #FF0000; +} + +.logRow-info { + /*background-image: url(chrome://firebug/skin/infoIcon.png);*/ +} + +.logRow-warn, +.logRow-warningMessage { + /*background-image: url(chrome://firebug/skin/warningIcon.png);*/ +} + +.logRow-error, +.logRow-assert, +.logRow-errorMessage { + /*background-image: url(chrome://firebug/skin/errorIcon.png);*/ +} + +/*****************************************************************************************/ + +.objectBox-string, +.objectBox-text, +.objectBox-number, +.objectLink-element, +.objectLink-textNode, +.objectLink-function, +.objectBox-stackTrace, +.objectLink-profile { + font-family: Monaco, monospace; +} + +.objectBox-string, +.objectBox-text, +.objectLink-textNode { + white-space: pre-wrap; +} + +.objectBox-number, +.objectLink-styleRule, +.objectLink-element, +.objectLink-textNode { + color: #000088; +} + +.objectBox-string { + color: #FF0000; +} + +.objectLink-function, +.objectBox-stackTrace, +.objectLink-profile { + color: DarkGreen; +} + +.objectBox-null, +.objectBox-undefined { + padding: 0 2px; + border: 1px solid #666666; + background-color: #888888; + color: #FFFFFF; +} + +.objectBox-exception { + padding: 0 2px 0 18px; + /*background: url(chrome://firebug/skin/errorIcon-sm.png) no-repeat 0 0;*/ + color: red; +} + +.objectLink-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + color: #0000FF; +} + +/************************************************************************************************/ + +.errorTitle { + margin-top: 0px; + margin-bottom: 1px; + padding-top: 2px; + padding-bottom: 2px; +} + +.errorTrace { + margin-left: 17px; +} + +.errorSourceBox { + margin: 2px 0; +} + +.errorSource-none { + display: none; +} + +.errorSource-syntax > .errorBreak { + visibility: hidden; +} + +.errorSource { + cursor: pointer; + font-family: Monaco, monospace; + color: DarkGreen; +} + +.errorSource:hover { + text-decoration: underline; +} + +.errorBreak { + cursor: pointer; + display: none; + margin: 0 6px 0 0; + width: 13px; + height: 14px; + vertical-align: bottom; + /*background: url(chrome://firebug/skin/breakpoint.png) no-repeat;*/ + opacity: 0.1; +} + +.hasBreakSwitch .errorBreak { + display: inline; +} + +.breakForError .errorBreak { + opacity: 1; +} + +.assertDescription { + margin: 0; +} + +/************************************************************************************************/ + +.logRow-profile > .logRow > .objectBox-text { + font-family: Lucida Grande, Tahoma, sans-serif; + color: #000000; +} + +.logRow-profile > .logRow > .objectBox-text:last-child { + color: #555555; + font-style: italic; +} + +.logRow-profile.opened > .logRow { + padding-bottom: 4px; +} + +.profilerRunning > .logRow { + /*background: transparent url(chrome://firebug/skin/loading_16.gif) no-repeat 2px 0 !important;*/ + padding-left: 22px !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.profileSizer { + width:100%; + overflow-x:auto; + overflow-y: scroll; +} + +.profileTable { + border-bottom: 1px solid #D7D7D7; + padding: 0 0 4px 0; +} + +.profileTable tr[odd="1"] { + background-color: #F5F5F5; + vertical-align:middle; +} + +.profileTable a { + vertical-align:middle; +} + +.profileTable td { + padding: 1px 4px 0 4px; +} + +.headerCell { + cursor: pointer; + -moz-user-select: none; + border-bottom: 1px solid #9C9C9C; + padding: 0 !important; + font-weight: bold; + /*background: #BBBBBB url(chrome://firebug/skin/tableHeader.gif) repeat-x;*/ +} + +.headerCellBox { + padding: 2px 4px; + border-left: 1px solid #D9D9D9; + border-right: 1px solid #9C9C9C; +} + +.headerCell:hover:active { + /*background: #959595 url(chrome://firebug/skin/tableHeaderActive.gif) repeat-x;*/ +} + +.headerSorted { + /*background: #7D93B2 url(chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;*/ +} + +.headerSorted > .headerCellBox { + border-right-color: #6B7C93; + /*background: url(chrome://firebug/skin/arrowDown.png) no-repeat right;*/ +} + +.headerSorted.sortedAscending > .headerCellBox { + /*background-image: url(chrome://firebug/skin/arrowUp.png);*/ +} + +.headerSorted:hover:active { + /*background: #536B90 url(chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;*/ +} + +.linkCell { + text-align: right; +} + +.linkCell > .objectLink-sourceLink { + position: static; +} + +/*****************************************************************************************/ + +.logRow-stackTrace { + padding-top: 0; + background: #f8f8f8; +} + +.logRow-stackTrace > .objectBox-stackFrame { + position: relative; + padding-top: 2px; +} + +/************************************************************************************************/ + +.objectLink-object { + font-family: Lucida Grande, sans-serif; + font-weight: bold; + color: DarkGreen; + white-space: pre-wrap; +} + +/* xxxpedro reps object representation .................................... */ +.objectProp-object { + color: DarkGreen; +} + +.objectProps { + color: #000; + font-weight: normal; +} + +.objectPropName { + /*font-style: italic;*/ + color: #777; +} + +/* +.objectProps .objectProp-string, +.objectProps .objectProp-number, +.objectProps .objectProp-object +{ + font-style: italic; +} +/**/ + +.objectProps .objectProp-string +{ + /*font-family: Monaco, monospace;*/ + color: #f55; +} +.objectProps .objectProp-number +{ + /*font-family: Monaco, monospace;*/ + color: #55a; +} +.objectProps .objectProp-object +{ + /*font-family: Lucida Grande,sans-serif;*/ + color: #585; +} +/* xxxpedro reps object representation .................................... */ + +/************************************************************************************************/ + +.selectorTag, +.selectorId, +.selectorClass { + font-family: Monaco, monospace; + font-weight: normal; +} + +.selectorTag { + color: #0000FF; +} + +.selectorId { + color: DarkBlue; +} + +.selectorClass { + color: red; +} + +.selectorHidden > .selectorTag { + color: #5F82D9; +} + +.selectorHidden > .selectorId { + color: #888888; +} + +.selectorHidden > .selectorClass { + color: #D86060; +} + +.selectorValue { + font-family: Lucida Grande, sans-serif; + font-style: italic; + color: #555555; +} + +/*****************************************************************************************/ + +.panelNode.searching .logRow { + display: none; +} + +.logRow.matched { + display: block !important; +} + +.logRow.matching { + position: absolute; + left: -1000px; + top: -1000px; + max-width: 0; + max-height: 0; + overflow: hidden; +} + +/*****************************************************************************************/ + +.objectLeftBrace, +.objectRightBrace, +.objectEqual, +.objectComma, +.arrayLeftBracket, +.arrayRightBracket, +.arrayComma { + font-family: Monaco, monospace; +} + +.objectLeftBrace, +.objectRightBrace, +.arrayLeftBracket, +.arrayRightBracket { + font-weight: bold; +} + +.objectLeftBrace, +.arrayLeftBracket { + margin-right: 4px; +} + +.objectRightBrace, +.arrayRightBracket { + margin-left: 4px; +} + +/*****************************************************************************************/ + +.logRow-dir { + padding: 0; +} + +/************************************************************************************************/ + +/* +.logRow-errorMessage > .hasTwisty > .errorTitle, +.logRow-spy .spyHead .spyTitle, +.logGroup > .logRow +*/ +.logRow-errorMessage .hasTwisty .errorTitle, +.logRow-spy .spyHead .spyTitle, +.logGroup .logRow { + cursor: pointer; + padding-left: 18px; + background-repeat: no-repeat; + background-position: 3px 3px; +} + +.logRow-errorMessage > .hasTwisty > .errorTitle { + background-position: 2px 3px; +} + +.logRow-errorMessage > .hasTwisty > .errorTitle:hover, +.logRow-spy .spyHead .spyTitle:hover, +.logGroup > .logRow:hover { + text-decoration: underline; +} + +/*****************************************************************************************/ + +.logRow-spy { + padding: 0 !important; +} + +.logRow-spy, +.logRow-spy .objectLink-sourceLink { + background: url(group.gif) repeat-x #FFFFFF; + padding-right: 4px; + right: 0; +} + +.logRow-spy.opened { + padding-bottom: 4px; + border-bottom: none; +} + +.spyTitle { + color: #000000; + font-weight: bold; + -moz-box-sizing: padding-box; + overflow: hidden; + z-index: 100; + padding-left: 18px; +} + +.spyCol { + padding: 0; + white-space: nowrap; + height: 16px; +} + +.spyTitleCol:hover > .objectLink-sourceLink, +.spyTitleCol:hover > .spyTime, +.spyTitleCol:hover > .spyStatus, +.spyTitleCol:hover > .spyTitle { + display: none; +} + +.spyFullTitle { + display: none; + -moz-user-select: none; + max-width: 100%; + background-color: Transparent; +} + +.spyTitleCol:hover > .spyFullTitle { + display: block; +} + +.spyStatus { + padding-left: 10px; + color: rgb(128, 128, 128); +} + +.spyTime { + margin-left:4px; + margin-right:4px; + color: rgb(128, 128, 128); +} + +.spyIcon { + margin-right: 4px; + margin-left: 4px; + width: 16px; + height: 16px; + vertical-align: middle; + background: transparent no-repeat 0 0; + display: none; +} + +.loading .spyHead .spyRow .spyIcon { + background-image: url(loading_16.gif); + display: block; +} + +.logRow-spy.loaded:not(.error) .spyHead .spyRow .spyIcon { + width: 0; + margin: 0; +} + +.logRow-spy.error .spyHead .spyRow .spyIcon { + background-image: url(errorIcon-sm.png); + display: block; + background-position: 2px 2px; +} + +.logRow-spy .spyHead .netInfoBody { + display: none; +} + +.logRow-spy.opened .spyHead .netInfoBody { + margin-top: 10px; + display: block; +} + +.logRow-spy.error .spyTitle, +.logRow-spy.error .spyStatus, +.logRow-spy.error .spyTime { + color: red; +} + +.logRow-spy.loading .spyResponseText { + font-style: italic; + color: #888888; +} + +/************************************************************************************************/ + +.caption { + font-family: Lucida Grande, Tahoma, sans-serif; + font-weight: bold; + color: #444444; +} + +.warning { + padding: 10px; + font-family: Lucida Grande, Tahoma, sans-serif; + font-weight: bold; + color: #888888; +} + + + + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* DOM */ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ + + +/* See license.txt for terms of usage */ + +.panelNode-dom { + overflow-x: hidden !important; +} + +.domTable { + font-size: 1em; + width: 100%; + table-layout: fixed; + background: #fff; +} + +.domTableIE { + width: auto; +} + +.memberLabelCell { + padding: 2px 0 2px 0; + vertical-align: top; +} + +.memberValueCell { + padding: 1px 0 1px 5px; + display: block; + overflow: hidden; +} + +.memberLabel { + display: block; + cursor: default; + -moz-user-select: none; + overflow: hidden; + /*position: absolute;*/ + padding-left: 18px; + /*max-width: 30%;*/ + /*white-space: nowrap;*/ + background-color: #FFFFFF; + text-decoration: none; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.memberRow.hasChildren .memberLabelCell .memberLabel:hover { + cursor: pointer; + color: blue; + text-decoration: underline; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.userLabel { + color: #000000; + font-weight: bold; +} + +.userClassLabel { + color: #E90000; + font-weight: bold; +} + +.userFunctionLabel { + color: #025E2A; + font-weight: bold; +} + +.domLabel { + color: #000000; +} + +.domFunctionLabel { + color: #025E2A; +} + +.ordinalLabel { + color: SlateBlue; + font-weight: bold; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +.scopesRow { + padding: 2px 18px; + background-color: LightYellow; + border-bottom: 5px solid #BEBEBE; + color: #666666; +} +.scopesLabel { + background-color: LightYellow; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.watchEditCell { + padding: 2px 18px; + background-color: LightYellow; + border-bottom: 1px solid #BEBEBE; + color: #666666; +} + +.editor-watchNewRow, +.editor-memberRow { + font-family: Monaco, monospace !important; +} + +.editor-memberRow { + padding: 1px 0 !important; +} + +.editor-watchRow { + padding-bottom: 0 !important; +} + +.watchRow > .memberLabelCell { + font-family: Monaco, monospace; + padding-top: 1px; + padding-bottom: 1px; +} + +.watchRow > .memberLabelCell > .memberLabel { + background-color: transparent; +} + +.watchRow > .memberValueCell { + padding-top: 2px; + padding-bottom: 2px; +} + +.watchRow > .memberLabelCell, +.watchRow > .memberValueCell { + background-color: #F5F5F5; + border-bottom: 1px solid #BEBEBE; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.watchToolbox { + z-index: 2147483647; + position: absolute; + right: 0; + padding: 1px 2px; +} + + +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/*************************************************************************************************/ +/* FROM ORIGINAL FIREBUG */ + + + + +/************************************************************************************************ + CSS Not organized +*************************************************************************************************/ +#fbConsole { + overflow-x: hidden !important; +} + +#fbCSS { + font: 1em Monaco, monospace; + padding: 0 7px; +} + +#fbstylesheetButtons select, #fbScriptButtons select { + font: 11px Lucida Grande, Tahoma, sans-serif; + margin-top: 1px; + padding-left: 3px; + background: #fafafa; + border: 1px inset #fff; + width: 220px; + outline: none; +} + +.Selector { margin-top:10px } +.CSSItem {margin-left: 4% } +.CSSText { padding-left:20px; } +.CSSProperty { color:#005500; } +.CSSValue { padding-left:5px; color:#000088; } + + +/************************************************************************************************ + Not organized +*************************************************************************************************/ + +#fbHTMLStatusBar { + display: inline; +} + +.fbToolbarButtons { + display: none; +} + +.fbStatusSeparator{ + display: block; + float: left; + padding-top: 4px; +} + +#fbStatusBarBox { + display: none; +} + +#fbToolbarContent { + display: block; + position: absolute; + _position: absolute; + top: 0; + padding-top: 4px; + height: 23px; + clip: rect(0, 2048px, 27px, 0); +} + +.fbTabMenuTarget { + display: none !important; + float: left; + width: 10px; + height: 10px; + margin-top: 6px; + background: url(tabMenuTarget.png); +} + +.fbTabMenuTarget:hover { + background: url(tabMenuTargetHover.png); +} + +.fbShadow { + float: left; + background: url(shadowAlpha.png) no-repeat bottom right !important; + background: url(shadow2.gif) no-repeat bottom right; + margin: 10px 0 0 10px !important; + margin: 10px 0 0 5px; +} + +.fbShadowContent { + display: block; + position: relative; + background-color: #fff; + border: 1px solid #a9a9a9; + top: -6px; + left: -6px; +} + +.fbMenu { + display: none; + position: absolute; + font-size: 11px; + line-height: 13px; + z-index: 2147483647; +} + +.fbMenuContent { + padding: 2px; +} + +.fbMenuSeparator { + display: block; + position: relative; + padding: 1px 18px 0; + text-decoration: none; + color: #000; + cursor: default; + background: #ACA899; + margin: 4px 0; +} + +.fbMenuOption +{ + display: block; + position: relative; + padding: 2px 18px; + text-decoration: none; + color: #000; + cursor: default; +} + +.fbMenuOption:hover +{ + color: #fff; + background: #316AC5; +} + +.fbMenuGroup { + background: transparent url(tabMenuPin.png) no-repeat right 0; +} + +.fbMenuGroup:hover { + background: #316AC5 url(tabMenuPin.png) no-repeat right -17px; +} + +.fbMenuGroupSelected { + color: #fff; + background: #316AC5 url(tabMenuPin.png) no-repeat right -17px; +} + +.fbMenuChecked { + background: transparent url(tabMenuCheckbox.png) no-repeat 4px 0; +} + +.fbMenuChecked:hover { + background: #316AC5 url(tabMenuCheckbox.png) no-repeat 4px -17px; +} + +.fbMenuRadioSelected { + background: transparent url(tabMenuRadio.png) no-repeat 4px 0; +} + +.fbMenuRadioSelected:hover { + background: #316AC5 url(tabMenuRadio.png) no-repeat 4px -17px; +} + +.fbMenuShortcut { + padding-right: 85px; +} + +.fbMenuShortcutKey { + position: absolute; + right: 0; + top: 2px; + width: 77px; +} + +#fbFirebugMenu { + top: 22px; + left: 0; +} + +.fbMenuDisabled { + color: #ACA899 !important; +} + +#fbFirebugSettingsMenu { + left: 245px; + top: 99px; +} + +#fbConsoleMenu { + top: 42px; + left: 48px; +} + +.fbIconButton { + display: block; +} + +.fbIconButton { + display: block; +} + +.fbIconButton { + display: block; + float: left; + height: 20px; + width: 20px; + color: #000; + margin-right: 2px; + text-decoration: none; + cursor: default; +} + +.fbIconButton:hover { + position: relative; + top: -1px; + left: -1px; + margin-right: 0; + _margin-right: 1px; + color: #333; + border: 1px solid #fff; + border-bottom: 1px solid #bbb; + border-right: 1px solid #bbb; +} + +.fbIconPressed { + position: relative; + margin-right: 0; + _margin-right: 1px; + top: 0 !important; + left: 0 !important; + height: 19px; + color: #333 !important; + border: 1px solid #bbb !important; + border-bottom: 1px solid #cfcfcf !important; + border-right: 1px solid #ddd !important; +} + + + +/************************************************************************************************ + Error Popup +*************************************************************************************************/ +#fbErrorPopup { + position: absolute; + right: 0; + bottom: 0; + height: 19px; + width: 75px; + background: url(sprite.png) #f1f2ee 0 0; + z-index: 999; +} + +#fbErrorPopupContent { + position: absolute; + right: 0; + top: 1px; + height: 18px; + width: 75px; + _width: 74px; + border-left: 1px solid #aca899; +} + +#fbErrorIndicator { + position: absolute; + top: 2px; + right: 5px; +} + + + + + + + + + + +.fbBtnInspectActive { + background: #aaa; + color: #fff !important; +} + +/************************************************************************************************ + General +*************************************************************************************************/ +.fbBody { + margin: 0; + padding: 0; + overflow: hidden; + + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + background: #fff; +} + +.clear { + clear: both; +} + +/************************************************************************************************ + Mini Chrome +*************************************************************************************************/ +#fbMiniChrome { + display: none; + right: 0; + height: 27px; + background: url(sprite.png) #f1f2ee 0 0; + margin-left: 1px; +} + +#fbMiniContent { + display: block; + position: relative; + left: -1px; + right: 0; + top: 1px; + height: 25px; + border-left: 1px solid #aca899; +} + +#fbToolbarSearch { + float: right; + border: 1px solid #ccc; + margin: 0 5px 0 0; + background: #fff url(search.png) no-repeat 4px 2px !important; + background: #fff url(search.gif) no-repeat 4px 2px; + padding-left: 20px; + font-size: 11px; +} + +#fbToolbarErrors { + float: right; + margin: 1px 4px 0 0; + font-size: 11px; +} + +#fbLeftToolbarErrors { + float: left; + margin: 7px 0px 0 5px; + font-size: 11px; +} + +.fbErrors { + padding-left: 20px; + height: 14px; + background: url(errorIcon.png) no-repeat !important; + background: url(errorIcon.gif) no-repeat; + color: #f00; + font-weight: bold; +} + +#fbMiniErrors { + display: inline; + display: none; + float: right; + margin: 5px 2px 0 5px; +} + +#fbMiniIcon { + float: right; + margin: 3px 4px 0; + height: 20px; + width: 20px; + float: right; + background: url(sprite.png) 0 -135px; + cursor: pointer; +} + + +/************************************************************************************************ + Master Layout +*************************************************************************************************/ +#fbChrome { + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + position: absolute; + _position: static; + top: 0; + left: 0; + height: 100%; + width: 100%; + border-collapse: collapse; + border-spacing: 0; + background: #fff; + overflow: hidden; +} + +#fbChrome > tbody > tr > td { + padding: 0; +} + +#fbTop { + height: 49px; +} + +#fbToolbar { + background: url(sprite.png) #f1f2ee 0 0; + height: 27px; + font-size: 11px; + line-height: 13px; +} + +#fbPanelBarBox { + background: url(sprite.png) #dbd9c9 0 -27px; + height: 22px; +} + +#fbContent { + height: 100%; + vertical-align: top; +} + +#fbBottom { + height: 18px; + background: #fff; +} + +/************************************************************************************************ + Sub-Layout +*************************************************************************************************/ + +/* fbToolbar +*************************************************************************************************/ +#fbToolbarIcon { + float: left; + padding: 0 5px 0; +} + +#fbToolbarIcon a { + background: url(sprite.png) 0 -135px; +} + +#fbToolbarButtons { + padding: 0 2px 0 5px; +} + +#fbToolbarButtons { + padding: 0 2px 0 5px; +} +/* +#fbStatusBarBox a { + text-decoration: none; + display: block; + float: left; + color: #000; + padding: 4px 5px; + margin: 0 0 0 1px; + cursor: default; +} + +#fbStatusBarBox a:hover { + color: #333; + padding: 3px 4px; + border: 1px solid #fff; + border-bottom: 1px solid #bbb; + border-right: 1px solid #bbb; +} +/**/ + +.fbButton { + text-decoration: none; + display: block; + float: left; + color: #000; + padding: 4px 6px 4px 7px; + cursor: default; +} + +.fbButton:hover { + color: #333; + background: #f5f5ef url(buttonBg.png); + padding: 3px 5px 3px 6px; + border: 1px solid #fff; + border-bottom: 1px solid #bbb; + border-right: 1px solid #bbb; +} + +.fbBtnPressed { + background: #e3e3db url(buttonBgHover.png) !important; + padding: 3px 4px 2px 6px !important; + margin: 1px 0 0 1px !important; + border: 1px solid #ACA899 !important; + border-color: #ACA899 #ECEBE3 #ECEBE3 #ACA899 !important; +} + +#fbStatusBarBox { + top: 4px; + cursor: default; +} + +.fbToolbarSeparator { + overflow: hidden; + border: 1px solid; + border-color: transparent #fff transparent #777; + _border-color: #eee #fff #eee #777; + height: 7px; + margin: 6px 3px; + float: left; +} + +.fbBtnSelected { + font-weight: bold; +} + +.fbStatusBar { + color: #aca899; +} + +.fbStatusBar a { + text-decoration: none; + color: black; +} + +.fbStatusBar a:hover { + color: blue; + cursor: pointer; +} + + +#fbWindowButtons { + position: absolute; + white-space: nowrap; + right: 0; + top: 0; + height: 17px; + width: 48px; + padding: 5px; + z-index: 6; + background: url(sprite.png) #f1f2ee 0 0; +} + +/* fbPanelBarBox +*************************************************************************************************/ + +#fbPanelBar1 { + width: 1024px; /* fixed width to avoid tabs breaking line */ + z-index: 8; + left: 0; + white-space: nowrap; + background: url(sprite.png) #dbd9c9 0 -27px; + position: absolute; + left: 4px; +} + +#fbPanelBar2Box { + background: url(sprite.png) #dbd9c9 0 -27px; + position: absolute; + height: 22px; + width: 300px; /* fixed width to avoid tabs breaking line */ + z-index: 9; + right: 0; +} + +#fbPanelBar2 { + position: absolute; + width: 290px; /* fixed width to avoid tabs breaking line */ + height: 22px; + padding-left: 4px; +} + +/* body +*************************************************************************************************/ +.fbPanel { + display: none; +} + +#fbPanelBox1, #fbPanelBox2 { + max-height: inherit; + height: 100%; + font-size: 1em; +} + +#fbPanelBox2 { + background: #fff; +} + +#fbPanelBox2 { + width: 300px; + background: #fff; +} + +#fbPanel2 { + margin-left: 6px; + background: #fff; +} + +#fbLargeCommandLine { + display: none; + position: absolute; + z-index: 9; + top: 27px; + right: 0; + width: 294px; + height: 201px; + border-width: 0; + margin: 0; + padding: 2px 0 0 2px; + resize: none; + outline: none; + font-size: 11px; + overflow: auto; + border-top: 1px solid #B9B7AF; + _right: -1px; + _border-left: 1px solid #fff; +} + +#fbLargeCommandButtons { + display: none; + background: #ECE9D8; + bottom: 0; + right: 0; + width: 294px; + height: 21px; + padding-top: 1px; + position: fixed; + border-top: 1px solid #ACA899; + z-index: 9; +} + +#fbSmallCommandLineIcon { + background: url(down.png) no-repeat; + position: absolute; + right: 2px; + bottom: 3px; + + z-index: 99; +} + +#fbSmallCommandLineIcon:hover { + background: url(downHover.png) no-repeat; +} + +.hide { + overflow: hidden !important; + position: fixed !important; + display: none !important; + visibility: hidden !important; +} + +/* fbBottom +*************************************************************************************************/ + +#fbCommand { + height: 18px; +} + +#fbCommandBox { + position: fixed; + _position: absolute; + width: 100%; + height: 18px; + bottom: 0; + overflow: hidden; + z-index: 9; + background: #fff; + border: 0; + border-top: 1px solid #ccc; +} + +#fbCommandIcon { + position: absolute; + color: #00f; + top: 2px; + left: 6px; + display: inline; + font: 11px Monaco, monospace; + z-index: 10; +} + +#fbCommandLine { + position: absolute; + width: 100%; + top: 0; + left: 0; + border: 0; + margin: 0; + padding: 2px 0 2px 32px; + font: 11px Monaco, monospace; + z-index: 9; + outline: none; +} + +#fbLargeCommandLineIcon { + background: url(up.png) no-repeat; + position: absolute; + right: 1px; + bottom: 1px; + z-index: 10; +} + +#fbLargeCommandLineIcon:hover { + background: url(upHover.png) no-repeat; +} + +div.fbFitHeight { + overflow: auto; + position: relative; +} + + +/************************************************************************************************ + Layout Controls +*************************************************************************************************/ + +/* fbToolbar buttons +*************************************************************************************************/ +.fbSmallButton { + overflow: hidden; + width: 16px; + height: 16px; + display: block; + text-decoration: none; + cursor: default; +} + +#fbWindowButtons .fbSmallButton { + float: right; +} + +#fbWindow_btClose { + background: url(min.png); +} + +#fbWindow_btClose:hover { + background: url(minHover.png); +} + +#fbWindow_btDetach { + background: url(detach.png); +} + +#fbWindow_btDetach:hover { + background: url(detachHover.png); +} + +#fbWindow_btDeactivate { + background: url(off.png); +} + +#fbWindow_btDeactivate:hover { + background: url(offHover.png); +} + + +/* fbPanelBarBox tabs +*************************************************************************************************/ +.fbTab { + text-decoration: none; + display: none; + float: left; + width: auto; + float: left; + cursor: default; + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + line-height: 13px; + font-weight: bold; + height: 22px; + color: #565656; +} + +.fbPanelBar span { + /*display: block; TODO: safe to remove this? */ + float: left; +} + +.fbPanelBar .fbTabL,.fbPanelBar .fbTabR { + height: 22px; + width: 8px; +} + +.fbPanelBar .fbTabText { + padding: 4px 1px 0; +} + +a.fbTab:hover { + background: url(sprite.png) 0 -73px; +} + +a.fbTab:hover .fbTabL { + background: url(sprite.png) -16px -96px; +} + +a.fbTab:hover .fbTabR { + background: url(sprite.png) -24px -96px; +} + +.fbSelectedTab { + background: url(sprite.png) #f1f2ee 0 -50px !important; + color: #000; +} + +.fbSelectedTab .fbTabL { + background: url(sprite.png) 0 -96px !important; +} + +.fbSelectedTab .fbTabR { + background: url(sprite.png) -8px -96px !important; +} + +/* splitters +*************************************************************************************************/ +#fbHSplitter { + position: fixed; + _position: absolute; + left: 0; + top: 0; + width: 100%; + height: 5px; + overflow: hidden; + cursor: n-resize !important; + background: url(pixel_transparent.gif); + z-index: 9; +} + +#fbHSplitter.fbOnMovingHSplitter { + height: 100%; + z-index: 100; +} + +.fbVSplitter { + background: #ece9d8; + color: #000; + border: 1px solid #716f64; + border-width: 0 1px; + border-left-color: #aca899; + width: 4px; + cursor: e-resize; + overflow: hidden; + right: 294px; + text-decoration: none; + z-index: 10; + position: absolute; + height: 100%; + top: 27px; +} + +/************************************************************************************************/ +div.lineNo { + font: 1em/1.4545em Monaco, monospace; + position: relative; + float: left; + top: 0; + left: 0; + margin: 0 5px 0 0; + padding: 0 5px 0 10px; + background: #eee; + color: #888; + border-right: 1px solid #ccc; + text-align: right; +} + +.sourceBox { + position: absolute; +} + +.sourceCode { + font: 1em Monaco, monospace; + overflow: hidden; + white-space: pre; + display: inline; +} + +/************************************************************************************************/ +.nodeControl { + margin-top: 3px; + margin-left: -14px; + float: left; + width: 9px; + height: 9px; + overflow: hidden; + cursor: default; + background: url(tree_open.gif); + _float: none; + _display: inline; + _position: absolute; +} + +div.nodeMaximized { + background: url(tree_close.gif); +} + +div.objectBox-element { + padding: 1px 3px; +} +.objectBox-selector{ + cursor: default; +} + +.selectedElement{ + background: highlight; + /* background: url(roundCorner.svg); Opera */ + color: #fff !important; +} +.selectedElement span{ + color: #fff !important; +} + +/* IE6 need this hack */ +* html .selectedElement { + position: relative; +} + +/* Webkit CSS Hack - bug in "highlight" named color */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + .selectedElement{ + background: #316AC5; + color: #fff !important; + } +} + +/************************************************************************************************/ +/************************************************************************************************/ +.logRow * { + font-size: 1em; +} + +/* TODO: remove this? */ +/* TODO: xxxpedro - IE need this in windowless mode (cnn.com) check if the issue is related to +position. if so, override it at chrome.js initialization when creating the div */ +.logRow { + position: relative; + border-bottom: 1px solid #D7D7D7; + padding: 2px 4px 1px 6px; + zbackground-color: #FFFFFF; +} +/**/ + +.logRow-command { + font-family: Monaco, monospace; + color: blue; +} + +.objectBox-string, +.objectBox-text, +.objectBox-number, +.objectBox-function, +.objectLink-element, +.objectLink-textNode, +.objectLink-function, +.objectBox-stackTrace, +.objectLink-profile { + font-family: Monaco, monospace; +} + +.objectBox-null { + padding: 0 2px; + border: 1px solid #666666; + background-color: #888888; + color: #FFFFFF; +} + +.objectBox-string { + color: red; + + /* TODO: xxxpedro make long strings break line */ + /*white-space: pre; */ +} + +.objectBox-number { + color: #000088; +} + +.objectBox-function { + color: DarkGreen; +} + +.objectBox-object { + color: DarkGreen; + font-weight: bold; + font-family: Lucida Grande, sans-serif; +} + +.objectBox-array { + color: #000; +} + +/************************************************************************************************/ +.logRow-info,.logRow-error,.logRow-warn { + background: #fff no-repeat 2px 2px; + padding-left: 20px; + padding-bottom: 3px; +} + +.logRow-info { + background-image: url(infoIcon.png) !important; + background-image: url(infoIcon.gif); +} + +.logRow-warn { + background-color: cyan; + background-image: url(warningIcon.png) !important; + background-image: url(warningIcon.gif); +} + +.logRow-error { + background-color: LightYellow; + background-image: url(errorIcon.png) !important; + background-image: url(errorIcon.gif); + color: #f00; +} + +.errorMessage { + vertical-align: top; + color: #f00; +} + +.objectBox-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + color: #0000FF; +} + +/************************************************************************************************/ +/* +//TODO: remove this when console2 is finished +*/ +/* +.logRow-group { + background: #EEEEEE; + border-bottom: none; +} + +.logGroup { + background: #EEEEEE; +} + +.logGroupBox { + margin-left: 24px; + border-top: 1px solid #D7D7D7; + border-left: 1px solid #D7D7D7; +}/**/ + +/************************************************************************************************/ +.selectorTag,.selectorId,.selectorClass { + font-family: Monaco, monospace; + font-weight: normal; +} + +.selectorTag { + color: #0000FF; +} + +.selectorId { + color: DarkBlue; +} + +.selectorClass { + color: red; +} + +/************************************************************************************************/ +.objectBox-element { + font-family: Monaco, monospace; + color: #000088; +} + +.nodeChildren { + padding-left: 26px; +} + +.nodeTag { + color: blue; + cursor: pointer; +} + +.nodeValue { + color: #FF0000; + font-weight: normal; +} + +.nodeText,.nodeComment { + margin: 0 2px; + vertical-align: top; +} + +.nodeText { + color: #333333; + font-family: Monaco, monospace; +} + +.nodeComment { + color: DarkGreen; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeHidden, .nodeHidden * { + color: #888888; +} + +.nodeHidden .nodeTag { + color: #5F82D9; +} + +.nodeHidden .nodeValue { + color: #D86060; +} + +.selectedElement .nodeHidden, .selectedElement .nodeHidden * { + color: SkyBlue !important; +} + + +/************************************************************************************************/ +.log-object { + /* + _position: relative; + _height: 100%; + /**/ +} + +.property { + position: relative; + clear: both; + height: 15px; +} + +.propertyNameCell { + vertical-align: top; + float: left; + width: 28%; + position: absolute; + left: 0; + z-index: 0; +} + +.propertyValueCell { + float: right; + width: 68%; + background: #fff; + position: absolute; + padding-left: 5px; + display: table-cell; + right: 0; + z-index: 1; + /* + _position: relative; + /**/ +} + +.propertyName { + font-weight: bold; +} + +.FirebugPopup { + height: 100% !important; +} + +.FirebugPopup #fbWindowButtons { + display: none !important; +} + +.FirebugPopup #fbHSplitter { + display: none !important; +} diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html new file mode 100644 index 00000000000..aa078099fb4 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html @@ -0,0 +1,215 @@ + + + + +Firebug Lite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+   +   +   +
+ + +
+
+ + + +   + + + + + + + + + Inspect + + + + + Clear + + + + + + + + + + + + + +
+ +
+ + + + + +
 
+ +
+
+
+
+
+
+ + +
 
+ + +
+ + +
+
+
+ +
+ + + + + +
+ Run + Clear + + +
+ +
+
+
>>>
+ + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.png new file mode 100644 index 0000000000000000000000000000000000000000..e10affebb48dc2aa7a70c575b87b901d91de1aca GIT binary patch literal 1167 zcmV;A1aSL_P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP% z1vMe5=_CX>@2HM@dakSAh-}000B(Nkl7?fcTwQeNIxiXejg>Qttn`vK11&PA8F>~71F-R#aSS$46%VwcNq zmUy$PU6|;Ganl8yG1G0R6SPVzR7DKMg8faQ?fc{G0<0lue3B<8IXQXG=Q-c+c|>7x zSt29=tS;3MP{?Pgb1T>`FX?oKQn^gAkio+eiL`~|I1CI7pt)-RPc-)&28u#yHGWiYuEn5w(aV9rAh}N#A>e4Az|mPJuCLE*$qkR9}Pxv=>pbEkG*ch>B zlsu}sa$qg5*^57#CScCvx%wkob_Qd|9zGrHVLCp^(}fg?M1sA0_aP-HQd&69 zN=nD%_UJ|CZk(pHyhwC1#>mJpotc~XM^8~k#kL$|rNZYYzv9cITbX+t=fJ@O?AqPO zrUnrC#k`cVDk`~LinG7`icxP6@@oWRLxc^5oDx8(t!G2?Ce%&cP~QTYi|FtfT2Fk9 zX<0FScQiax_kFKp@gsT=B+7NX*rdh^nAH*~* zV$s;EMNzMwVt(N%kwPumOpeZgN^6sknfL>0y$v+j0#7o6`QAwcps6Z>fPsp%dET;F zsc`n(1-i{C!ufG7XPU{RQ@q_I+1(N3?xSa9i#A(Z1WeEJq~{}wGf#LpGfQu{olvN8 zt;&B4U#6inN6%7}k)_Qa(O@v+I-R?%>H@)1Loae((hQ9VDu1_Ypm3GtFV$f;8<7|ao_s4v8 z_zPtOvrH`(51aCu^Ze~L}K(;eby zeG*S&7xL&gBrjazct^`i#Z;uU5sm}4jVR^NT_xIEbVjFA+{*|)*`{&O$TBY5aUK5_ zp}Db;EJV&D%LSxkAsq`=bB!o2Z$19DFj{GuknZP;5BdlVoZt#?GNbRrkt;qu_Wl9p zOr7jvc^#ohJxjcphY0Lq8oQWBY94H@j^Bo_Q0Mh<@uJ~-!0+cum_DYZ5wA-1(jY43wJMoAe$u-1C-Fd;ES zgb)|JqErZh%MxMyL5NgUj1m4MrvJ-D=k5uE+2y(~&iUh3oM8GsrsLOSlx0u4o$rRQ F`U22=Vrl>Y literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/html.css b/public/vendor/lodash/vendor/firebug-lite/skin/xp/html.css new file mode 100644 index 00000000000..5b7c5f4baf7 --- /dev/null +++ b/public/vendor/lodash/vendor/firebug-lite/skin/xp/html.css @@ -0,0 +1,272 @@ +/* See license.txt for terms of usage */ + +.panelNode-html { + -moz-box-sizing: padding-box; + padding: 4px 0 0 2px; +} + +.nodeBox { + position: relative; + font-family: Monaco, monospace; + padding-left: 13px; + -moz-user-select: -moz-none; +} +.nodeBox.search-selection { + -moz-user-select: text; +} +.twisty { + position: absolute; + left: 0px; + top: 0px; + width: 14px; + height: 14px; +} + +.nodeChildBox { + margin-left: 12px; + display: none; +} + +.nodeLabel, +.nodeCloseLabel { + margin: -2px 2px 0 2px; + border: 2px solid transparent; + -moz-border-radius: 3px; + padding: 0 2px; + color: #000088; +} + +.nodeCloseLabel { + display: none; +} + +.nodeTag { + cursor: pointer; + color: blue; +} + +.nodeValue { + color: #FF0000; + font-weight: normal; +} + +.nodeText, +.nodeComment { + margin: 0 2px; + vertical-align: top; +} + +.nodeText { + color: #333333; +} + +.nodeWhiteSpace { + border: 1px solid LightGray; + white-space: pre; /* otherwise the border will be collapsed around zero pixels */ + margin-left: 1px; + color: gray; +} + + +.nodeWhiteSpace_Space { + border: 1px solid #ddd; +} + +.nodeTextEntity { + border: 1px solid gray; + white-space: pre; /* otherwise the border will be collapsed around zero pixels */ + margin-left: 1px; +} + +.nodeComment { + color: DarkGreen; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.highlightOpen > .nodeLabel { + background-color: #EEEEEE; +} + +.nodeBox.highlightOpen > .nodeCloseLabel, +.nodeBox.highlightOpen > .nodeChildBox, +.nodeBox.open > .nodeCloseLabel, +.nodeBox.open > .nodeChildBox { + display: block; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.selected > .nodeLabel > .nodeLabelBox, +.nodeBox.selected > .nodeLabel { + border-color: Highlight; + background-color: Highlight; + color: HighlightText !important; +} + +.nodeBox.selected > .nodeLabel > .nodeLabelBox, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText { + color: inherit !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.highlighted > .nodeLabel { + border-color: Highlight !important; + background-color: cyan !important; + color: #000000 !important; +} + +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox, +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue, +.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeText { + color: #000000 !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox, +.nodeBox.nodeHidden .nodeCloseLabel, +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeText, +.nodeBox.nodeHidden .nodeText { + color: #888888; +} + +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.nodeHidden .nodeCloseLabel > .nodeCloseLabelBox > .nodeTag { + color: #5F82D9; +} + +.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue { + color: #D86060; +} + +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox, +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeTag, +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue, +.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeText { + color: SkyBlue !important; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +.nodeBox.mutated > .nodeLabel, +.nodeAttr.mutated, +.nodeValue.mutated, +.nodeText.mutated, +.nodeBox.mutated > .nodeText { + background-color: #EFFF79; + color: #FF0000 !important; +} + +.nodeBox.selected.mutated > .nodeLabel, +.nodeBox.selected.mutated > .nodeLabel > .nodeLabelBox, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr.mutated > .nodeValue, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue.mutated, +.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText.mutated { + background-color: #EFFF79; + border-color: #EFFF79; + color: #FF0000 !important; +} + +/************************************************************************************************/ + +.logRow-dirxml { + padding-left: 0; +} + +.soloElement > .nodeBox { + padding-left: 0; +} + +.useA11y .nodeLabel.focused { + outline: 2px solid #FF9933; + -moz-outline-radius: 3px; + outline-offset: -2px; +} + +.useA11y .nodeLabelBox:focus { + outline: none; +} + +/************************************************************************************************/ + +.breakpointCode .twisty { + display: none; +} + +.breakpointCode .nodeBox.containerNodeBox, +.breakpointCode .nodeLabel { + padding-left: 0px; + margin-left: 0px; + font-family: Monaco, monospace !important; +} + +.breakpointCode .nodeTag, +.breakpointCode .nodeAttr, +.breakpointCode .nodeText, +.breakpointCode .nodeValue, +.breakpointCode .nodeLabel { + color: DarkGreen !important; +} + +.breakpointMutationType { + position: absolute; + top: 4px; + right: 20px; + color: gray; +} + + + + + + +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ +/************************************************************************************************/ + + + +/************************************************************************************************/ +/* Twisties */ + +.twisty, +.logRow-errorMessage > .hasTwisty > .errorTitle, +.logRow-log > .objectBox-array.hasTwisty, +.logRow-spy .spyHead .spyTitle, +.logGroup > .logRow, +.memberRow.hasChildren > .memberLabelCell > .memberLabel, +.hasHeaders .netHrefLabel, +.netPageRow > .netCol > .netPageTitle { + background-image: url(twistyClosed.png); + background-repeat: no-repeat; + background-position: 2px 2px; + min-height: 12px; +} + +.logRow-errorMessage > .hasTwisty.opened > .errorTitle, +.logRow-log > .objectBox-array.hasTwisty.opened, +.logRow-spy.opened .spyHead .spyTitle, +.logGroup.opened > .logRow, +.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel, +.nodeBox.highlightOpen > .nodeLabel > .twisty, +.nodeBox.open > .nodeLabel > .twisty, +.netRow.opened > .netCol > .netHrefLabel, +.netPageRow.opened > .netCol > .netPageTitle { + background-image: url(twistyOpen.png); +} + +.twisty { + background-position: 4px 4px; +} \ No newline at end of file diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif new file mode 100644 index 0000000000000000000000000000000000000000..0618e208c3488f9d332eff840219c4b37806a396 GIT binary patch literal 359 zcmZ?wbhEHbMdn4SjS?t zoyB-Jv&kW5)5FYWr^BHcuFBo-x|KV6=V7X!nNE z?k%I;dq(?@3=Urz9KJI+{9thW#o+W`(Eq=2#($TR|K+p(uR8F5_u2oH1I2%W&PAz- zC8;S2<(VZJ3hti10St;iSs1w(>=|?zfB@uC2e$kJ^8+|EnA%*k!W#MOQK{ljl)GVz=%0?7rtE9wY7Lo;JgN;%aBBhkF zup$d#AvGJB@i#x7d!6%U9&aT1>bvKh+xgD9@4TZarC_79XA_3Y)u z4jj;IA&G`4T$SEtNOwJyD9b09DTwq1MCN*%!X+xO|0N{Rs4^;&b|i*sHEGPq;n|zOEfbH<6(;%L`k^mT)7z~ywlRx3h4?$ zl>}*o?-02Jb-IWCddBfMi57}>wIB`^Hlv*vh?pjx5|0aenzD001sDKWl9*QR=`djc O0000nb99Af5rT)t{mCEg5urg=A(g z{C|6SPb~9Xage|wB`SrZk2FOMYM!buln2sX?5Y+T78iB(Zu9cS7|LZyZ++}u$^oi1 z_j@S}bW9OzU2R+RMy&~OT>X-oZ98$jq#ogNfJ!BM-42wHGZk*6s2KD}U*IA%epmxb zm}|6BK9YoIF;*xSL!+z@<64lB7->LTW2Vi4ostCA(z&2XniwNIv}fFo-`MbG;)u4G z^p@F!)|9HhZprHd_vXjDoxs6WkK-6P0@lfxnGT>*p(QHoUV=u1FAqb@b%*W=a3{`LsH5k^AvQNL>6fPpy#oU(&MuH(*aEX4b35*} zn4n7)`I2U%=+Z=?BVZQ?vjQFW4gD@~XSOO6b{qu81`4&LFuU2(ilxW+1|ZkNMnWe79C$gs zWT?Ele|HR{JGPe)5BTW>0Ey?-Ls6S#GoV0tbt6ku7B&*0 z;i9QM$W1Rj*rRIdceL)rAOSl+sDe3LkB87<%){;ZdHp6|SNlopDXRx< zxBDF9-lTo&v`8$humFygUij@qgT=Qzhj8{ym2-{Xciwqq_Xwk%=O3B-MNAL_6e`3U zyxwmXex4`g0^1RYw~Dth3av3Dl^AAlpO3mG!nLr#&ZZ7c_wUboI+deC+&%TFjK2Lm z!Y&f1h|T_On%RCV&=4bx`!>(YezqGVhl&QpED?N6GV)HmzJ9&rh$x*i?*@o9#6QI< z5ZI_MRX;0+pY8$`j)eF#TlUyG(eE%E7S!rj;mj^M5vhUicPm zVWQ2z+imFyg}SRABmOBY_@osR!>7Ov!ioK`NB6_Rv}7Ud?35ed5Sb@?yND?kv~RCa wqs^a3Sh>&&L4)!LKI?D2&k@))k(LESaga|C278ChSzn3NWVkcuNoY&{0f?~U_5c6? literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/min.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/min.png new file mode 100644 index 0000000000000000000000000000000000000000..1034d66fb4405598401302e3e624b1674d2bf1e4 GIT binary patch literal 552 zcmV+@0@wYCP)vmZ_X00D$aL_t(I zjiuAUOB+EH$MNsZCL1?R8&!4bS6fS<)tz`g=*viD*Jl^CK z`t%GY-@^kYv5587_1khrGe5s5z@kn#$OJ&Z!eFXD1Sm(L2g^?Y zYmr`er0{)dYkvX6!fog80Qn7&_6-2NexGu6D>c-py(Qzi=>Y9E03D+rytQ-Lq?j9f z2uM00HtcO|a&62|cs!S*R1Cm$(*e`E!c#83wM^skESnnwvbgx22+@Yv_J;w1RwKFy zozi=wY0ONJ#dDq19mIX%q}AnE8w#$f!{9Ff q>@2^m0u@I4O!e0v_iIDIzt$ZPov@8OQ*!|T00004nJ za0`Jja>QWe}Xi&D$;i?WOTH_Q7mFfclLx;Tbd^e&ye){i+*pmo2d_v|Tm-%WA| zXmI?Fy){a=&u=f4TRr{YyjOcz1)84U;wyZtH?bkx zx+N`yTY>4zLPu-Cz0D_%tZP1UcQ?mxR;7exlD+Hw9x3Ds8E&w9mVc-F%e}wTn|nOB zsFnO>dbZ5cgE>_2UfT)14Y$J0A1JWga=XiV?aW^J%?{U&Jlo*F)ij&up5HFxF55(& zhFj`OqF8JjSv9WT`ccMbRy@JCSFNt*@K@_Avt;>Z#4=cjXfhpFiC*?;WmcxcgY`cc YmzC#;_!)WW0t1}E)78&qol`;+0Lys1;s5{u literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/off.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/off.png new file mode 100644 index 0000000000000000000000000000000000000000..b70b1d24d881014f43bbc8f6ac6e5d84921c0ece GIT binary patch literal 742 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FZT01FZU(%pXi0007cNklqH)0mG!ByR2T(Q_4JHo86*cHY zh~fooLU0lPU?Oy!fyZLq%=)}Z(j3lpdV?PzJq7_g47OL4;4Cdfcc&3D43;LRvAT0esjMcVWGjFG2rKDI*|i%PS;bLS zOj^x8?AdMrgjL6pn?>%C8nl8ug2ocIJP%

yFlpb(nCwj+ns@?rq|_a5+Sz>mY} zr1X%NW9QG3U*lzXY6iI&#FUa>3Zz8EqS%W(06f3b&D#M#ZMSc7>DpDCfv>#leGEWO zRXKAD^Q^?;4+{R z*+y2$wpbIZSBe$Gz!q6u%R;fVxJXEDRS*mknw|xqwB`U-O+&?E#A9&?hjA3<128}S z8BCLil3M}G82%vuYA?5-7kT*D(ni<03miUKkNb2303$sQNtKG|np%Aw5HY@^{4e<8 zPN(2ZBUQ~!(A>nHO@}mm_der&PbqPwGre}hdcqaPq`BZKOL4H$-NnmK@5!yJW2e_k z)HInN8)KxWhw=V?3Y<=&!bE7Au>sg-5uFp^WuMKtN`AVIJ~PC`^=AyOm>A(GLW9&~ zD|Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FZT01FZU(%pXi0006zNklLBO?ccg<(Vi96RihU3|k%V;awJqJeI@l%)iVo_6hv)OYp8NjsJn)aIDi0D60lEO+ zUt(r~8@r3=iv~|2TucG{LTU=bZDDP0X624n%=$VCEHOJWM*7tooR*ioVKDu1bo(Q?>Hx@q&+GP(AA5&) zXDe$?b>M1l1i)~bcs4e%;dCcv`zF%whw!-EWJ;wv0Bk^?#;}?i@E<({K>SjeXk#Yp7uEqW^LZ-5|E1)5lTj@kXlJxvSm!hUq`CLwgTuyOog&g>}9I&!tKMTxN8rXmn$R?8jYzYK#)z_n3j>6(1HNdJs0D!4vl4@YSl0heE5;PeM zs;uEqNTZG5txeV=>(X^@H|S8XSO`J|~)U zqm&yBpABjvF(64=ux(8w2GoBhsN1zDZlSv-+7B~OddfivTMi;8{IJUd1z4WI + + + + + diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/search.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/search.gif new file mode 100644 index 0000000000000000000000000000000000000000..2a620987e0e1b7e9ad3213c1c0541cc7db5afea0 GIT binary patch literal 550 zcmZ?wbhEHbN5Ue#OE(%(37LBq6V z9n+Td&t5rW;i_4SRxet#bn%j9%U7*mvS!Pg4O(=etw(i)p zW8a(u7dP)YI{(m>`G>D=+jn@?$s4Or-8^vY%$n0T*POm}_~iLRr_OIYcjxG-3rEge zK6d8fwhQ-8oWHv3@`D{$9-h2#Vqg^X to)YGxrtD|QY%Y26QkOZasnngjVb*LG0!~jl%~&`CUUzEyhBY!+0{{*nXwCot literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/search.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/search.png new file mode 100644 index 0000000000000000000000000000000000000000..fba33b8a57d351da68abc3340dd4e589bc55764d GIT binary patch literal 685 zcmV;e0#f~nP)#l3+^+pwak93=7Z|VPQhts0;rO9~&RUt*+FN7~L36 zT)GisL=jOEKtN*@DU?oWX&E}5(#K3^oZtp@;Ysf9{_Z{ZTypLD4RDN|2}L5e!mZIB zPVgYBs>x(x>852`PZ*>4TO0xUJuGWFNZDW5&SL~3w^7#Gh(FVO(dO+!J zkSd|QcRG$9Z!Wq-&8%V5C?k>8AcUf5kHv>AtKMXq7IqfLi;dlc<4~`ap=t(7RSR+h z56vi{nloG5JO3=$yv;<_dHpvJ|UZ~3kek5~Z%{1Ls^2Zt> zpc6CS5?fUh#fpVSpBMWSKMpqg5e-UsHTF5HZRE!Nq5!dhgXrbJhy|9`EkjZE40d+L zsLxHVEfj#_Hhr4?j(hhXRwbwS;`FiZY&hscM3EqQY%rS%vs2UaT1nqJ74{1(pUv4L z&tB%QT)kZh`9)>u+(7?YW_{efYprfO*eaJnyx|y#AcHVI!Z8}FJ7B?}@^y1*E)?%M z@_gj(aBN|AW`2BP@}m3CFS=d;<0J)~-~(kI!*QHtci56&mJO$@Wfs$$?-o<}z6(PG zoua_^CRbA*DwRrR8=@XB2xV?I&UQkgD8bZouqdBdRM+4BOsKt=&JT7Odg1KqYHIQy z$k_qa;DOgCXMZgxKaNe!_vr;aEl~RrhyRGUf8lx0^xWLSWuMc&5^oKpehV-FA-Vn? Tug%)J00000NkvXXu0mjfX&yMP literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/shadow.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/shadow.gif new file mode 100644 index 0000000000000000000000000000000000000000..af7f537e391f08327e417c7be6323c917d5d3788 GIT binary patch literal 4364 zcmeH~`9Bkm1IFhn#FWr1l_*z|D{WJftISm@$4F91mrqH>qTDfTa$Anf*leuLoO4ue zV{GFw|)Su9pZM@MI8 zXLoluhr<~f8X6uR9vK-K8ylOMnVFrPotv9mSXfwDS=rdw*xcOQ+S=lBxjQ>MySux5 zK7a4O0RGSa4g9YeILQ?j002M$&Hn`d&n5t(e1MLq!ZqrbAEBa$HC&si*>xWeD#O#S z(QpmXAiXC;&9s~*jG`$@!LkV7k^mHtfP4oCiX>{^XQo@y^H`rDuV#l@=mfh@8qSOz z%VJ_@&b0)MJIvzz?yM^=H39GvQZLzw{$!ZB{+*=I4)!E_JvafeTpYDktw=Sptt}_%;c55rAjW@-KXrQetiY8i9OKjO~74)h0 zFE-vl8%FU=H&G|Xmc=NU>ko0P%e1L1{YxqOXKsX5`PQS-J@CK@+bT%&P-DWGyKL9< zFG^WCR$1?lLf7Xw^hc$X>dMWsqm*vTB^4$-zm$o;T#>s(N-)eTZzlEDL zwSW7zH@>#3IvONw`^POu(-kWC{n^m?RVI)`L*BSAl*mzA{5?menuRRP#b%y zi1r$b%1(+LLuoqXc|>bB`f;Ll`@F{8^hb!}=qZuraf~r9>3*Db(6Px~WRvbQ^tpY|(4Z2T*gAni=NY*IusZJavx?pQjbbN6#1K zs<+P5Goc6m7N=NR{Vj=ej{aL3{kZjS*>AA}3*}&x)j~ysfAj*wC%<)}@_EI9#j1xb zR*Ti;Y0--{gn`z@Z@0JymcCnwSug#tkdIlay`#(G8qc&Y zH~Br5SZS7zvR-L%N5-r$*GX+Ft=!rDt8Hgntyft(zrt79&S`CWETcV%R~;sFYu`?_ z6S4X&@N<$r-L|hJUiX0PY|b}0+?9OUhtO+#-7oq<(%^>&M)Jjg=W6tu!7U2QpxXPJ zUd*nQ zlJZ(^T#X7?@zZL*n8Cg*^=Pe&ZW*}#m=ROm*Z_3TEYf7l9WB0}H^y%8?~v z_Aj2wRekB#CH|nkq=r7&RupYlsXat03{!b_RQ#rBnQYMuZ>@mrVGp&ss!nP|K;=o` zUS8cXamq)&k*31I(8%(W(l1|HT*QsX6YHCGXiFLD!+1CY)m5KGi83AHdU{`5nmLUck#6J z&3xwRqM{_*fYX{eYOT)pwB*t%C9PNa4R=9g^`^)6baY>p%5i!=>yi)s^ZOkYh>$w_rF2pHK0Mf6V#0jj6Q)!Qw* z=$Xm<9(kjz{PsXNJ&RwUVl0GW1xx#W-gk+3;V`M=g>G^7p8z$pcJ})?YR2`{G?`UA}mO>U_)1obC&+iwNml z_3JvyJ=usnqD8KReFFCEb=}^}R)J6M<1~7`p(UivYz+s=oZiBSxlFf*HyrDDDYW>K zbDo#Zpedw2yO5GXCHNWF8KwS;(&)lBr|s^n@{+4p&b#^TJ|zpDar(PeLiL1ddbHp; z4X9iin&oV(?4sOk@1Pe}{j5-zJTT!@N_XIDdI>cSbmozZ^NyjtZnh@&7CER>0{Lg{ zJCmkrjgy8HXvYRp&p-n}s{ z2K{}kqVUzH6Kx0>p^u%{u(1sj4)z~=d)&;|9d5_oc5v+NwGUsv`H*!Bq1xML z(Xf8~wd5_g&7OXk$c9b0%}tM{9*&mz20SU&-ZQ0VKq-9VYA)->lSe&+at#}1C6YJ1 zFZBEr7umehU~}DPf6ox#Y}2SW*6vMz_wY*Crr`w37D?zHnXKQ0tV`Oyf7|_QP+B)J$_wU-Ut#hZ@R;a12vC{f2&GSGjbXnIpNtmn7H@Ahyf9(2` zX~tE#jkZj7?3#!V<0?O7U&E?)O-9sn6<-6dWo~v(A%%Iz!fmhOnmVVw&3ND>v_)P@ z=Zr@f??^7&ob;%3)}fv!Qvx)nTo0j%>{M8NHJ}|*H zVGueNRO+`y*MTNAZ#x#j!aE`&c2{bzb}RwSb_8WH#*IfhmIcFh_D;25W=^%QY}D=S z=u2H@m$k3X3hi>u?2NiTwy%wt?rz+|TM} zA;kY1W@k9o#NMKq@@Eq zY53`#6V=z`gq%0E*PnLQ^9#i!g~HUFFZPCV4u$F|hXG_ljJ?4BZqO29Y~dF++z@IT zDQi`(d(+F<1{9ukBV08nT+BDzi6id{(Y@Df?6wvDQy|>a>Bz&>h^yfd&o~J0tq7{@ zM<0lbuhYlUk_f+P(ETl)z%65B`Nx>$k4H@-rDY=dA(5a6Kb>f&E0L*@uCG7Fskz2O zq8e^TB}ckqQlpegqtcx`GPk1CJVS9D%~U6pZfE4-927_fMZD%n*g`o-M;EDqi=CoJ zUq_ci-0p zN}pe>D=l`v5c*_K>`0{ZA1AauBzD?Makd;S+=pK9b6)~s#0@ZO9QO@B%)w*~4{~pZ zgOS_70MhOWLgN$+V)nHh8G~paur!%UjkC6o6B>xyGx?;t7I)BDMy}%A(R>q7+NY}Z zPl^ND(qeJP{LiTlm?%NxxxsOo{*Gt3{CMniyzW4-j&lP0cKmK?{Az82wr>JN{*JMH zqR#JxMUO;1{X{cpG`u3Ql#^(i7JXelsS4tJ%RdN~kGWQ%IV(OKayttmc08054l5$x;#n(Aje;~yrJLRWRYVKM}(16~X z3UrwN+2|;Egn#PaPpQ+K)NYeB@>*(~|EYLqbTU^nV+Nj@mi7dM#SO@0RcI3At?``PS9dL=Hs+Bdx! zdZLD#{!lu-r#ZdMBjfv@^e+BDyhBAsqm7YX~r{cLuWh*R$#&Uvh9?zDNUvg8=pd0BKmZ z3?f?&nGM2bgBjThgW1P<*@|GC5)5|=fm1=^)UY@W22OJjr^Ul*gL8CYIr@kk17yxI zFhKA+K&%6xr2{Yq<4s_AGX&lOiNA)$!x?z%LA)&we;u4_56iuU$aO&GI$?7Wj9k~j zTsK~>J2=k+miGXWX9Wj{xC6vT0f(*&>|e@z0Veps2)+oy8zjLGOF%LR0fU71JVFqd z7y=`1gdvC#NMaQRA{e-BOx=x3^UB~{`l6}U}3c$6k> mNRx4)$-Se2KGVRJG=-nEko^8U73WMVE8{ literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/shadow2.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/shadow2.gif new file mode 100644 index 0000000000000000000000000000000000000000..099cbf35d455e13d49010e736c40bab22c3d1a23 GIT binary patch literal 3093 zcmV+w4C?boNk%w1VITt_1B4d<%F4>i%gfEp&Ct-$(b3V<)6>}4*xcOQ-QC^Z-rnNk z;_2z>>gww2>+A0B?(*{T_V)Jo_xJet`1$$y{QUg={r&#_{{R2~|NsC0|NsC0|NsC0 z|NsC0|NsC0|NsC0A^8LW0018VEC2ui03ZV(0{{j7;3tk`X`X1Ru59bRa4gSsZQppV z?|kq7z@TtQEE-fBW&+q&HUw43mgoTEOh>323ij9tskdcy;CXAGqn3@z$jQok#>>vn(9v7X($&`2*gn+R+}+;a zB-`NQFo8M@6i-F;d2kBmaafxiU`5mNUP^ zthuvE&YnYm1TDI>Mbf5IKSZs%HA2>|V+VvSyS7M-wsRk)t-JR+-oAsA11`LHH{!;V zTSKnA`7`Fuqo2bqz4~eD*0Zn1uD$zf?%u=C1~0z+ZS&~;K~K*<)cW@D=aG+3|J(ce z_#fTR&mYnL{s0d0Uw{PiF<^lR*8Xu|f(-sMUxN_ZvS5T1mN8+47%uc+h8&i1VTT}A z@nMK0jM&!CIM#4ujy%G!V~;?t@MDlfUR30f2A+uI zl4lIbWROli*<+Ma=166gHC~Bjj9PBlVwYZ~2xgcij!9;SW}ew$nrddqW}6kh31@_I z&e>p{b|#2to(1m7XMleG*grYeVOs>ZF#s&KBp>RYU`>PBm=w%v-WY`X61TCcvU25hjV4NI(O z#vbcgvdU`4Y_pa<3$0|*{!Z&ywbm+zZMKGO%dKGEe(P7b;_5|ixptk4E?w%b3s<}D zvITFveH8S#a3R7 zah4iy%w@+Oe+hEPVvbC5nI@l%X38qB$#TnXz6^7mGS5tB%{JeObIyA1%yXYU{|so* zLJvxG(S{z4bfQWx&1ln3KMHl!l1@!^rB+{!Y1UeA%5~SCehqf0VvkK~*=C=LcG{}0 z&33D9zYS~La?eV4-L~G1cdmNx&1>I&{|b2E!VXS&v4$UxY~qS9%Xs6?J`Q=bl21-; z<(6NIdFI+~&Uv@~o_`K*=%SBHdgfn@WP*Dd+}Wnk9;}GFF#K6&f9l<^j1MnJxkMH53%;$Sm6|=|; zEOId(Tm1ea9>OR_I*_r9RPiDjjkm_UoUx6IP$L|NBgZ*{k{I zda#+!3}G~Na?EMYpqkc<0XDNK7ie;`h2H!o48@s1a+cGa3=AhaC$Y_So}iuXyo);H z2}F991fKRh#XR$w2z=VppY#N1JO%1bg09n`=tQVF6-rKqiqoOrgeW&9noWvY)1uSF zX#O-c>P(I<)1$})X)#4AOp^Z6q`X9FE>&tvmd?_pu!LzVWvWV=qSB_N#HlEC3QC@O z(x;pRY9@thNupBHsFFnLBbCZXrY6#<6zg#=g+8EQD+dC2K;;j?l6o#B2vOt3l3Q(6bZ-Z3IQ@K+-PIv#*tyFT8c&$s0RuK0xeJ>qiDxY|SR^^{9J=0?xC&V%mq{-ldM z>K4zs!o%+Gw97m0=FYpf1265w8$0s4&b+HbFY44=I`)dry`O_G=j59?`dZGulfy6M z^xHW8D$c)$12EwP95@2|&A@v@Fy0hgHwMei!Eb{w+a#Pe3Y*QsW5Y1mG~6`~Yt6$~ z12NS^95oU<&BRMXG163AG!_fZ#Xo~F&t#l48r#gqGs7{=blfr?tIWqI12V~k95Nz% z%*Y!&wgc0yDkD94|7v%gpOSGrH7V zE;ftH&EJAEx8$5HI$O)m)50^f^xP~yE6dNv0yMD%9V|lo%Fw$)G_Dl>T`NY*%F(Za zG^-??DoUHm(xbvOs5IRvPHW23mjX4VL>(zoJId6HLN%gPT_{!y%GG~@HJ@aiCtBOd z)^ox&oOInLUaQI1X96~vgdHYg<7=P%JcO~gv}`OjyGqXv7_yU{k7-j|K-RwYAENyv zZZpZ-O9HpTvaRhti2F$AE>gOQwC;(OJ0tA|QoJqp?(5WBh2*~X81t=fGxEFNX$1Io z1n!V|^T^;CBX~m)jw6P1OyN5GH^kZCa2i$oV-x>Q#t(9F7qyu-hQv-Yu(Q$Z<{&$T)P6Ixr~T`0M+@BJ z9t5^S$nH9$`}5|mcVPBidVlu@-WfFbo(29md_R0L6`zg5BgpYUV>~t>Paw(6!~F1%kJ z@^4N2IvxhH{b1Az~Of$cznwr7FK;el|Yfy2asum^(0VS;lbf~|mp z!NG!dqkmBEOYgNTygh)bl1 zm?w#v1BpFEiIQiDjlqeXgNcyfiHRYKpaY8aon(rQK#GN-ilc*yjSv6?5Dow^i?mpa zws?!Un2Wl&i@ey2zW9s47>vR=jKo-s#(0d#n2gG}jLg`K&iIVb7>&|6jnr6;)_9HB z*oy%m00lq-1MrRD7>?pNj^tR5=6H_in2zeWj_lZu?)Z-I7?1KekMvlN_IQu@n2-9n jkNnt={`ijo8IS@wkOZlY1W*75;ED>lkPLY#Apih7!xnFd literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png new file mode 100644 index 0000000000000000000000000000000000000000..a2561df971728d988424100c74c817916eca1979 GIT binary patch literal 3403 zcmeAS@N?(olHy`uVBq!ia0y~yV738bR}L1Sh{z?w93Z7#;u=xnT$Gwvl9`{U5R#dj z$`F!Ks$i<%mYSqsWME*TU}$J%1Vly(x&~$j21QvmX+Ul4C7!;n>{pmr1x4l8+mz-4 zg*Xd5B8wRqxP?HN@zUM8KR`j2bVpxD28NCO+HqZdXMAQjaPeGm)a##I4DP>8^|Q}*osX?x zu(w4E^8SQ>2<4nWJ;;$Ch1?$dLgm)?6;Yr9_CdHMW*W(@x+ip*R06EH_T4pml(Y0_%+Z_b^VZki z+Ig-L)GH{z-FHJSJv;l^O{dMW8|Geryoiy(edpWebG3Q>oX-o7q}^hCpz*y@X6IS? ZpGWQ1{0Pup3+%oyc)I$ztaD0e0swh%>OlYi literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/sprite.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..33d2c4d4649ec35b7771213977d7c83180907ccd GIT binary patch literal 40027 zcmeHQ32;*Xd5@PDnyRAS950pn@kTqkzH)Dmsf-xivbj>rOel&QW5GYq_;B z)^62WJ7syy&Nz!Zhwjd}GXh?Vx}fNeI;$WeSGYt75(r7>K01B9{r$h!U#D{!sG23D zUw>WS>+kx1-~alq|NGv1kALH)>vH_%egHYkmn~Vz|3&m`eHpy&eDdEe=AW@@<#pF! z{q~ARMl$Zb;kqTTfJjQ z-49H~@+FIJ{aXX8SFc%Py!7JFEf#aEGb}pCQcuFo*49=h(+|^=k!dr)i1R_~gG1sj zM~k1tn=rJ<>mW;49KjrhxXe7QJPI*#?CosJZ(Dwm`=g&A!{iokN zc!NF*aGGQ|UK{&0GIfOm@ZYVh;`Qms^41DxIk(u1JO>0^M) zNfpJ8Aao=m9e;}4G!l=PjKrhVPUl`AwFw-JIvNKZepUd3-<~oW%Tg?#UBKCWz%)&V z-zg>sQcS7OjGS~AaQvxJx6=nfSK27*@P&}$4^F4e;!ZbfyipS_b?EzVvhK8*U^7DM$jQw^S0}$0 zF~&}r$Q5!R0mL}jVyu}Er(2<#EJb-qOC%COEEi%?wqT7c2fk*8$~|Ijakv<$p6KlBY|& zcuh&p7M~(U^0egmpTL=`NgGy(?(gp!Mqcf&!wQ~!({X9fX0pvpiI0`zTPJNKY$9y} z1yS*ua6wMO{qeYdC%B=z4vChsiW&*U%c;1Xr-B30?Ub>)n$j1=MR|@;OGq;y8L4Cj zNLcKO0;{_a*<&+}Knh-nj5A1e2NhLnkYJ>SO>XCGhs_v~)PxIR{ozV9A{mc+T`4fqXAL0yt(Zx2k z6I{V5SY0$OeSz->w;4L5suxhXa7&bOs zD`zz=F$>tp2x7v6I59%7#MPA-MMP4H#OYOvT1l#)RM1~XJV{C=>WPOmY`n;r{Hcej z1hHt;{u+g4aWG?C?1ox`f~YK%gb2N0lG|$%<}=?P2<%up zy9*F0F_mki6>_|2WDlcUPma3Ve*3BQOFx z*3O(i?;?Eg;U}`5L2+rhvJ7ce9*mqwR4hSFjSbkdXEzpHd5J1=`0z0$?dXx9(|ZqbPEIziyh1({+V2h@u1DW# z9>e!$WF&|$EuYn2$ryyj+(cedHnt2H^ONJZ+%dKcNWWaAt0$;>o?exznT;t2?1I~GLE(y<29yiDwGm&eT7}?|BLmV0M?A&FsCe)}O#1E* z24sy@`-%Y_+qR-*?JqF#p1*`=!i0YD4?vL-sz1AuQboqHeWRnR!#Z;RcaeMLHE?;o z{nlYw=5pz)oV~as1B3^%;saVdtpC%ruQVXsC?8^QQ@Zv#Al4U#ryv)R4#t!A1hPPqF~Zv|cO&c};bQcOd)>b3kWH-672(sQkuajI zEvWnT3oI8_Oz;%is=j6QKcMxcb+GmyMDK=;2sAYl5i)@t`UsnH1$#gaA$gyI2P+oz$dpaa79;N4wKzv+osJpu>DhH=Hz#LzRa%UqKU;=KFWZzl=ym%>Ma8jWY9MGO zdyU=>aJvH+J7DgYZRiRFkTt=6!YUl>MakX+$ai_+^0*-zxt^r2x*iuUbZ{_X?d(`6 z*?j^n$Ldi~S;ZWnx1$r&KKcN{57E0DCq4XiVAAiGFIjx6kMTBo?KCbi4gk>!g(#}2 zMu_UO_w|j+ATf1j4azT>gU|QxhpVYY4G=xDXOBCLODq_KYxYc(Ew~&(4sf6R%g!JMz~)gTAahG`F>*{%AcqN!W}pK1ZHe zh6yYyN+t+scxfMOKg6~tEn6xXc#re+6b?dDII2om(kgFWL4NnZBA$cWH`snS97OFS z53*+9^@o0lDIM*o?CelsNjv`^S0ltS7V*^WD{W|JM=@B~;Qg`?C-wHmX5^b0*!kn9 z5juPbbDsP$h$2iQf?|#;qA^`b@91b`p3!s?MmU6EIEn*@k7AuBjkkaJFxvWhQR>e|S$>|%uiw2JFWhxE@+p++`~qaM zzYN!YFX4WF@nuS&Wt_UI;txuE@#<@MV*Pq*vl}(sn)>*!+gA*Ta-?;2uscwq!NzXv zc=%CdU2s0Cw(dc(%fz;a{|;RwdT}GcJQ)fpH9FfNh=JMNbc#c51fAXO*m(2x$giG- z%MS0wROW4%b156N@K@l#GmO*i`#3O7ro<=v{qXbO%Q;QlRECS$$;o74vP2Pr&EyJezYjLeH)Kq+rMG>_QPk`z7Yr?dUg2R8CZ2v(lNOP{yHz?aTuNUM@vg9 zf}HoeJ6qvH4;or{Vv5t?ym|BB;VMX6j;u?^kxO*Vk#OF(=M(JOw3Y`08&ObLfYOp; zOqo0dc@wAckbsE~KKO$&{f-2GuBUTGND{kD8_E-H>9owc@S-cwTR0ax_wI*>J0mam z&{;WosH@wLS6+RE6M_qy-`>PUOpt@RJwl)BL?`2?W`X2{!C|l0&&7=gGp0>eS?<7L z^c?sIPd)xCvM;>f^`Pd~-Jx;o67JsWr2cH7`8&guA&F3{cC&Z(BWh$MIDiz6ru zpMvlGXW?n%djEneaL4=_9659VCusy|XlTHK1z$@T7L!2F`5^Z(GLYHciiPpnM{nZ9 z*54u6+l9IV`>=WQCRDd>L(WHQxim2mjc}6W9g%O||JV4=l9@Ph^boFIcr`A%cn&7X z%4eLpAzfhBB@3~B!^AnP@gCU(v75;%C$}7TeeWS$dfqxz z+;|JZw2g&b<7);GSsCq}?e?92B{>=S#94@Tx59eo8U6?Gd8>tvo)9XEvM_CCEiF7G zG=B<=>^$zk`Vgs{&;JO=fqlGzc%@y%IJpkd0FAf#{#u;k4sTayCfZutQC(=FqBINj zhdxK9KOgzr!T1_?A$;ZiYQm5iqL9<%cynqqBxYZ6VfO9yufBol;e)8{*nusrWoT^) z;L=hH7grTw_ZQ7*>yBb(IW^_TaU82zf$sWaI9z`mvui6+!ZpHpa|339w>E7-e((fp zdg`#bXEN%J2QW9k7ne-U!;XWE*w+~3O3#Y|wBvtUe0aSsfStSdB8y`}%V+Cog`rVo zoH=vXw)at8mW^GcV(=PY&b(mm6|m;cS9dT%9W6M- zzFnIe#;hp?*s`|?f9x*BWz$QrB)1X1;%Ts!d>@T(yp3g5<>QOe5wPV#NNy6)(jtb@ zU_0{MK~$FK;iH2A>}fS{`*|KLENH_Hs}k;It7tp-(k5e}Y1iw#C0P6zlPp;*-ixwtwPRn5ksQ(@E;(GEX~8^V}T%AYIQ-`?|llS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwZ>1U+3GLp09UPTc5s*g?SM{+oLf)tZ__ zR!(p@Fh5waj_C($l(<4b1Dm6(NVVp>bAC-z*80u6IrHYmcX|1bKE0ZCzF*XXfkUxT zgTe~DWM4fd9|gm literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png new file mode 100644 index 0000000000000000000000000000000000000000..fbccab54d050bb7d2f503df9fb040d6328cda5ea GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0vp^Oh7Eg!3HE>ee_)kq!^2X+?^QKos)S9lS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwYw37#&FAsp9Z`wnt8IPkEj?~G{Df4iTh zUTFpEiAG(HZx0IX^c+8!zj<@$l###afwqn82Tlb`aXeaJq9ByTdv(stQpRt=O8dMf yu2?qz()u?OCY5`Aw6~OJI8i(I?dNUp820>T70JFbhpioG7lWs(pUXO@geCwfnO58Y literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png new file mode 100644 index 0000000000000000000000000000000000000000..3db0f361799c5fd3c8d44453c0e74eed59fe203e GIT binary patch literal 436 zcmeAS@N?(olHy`uVBq!ia0vp^96&6_!3HG%UcQ?Eq!^2X+?^QKos)S9lS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwZ>_&r@5Lp09UPBip)Hsoosf4=iA3(pD; z&MOQ5{@0dvVya_Ub%g0mfZ3I@nv;eW`n7KZ+$YNXJXc=!=H*AnNfR767*w8_Z?CDX zdr^Pfl*Iy{f+nQKHhnLiYs8rq9;!GzpE(ovEA@kR#xggRnuT|SWierz+*$L z?K+CB9g!yH*+yE2beElS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwZ>WISCQLp09UPPFx7HWX;xFL_&9#6_4> zv5;FUL;0O2qxnPikR_*CCv+^}lieuaDw{t^Y1*~y>UsBnn`~Qtoc-+c?@Oi};9lv)z3{>08%V(x%&Y={0ohZWh?Hcy?s7>7ombZ$Ca?6_Wft=M8hi<>IyH zWiRzacpYq*^IvLCa>H(OHco>(hvUjh3(J$Tg%w*2ugm5?@~G~9!1&hqOGnz%cW)2w z;xRscetG(u>+?A#2Bk=-@f>#W>J(u5`0iWV_M_}-JNE2STUv6V==Z-U&V-NqQa1m5 oT5;oim8xIQk57N9R-LoIqLa7d%*7w4fPu;2>FVdQ&MBb@0HlJn00000 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png new file mode 100644 index 0000000000000000000000000000000000000000..4726e62208e024a017a7c5325e160e202976d7d9 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^oItF^!3HFIe|>%mq!^2X+?^QKos)S9l5Y=V1NPK>@SUgxxb#j0)Hj=K%#H@hrp<0(ziY;Cn$qB$lOko9uweeR$HkHl1f*xW@hE+`)>M8>*Knp@$Fl1{s~J39 L{an^LB{Ts5ttLz; literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png new file mode 100644 index 0000000000000000000000000000000000000000..eb4b11efe5ee01103c2d5fa2f0f6b8921e640cce GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^Ahr?*6Oeql{{BWF#aJBV?!>U}oXkrghb_t5-G$*l z2rk&Wd@@jkv%n*=n1O*?7=#%aX3dcR3MP5FIEGl9PX6=%zddu_fg=s#0eWXoobYfk z*AwM9ym6JoM(JSIE|W9~gNGBHJsxveA7kVT`LtA$cXJ=#lFR3Ow<;}=p3c?sFd}rp wjoy_^uD3LPOZa8_Shkpi?Q|4a@x+^%p~OOFcBy}4KhQP?Pgg&ebxsLQ0DvV!;s5{u literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png new file mode 100644 index 0000000000000000000000000000000000000000..55b982d7c8e69c12497a020b798010f570d5cdaa GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^96+qZ!2~4VM)CImDaPU;cPEB*=VV?2Ic!PZ?k)`f zL2$v|<&%LToCO|{#S9GG!XV7ZFl&wkP%zlj#W6(VeDa_F|LvL04jgG<4hU>wbuu(E zI@G%0+`(6?{{R0Ud^0rTn!}DBW=RzbRSDkBeS9S@b7yCW4qd@!}UDqW~jjJ h3p(-_6b@=KGECU6@Nto2r904g22WQ%mvv4FO#l_>J;wk5 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png new file mode 100644 index 0000000000000000000000000000000000000000..957bd9f2adabb791aee25df923efebc85e86c0da GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^+(695!2~4VPBOj%q*&4&eI0=`L$_*zFOXs@3GxeO z_zz_L?>wMC0Z1Erx;TbNOiliC{=FVdQ&MBb@07%~_*#H0l literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png new file mode 100644 index 0000000000000000000000000000000000000000..200a37083d6d9f325c493ffe490dde115521f2bd GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^+(695!2~4VPBOj%q*&4&eI0=`L$_*zFOXs@3GxeO z_zz_L?>wMC0Z3bVx;TbNOij+o$VfQAJ>!G7@X{t$r>obmC!aDkGgCU#P++h$Fvl^h q<&0Y)y9?7ee_)kq!^2X+?^QKos)S9lS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwYwiJmTwAsp9J&u!#wFyL{y`2L2+8+&%4 zGYo4Ea4j%!auMQE=QX)@`L5_R>p0N^#|jw^6rJk}>SBJS{xf{lH9>|C+!9KCv*g_a yas}qkY?A&t1opP!-NXCf1kI#W7yNzsv!Jk1CJ%pG6qjqKbLh*2~7agCR0=Z literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabRight.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tabRight.png new file mode 100644 index 0000000000000000000000000000000000000000..501130796a5fa80c352b87defde9def6510f91fc GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^96&6_!3HG%UcQ?Eq!^2X+?^QKos)S9lS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwZ>q&;06Lp09UPK@Ho;O zx^+9}gqbCCH{D$GF^DTyKw)O&sg(}|4b6+$R&YpmI?oIA&q-r3G`^Dd`jpF0eFfvf z@ay1Lxm|3g&V^n{DDfM`+31cr$fOL+ubRRmc= z*UoY&Fg|=n%#&?-(2L*x>(+<;_*fBhM1kk_FwOXRu3klbVJI*x89ZJ6T-G@yGywoy-Kj7D literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif new file mode 100644 index 0000000000000000000000000000000000000000..0ee5497874cec4a5599e562dd7c00b8c57bc5a44 GIT binary patch literal 117 zcmZ?wbhEHb6lY*$c+APLV&#f6XU^Qcd-vtbm#<#E`uzFx&!0d4{{8#^|9=K-K=GfT zb5UwyNotBhd1gt5g1e`00E6OB7Dg@xeg++o4v@hNEW974Dl+V{P!?d(JSND4Tx0C=3O*LgTp`yasZ&&(KOonh?z*!QeilV$8X*|!YF7G{jGr7TetkrpMD z&_dc5MOuhTDP`+ch}5NoLsMHuF z0K~@8Y3?=_1Rq~N0;U&O0RcjQ05mByfoksR>Ii>1W_tkv0MMmQqEch-DXzvx=ACRv zc;Yzxaq-#JzrGIu0Jv#BzJ34*0s!zwFz31ez#qb#+X4W8GM!2XKm-5)e`q8r3;;m| z05zJ2I}reJ2mr7V%=u{mz=kmAjR1g63XPxxAld;~@o`~MaR8V>0M@l(qlU|W}pLrZZ`nt<&QNw8sO3*fK2+2HTkUoLJk1c z?LXG0-2njr2*Hkoa2;&`06=hA0H6mLjA;b`%x-|M#SF$`C4=#`3INpya62Y6k(RXl zj}VAQ0DpJQ{LbZpLI5BEkSxeK7Bm{o%7H<%ak8^ z7vL8Z5)u{`6cH5_6B8AekdTm+B1lTh$jHdb%E`;iDJUo^Dl4ietE#A}sjIKj&{(CZ zrLCo-tE;Dz5V`pb?=iun*=gG ztFLcpxPHB{@y3mtH*ej#ZF>85Q&V&EojWZpt*v+O-n)1Iew%Gu+k*!WAGWtYdi2=o z@#Bt;Cr_R}?R4wx?CR?7?&<0E?CpK_?6=>ZKY!u-;>F9CeSQ7?1AzkruU-uf4h_A2 zJsdJT{N~Nux4-{BGBO%5I{NP2`}bpGA3ltaPsC14Oiq6MI5qX@(==^*dS>SH=h@l0 zx%v4oUlvjq78VzmmcD-d_KlIwU@-krR2CLAJ1Yx@gAK#Z#ev~u=fZJwVX-)F9vm+Y z&&$Wl&nF-#C?q5xEFvN*Dk3H>E-oP{DIrB55TvDLWMyUKVRG{*w9WX1<|3e2qS75c=!Ab>= zj~;!mptH+;xq?47F!1Wt;QHkTRtg9o8TqFICMM&)7qH@=v|PY1{)jC|GRllaoMj4q zopn1VfK7`X!`{PD!Aaw?;^yZb#$LhY@VN0x@=oCE`EvPv1QZ3P1#bxD3cHDjiVTXL z5=#^}l|V~8kSvn&BuEg3rBBIFWHn{yFVS@~;a>30xC|3~F7U8|+HrB|Q$w4|NLT4(lRUhnGa`jLeTpi%#4S6GM#+ zj`OEjQ4QkNXfg?WbToZFaU`iLxiRH<>fVjnX_QUQoAuM>GPp7(GrO}Ix0Gk6=R|I` z-KLU@%l(wswf)kLlKj+yfStyL5`|xj`gYwcuGpQshqBjYpGFD3WPbm^ft#g=4`!A{ z9CABsbVRZoRX%fcredLTv1<0%%<+#WCQpu557rEy>aXoS-F~L!?9Fr6&R@E4?&9f7 zr!LoAsi~{IdiL7g`u2vd>n|EdZX#|8+}3QeYmT|IucfJV@}AUv*S5R|o$a!Zk{-7| zQGR-`3*TMbtMKg3^T?Nq{m6mQ!HHp!x8Wnb?~BHZrm&x57bO`C0Oo!N0KlpTu-^c{ zs~Nxp53oZMK(Q7;uo}RX8^FU5kkU4Qrqvx_bj?B87z;`5@>5Q72U!r#!6@Hz^G#iFcWN2 zZ0>B=*a_@A*k?JyIC?m3II}n#xwN@zxfQr8vC>!)wgM-ItKre$xyDQ6?Z6A-1Mw4l znfwC$wE{$eT>|}rsX{_Rb;3Tvr-YY8DnxBXr^F74O^e%!&r4KG`b*)YqNQ31o2B)o zKgpbz#mQ3SH07q{uPUS~S}WEn@hJ5v*Qlhb+N+7HO{oW{-&=K5BURH=OGg{8J+0HD zYolAg`lw#6e!M||p`DR|vC=iKCY9tUg#z+05B8?Aaao z9i^Q#oy}a_UBlhd+;^|5^=S1R@?!W%`kMNY{I>?w26hH52FsJ&Lo&h!!|@R|k(;6} zM~}xS#)idJQikGXXyk+%`ec$`O4~+(w9ri#(@`0onKfCAY_FVi+t~A(cSz@F6$}^J z6rC={?@rtErlj$JYH3*+*P)E^=%YiGepNjuI;x#(9@n~^^*c9sA?o6%I?k)5^~wzm zH->H{H({GkwwT{bYZH0U(C+_;@zlO^s=KV$>e;$2R|D;ouUP!T9d~-{}8rAAl*qO#EVx<+S+^?E3{(zuPZ~REW$l?ZxfH z=OtX1?N{X8WX}JPS9q;x{kQVV?)ty~BL9DI4?Xv*{Lk*m89&RfxaX4pmHgqE^5{yx zs-EL+O#9Q_EB2Su>p1I`8yXs&nEEZ|t^I$l|I)dS!PI}7x8grJH&s8q=@)+hR=|TS z7=kAxLMb%C1cHF@KolY#A~}(6$TH*z$`Dn6dc$JIQh{bgQ_%gaj;yyZ2AC^sdTjOV zX6z3*0y!o)^SI=>TDZwr9QF#1%p=6p#hZ_J;=}QE@*ffK6~qa42<;Sh6A=}8D|$&R zN8D3FMq*L&iBv72K$<4wEo&&JCNHeOp)jvFp){uaTIHSUJGJRmpuw&wrKP9sq7$uK zy!xWvkO8luxe?X4dd=8cb<;Sr>*friyX7TocAE&>%XU5X;|>c>;?BA*ZmuM^9q!fZ z`aM~_w7mU&ihY~?<^%KsQ-kgVqe#{vMWN$l#eWg8f=8{+O!4dRa_aMQOW z&L_vE3~vlb8`$iZK9ot$8p#gNd9y7s54F7@A75~?(5$GtICS^K-i#8d{pU;N4i=Uz z9Nu_j>S#j6SQYgc<3!TQZ#9LrQm4@c2;2>o0G_ z-)jACIpRGU`7ZuFbu9G5x^c4!^+~SDv5$>Yg`d2p#in1*?ECEed3LsZ&SLK6JpBv$ zOYQ>4!p?<-Me<_DlF`z>r8i%7zh-~!`X>8r`?nd!dPWVhI;^N}c($ezs zvP2@0N~LRSYwPRlGMP*+mn#$sl}e>nt2G*pR;$(Nbb7tsU@#bsMw7{8u~@8DtIcM! z+wBgA!|8OoTrRiUy|uNqy}j-6c)VV(&*$6O*#Z5365-zE7X+#VCH$%XXA=;_S52Z@@oz*!bv=CIazsb4zPm`x8<}Cz;YkrO~??Jxo?_ANy(lz~HkX z4tIEDl=pn>1%G^Ea%y^pf~olq4nKbUM0F)}@y*h*WaaJZJL%f`d)bGNa>a&HrPgS5 zdV|qqwpeZUO^4Iv-rDwfeLJ9lpl}46G+rI}McjFS-7#6~xSenf{1+esd?KYp3Opsx zHo#6|!;$?IL1WDKjPrv2E+OIYrMXt*0QF@{#?M-*V1V}O3A{S!%iKZw0vXX5cVTX@ zdy$4F=eFfOW1InJR=5hm21rAAC=2`L=^^IZf#OBR1>q3u9miKDpKCkK>0KMGuxWo2 z@U!c#gzp)XG3!dGr`5epZdd$(OS-TPNBUVM6OZWVYFTw~cFc!+`_C4C_%Hp4%rEG$w2E{$sZ|WCIn-4Au`Y&6|AV-@&Q2d*j}AfkNTiifB)jiM2q9lP=9hqG5V{>Z!1 zz0q|3mS%%cSQD!xUT(Uod|XWJRyLOoX_PHxGlGr#N9OY`VW2>_s{L1oM)jl$9H%DT zJ5ZwTz(g_BowziunvBnmi*5X64iSk-Kf};aTW@JKG*V5RmQHCZ(RS0R3@wBC`MT0nbiF)>R@cV|@6q=VR%3eDKva*uUzDcP4~Vnl4TEpawnRQHZ!Tj# zlit!9II@~}BUjOM%{crZg@0#v)0NT05%WyEiDzHFW_s>Y_9Twu{{H+KI67pfE_WRD2TBbvbn3kFFa=m31R-0f24iQSNf*2apI+xHVKOU4? zL4@s1V+WrA?>mGB`2_^ZY_H&uM7#J@$aVVyBAR7?4Ts3=i|Cxh%{M=syS}-USH#*} zK3{IwloZw`I#wkJI#NK`03XLGb;<%vFIX zl79%m2WOh^1mj4%q7mak`R2Q!A`>3b4`Y*V{bVyd2kNx4~KRJ*CM9A4zpkosLsy`z*JPGr4Ehj+)qPH$K_kF zd7{n(9AG*r7v^6GC5O%;W>T6hxMCDJOajcLjaYs!!;!;Ph}q0lOMS(?OP@J_*<)Ll zhAJop20;QR!mM}fu_X=wg_S2%xAb1cB&E zrtPs9M@^+6UzX(CnpZ^BG>+iqjoY>s8I+bWi=6*Yv#nK)qGd`1^S4H9ZDt%TOND%O L`)q75=;;3dF|oR- literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png new file mode 100644 index 0000000000000000000000000000000000000000..a0f839dc598edd80d093e1cf954134a916db5af0 GIT binary patch literal 3960 zcmYjUXH=6**L?u#O}f%WdKaluf)Jz$NE1a7rAaSRL^Kdo1f+wA0-y@lQhQUs)9 zXa+(sgwR0(k(Yb*e(O6wX3g4X&)HMxqwxo2N4X1ZS2_KU1|j7U8M@#IdxSrpIV`=K;*3|H zMKB;aU1F=?(&p?*oFb{CI^VhUY@>`MoY!whO~7A+X_VSQZ*bGChfl*$hZUPgS|e&B zYS{hItj%4@j_s7fi3`F&5AvHEL7DTMfIWyjpX&}8nGO_Clu!(JC*W{2VQMbV$;g0F zxM`k+BSE4ZslN*IGzdnkp7?FgPPVr(13-hDR$C}QLk?`|%VV2>4uP&r6gd#405hW2 zJ_i6{uEG)wz;#MMDLqo11@NN*Dp`h(+<=ZZ^4K^4YC;#6Lm~K`?9R5|BXd9C!*yV) z?$OHYTYxkfV5D25cnzp$0VW(BB_07Y%s|e{^1A-RHkTX~L3CUY=d^+nA@D3pI~77a+$*MhBZ9-<;aLrOxVV-AE<@ttX#b zPS(D?eLFBvTmMX|i^0FZ2!v zQk$AeY%1(ZDtufULR+i0k#QL+KJ!gpA;&>y%io2m%gB_Koyj&oJq^w9x`%p2%`8xl z0EO(${M=rSj~6q`(%J}E$K!E00^#S+ef$RG0FPMAIukiY_YS5(AS4n+I0Z$;14bq$ z#@0|xUImSl)vqW-`>B?K0{4|`M8f>>p`)XKR&0V`2Tvm6n zv$KtjM@Mq@oEBW1oLp(|+d@ePaY@4u=f|HOO2lDQiE(B*3ESq zQN}0mLn?A=1|f|vLCW%5xXC3VhUzvQjljjD0&-E&GU5DsyR7Uxn2!wRH@`Y55^{y;Le)am{l+HtNgQOlFr)J0URkSa)4cST`{U%& z*P6V$+g=`|z!1&mq1fv+!x$|tg zag6c11$WFD(UYzUv@Ze_AzFUIMN*AdFu}NmxLLP%Cc_!~;~0eo1<|X`HQb1Ij|;G1 z@4ET?4y- z*W-Pn?im2C=C#)H56E}OeNyFqFf+8BASP-Sf17!VAsbh*p*0_xkHLTSpB}bv@+F{u z8W+e;-#@#AT0;)L&l3;cl&{Ni>w61%)6PwKR(r%Z)>OcY0(Y_|Ak+0N)Ub`}gk*jM z$UHQpl77){u4D^8y!$2XE$K`6x6bHCNr)epTQ_5nG5M9*j$9%a8GzMLYM`T-x^fZd zD7n}{!Ca}!rd`FUUCG^%%G1$(K5HyMpKnHbGWjkQ;7iI?aHa%mTY zR9Q+`gnMMcZ0M^N92eh6>mTIyn6H~#7amGCNneys7-e7Gg^TwBtu9-STN>;0RA`os zfAaWrubo)7|H=0^0V_nW0!cg$voW)<+W ze82eMFh$tc`*U{P^J8;QD@yiV_jz|5ei;zciAg62stKwfDjBN5lsR36E~@Tr7UiUj zJX!sOK5^umUTcdtXlK`z*W5&>UpJ4`PAvh66?p#Uxd@ewNp<4s6@xj6Ndy$?GZEnn9{l(B^PZKH6*{$ zyU~Lcxu3RSvoh~Ae41zzT;@tb>WR zM(xE}%icGS$LFOE(#F?S*Hd}8xnUoOGOJ~zu=3Nf#)Qs^unYtmq^4pGYW>L-2Hfs= zC4J$gn6Qq7V&}P+&@F9tA$}pXB=0W@D>MV=t)U^^4oHh|7d^{SQfYiCLZ0e_lxK+N zKHde-tV*sXs=C{-(rDYze~0&gE`I28&vqO1Ft+g#A$XiGTX_aD+-0~IS+$V#nOM1D>PRm>T;>e14|X4Qbho`qP}A`;dZ!^HVs zhIb*@3FoTQdxvBH!ZLWQ-$yB;&Z1x>TM0BBcnOcjW9F_Ul@#FOc=(lxi>~dM=g(`^ z8YAFJM+7v)7PZp7c97MV&o^o22Q##TXs)*EXT@nu2Pf@SR|#BsxGx#F_3KvOx|~nX zqC3f34Xw$2B>P)4P|&DDk07?BD*EOH{X63e~dqQ7F7RY*nxo2Akhl=9~-M8a;$hFVW^#GKv+JL^`OOf+X8sVsLaT+r2L@1LJ%`r-hbo|QHG?xN6 zFEL?dYXugDJv4R|ta; zKtN@KbdU@Uej5NN>3=!Uc4DV%GyDT@>U zt?D4^KOpqpc=vAGi(}N1>i~7W8_}2lFZ;Kgkpza?68q!Go$|j5p2+~;-?WhSe+7ie z1O8$L{|`K68Z9tur2m^~$dr&eYM}d%SdX)dY7>Yz_5fKK9sQqTxpu$NjW+HafrP%n zdCtEh?@uKkl7$$$s}V2tLy{wf+eMF(R}fQewN;TpFwHSU?=BT|eMOV^A9_xR$&O8O znsAu#QToc^R9k&jTF_UI3T=DajFc1D%JXC&HS5Cm`YZNG?-(rOAA$QdB1#lthC7el znSQs%eN}VDh!l|>B|D{YCy%JXH#Ie~BmKj1^}!2(FRUf<-=nZ{Wcn?m1m5Jp`e%fp zs9Pgf;3j*xT|1s^)Da}P`IkbFhlbBT)qnZC=%WI*8XxRx(Ql@%wbHdxuZbAWB3?NM zo1bbUYPrt5v)dd)q61Dme?R-PpY=IhDTl42eS&9PN(SnJ$yT5f;mD|nA46=oM z$r?C*XZ>-+z0pGzdyA`EYD%j1D2mot;|96Meg5u~jax68HT?f9Zz(-BcRzLZIZyYl zaKXbD+`~_$AVN1O|0aoh&=;jQ1dc;Z)R;%E@@6N{kN)*wgR(PR&SMvw`|DX0(;qf~ eaFWq5OFp3xyjg7(l{rBA2R76*1()kQeEB~+uV_F3 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png new file mode 100644 index 0000000000000000000000000000000000000000..10998ae7b37f7462d6d8780c092b2c42f2b87249 GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^EI=&H!3HFw3-t_u6k~CayA#8@b22Z19JVBHcNd2L zAh=-f^2tCE&H|6fVg?3oVGw3ym^DWND9B#o>FdgVlZ};CPx{!U2mgRVk|nMYCC>S| zxv6<249-QVi6yBi3gww484B*6z5(HleBwYwIi4<#ArhBcCv4FxtmpF^vf0ZtmW3wW?R6r@!BMzW0`(AIn1J}M@~KJ_Bfp%=6Xf@ zz*W^<8$T2>{TEPd+2($FN}ccfdoNwrRMcl3+}ZqT`{}$MCAqmNTD=YHw5@>lGI+ZB KxvXlS| zxv6<249-QVi6yBi3gww484B*6z5(HleBwYwzMd|QArhC9@9eDoThFF)V9^SVlMPBA zzMqd5VN_!?IdJI2iH3MqGu;(c5fT?zB{(?~q)xT0Ywhgpba6J^YG9l^!|_IdNxQ6F d35zQOLvk9Y)x(dMYJdhac)I$ztaD0e0sz`NNu~e* literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tree_close.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tree_close.gif new file mode 100644 index 0000000000000000000000000000000000000000..e26728ab36b9d612af0edb49ea297da5d6c4b96a GIT binary patch literal 300 zcmZ?wbhEHbnIzhYznme}4D*^ZPGfK7RlH z`Nxm1KYo1s@#Fj7zrX+f{qvv3K=GfTb5UwyNotBhd1gt5g1e`00E6OB7Dg@xdj=hl z_dp(HU^6-3oZ!L3;dnwN<42~M?d-(mhgZg_IrAp$ZsjqS@NN|<J`-rmugEauac+Ic? literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/tree_open.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/tree_open.gif new file mode 100644 index 0000000000000000000000000000000000000000..edf662f36f32f39352a4033cde8c43baef3611e5 GIT binary patch literal 202 zcmV;*05$(dNk%w1VF>^U0E8X@0001qrohU=v&+M@%fq$I#J11Ky1dKU*U`n<)XCY@ z%H7z{-rCXO-PGdV*W}^Z=;hz&=HKb&;O_0{@bK&N^X~KW@AUNX_xJPo`1Sbs_W1bs z{r&s>{rvy`|NsC0A^s6Va%Ew3Wn>_CX>@2HM@dak03rDV0SW*g04x9i000R92><{E zGT;%6WFUHI>Wy6sbX+!Wn+9l=G-5#CKckC<0+>J=qlkk6SSS#qgn=*^2nwbW=@0?{ EJ89T&G5`Po literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png new file mode 100644 index 0000000000000000000000000000000000000000..f80319b0a4e21532f5139acd618ce6782117b2d2 GIT binary patch literal 334 zcmV-U0kQsxP)1EtgQQRS5LtMzbrwA?yc}HH6?GB;baT0Ow3G2}Uok zHZ&NcAq(C>m59+ZoP+%k$L{2MaLq1=`W8Dud*c$1alVL@3 g)*%1<;7x!50Gzmo@IIg>jQ{`u07*qoM6N<$f_#RHXaE2J literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png new file mode 100644 index 0000000000000000000000000000000000000000..868012434599b589e3d75190202fe6b4a16270cd GIT binary patch literal 309 zcmV-50m}Y~P)j*YPNxz}~_W*jQMJ*jNY_HW3qJ$j|;{+%Yjh7DXTI z56m}@x3hwX@T)q!Emk{P?@u40(V&mXcqqWm)7g@~sjgSfEMqi;Xx*xG1SO027%Zja z3u|B{GbFdrh09XI_j^v><6-N<#u!A=#hwaOl5I99vJ`oGci}h;klUiRafr(2!})Hs za^Y)mMiPh6(!xz{g6A>fw=mCfzTr7{>*dawCI0!rhX4Zrv(|{FCa*++00000NkvXX Hu0mjfk~@O2 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/up.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/up.png new file mode 100644 index 0000000000000000000000000000000000000000..2174d03a9c91a29a40ce8e9cfd715e9b9320f178 GIT binary patch literal 619 zcmV-x0+juUP)vmZ_X00GBIL_t(I zjir-4h!a5+$A3Hfp&N7OLOAf6gQOHW4z;rpP76^GI}1A@mL|kbk8Dso#aMc^{UIn8 zDjZ1ALaoH=xD=v=aAe(F-0vBSP4t!v8uV2&%=`c5z5mS9lv4ap7?Z`v3zuBrI^g_8 z;{gwkJ>75tG^G^p&R=W`+jc8w&N6@e1j4qbDrFGhe|XQ%%NNKXa8Eva(lG!cB^#CH z6|99t!p~p-c)hi-NNIV6owvOPkOM*`ozhYb8O1a8U0SN~<;`mc#vX)9AvA$X(wP84 z6NF>)bAXhnG$|ydSw^*f1%UqgV+>s{oQG2G0VoNhA4PGq&gQMV0Gz&alm3JC0wh#9 zKuk8uAENvr$-tx9tkY|)VaEw}oX~5nQEk>q2Hvl{3E+1HzE3JMYWF$-oVtH|U+=Yc zi|re2453d042jc(&C6H$9!Ho$V2ZrtL}nyXa@ab+k@6gdVeSV+-T)JgN-CL0fCOZ= z*L%!VV(yNJP(^?OZ#($r96eOVvZg=X*j^yw(`Xl!f9V`h)vmZ_X00DzZL_t(I zjir-KOB+!XhM(h1jFXHIQ$%-J1iF$LXcdG~;$P^Z3pcLaRr&`q`2&*fT)XL_i~fa_ zQYiJK%;2ghsGzuMn}svAHkZ>yZX~0T2K&P0ex3I@=W;oss{D`K!D3q^0$u@?e|WmU zn>E!4Kvb12k)~5BNAoXUQhnaQDV0Y$O_uWG;|DI@zr$o{w659`Rb^A;=lt?Y<GJe9t4ZPjvxOfU{G~SoXeBI}a+r9Otte;PK85 z0N<9DaBcyz+W_bKcitQ)d*;HjO5Rf`kO!vOa_fB}*);1Y1`8NeTV=L55&DoAZW377+_cXIbt0H!Q3 z_X`w@-LFos(r^skJ?3G3F%B5Fdjs&Mr`sLTs5qIi=w9K;=mz_x!ftPh&UFb-r46vmZ_X00C-AL_t(I zjir;ZOF~f;#((GaRIvBdAc=w+8=8cmDH4u%Lb(A(07#^Mkopg(g^U z)IR}}kT13HoER*}AZiUazP>~C1xk&M2h3jp*b&JBdyMv(@&~H9KEPk~0rA~|dRYjc QCIA2c07*qoM6N<$f>7|)4gdfE literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif b/public/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif new file mode 100644 index 0000000000000000000000000000000000000000..84972788620159cbf2bfc443acbdea47350e69de GIT binary patch literal 357 zcmZ?wbhEHbE&KGfouIlu9-6#HcaB$ zG?i=146bdnIajS>*t>-D$U2T4+ZlK7W;nHt{p>EbO9$C5A7VRmhT+CZ)|;nUZ(U}7 zaE0aZ4VEXjnD5?Ycy^ch`90=WkCD>#a_b-_~zF~Uzj^Wc=rqAz~zJ6r< z@s;uCcg9~o82|rg_)j8G{3qyKl$uzQnxasiS(2gP?&%xAp!k!8k&D5eK?meLkVhTZ zau3W8h|mygJ8~u4lObW+>2n{Mco{e@gr`I#q?iebooI6SC$mJn?L%ahn2UG`Lq&?~ z@grF)Ic%m^c(92os;bHgx;wM0dxu2?y0fZ~Y@8+83$Y2csYTc*2 literal 0 HcmV?d00001 diff --git a/public/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.png b/public/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..de51084e8489f498b89dd9a59d82eb9564b7d050 GIT binary patch literal 516 zcmV+f0{i`mP)X+Z1;#G+8)`#`)nwDij+1|+};(+Jd*z{tojUrGNrgOti&26irp z_}33i3=gldFg&}%ydKD%4m4mlSOTPRRTp>8w%MHj-#%jkasQt=!|=bOgW><(yI^TB zesYWX|At9i|AA_qz?K0SLTh@t|9^bL1XtwZ!T_=ktQjT-!jEsTfHbZKahQM#GSy9g zGw=!jV;}@%)c=6I5d!peNPt)%qXt$R0@A3+&Ho=o(%2Y6D=A@WxvV2T-}@x_m?j12e;Kn75?FIa%Y*5~(_)p>;wfs>Yo>SSa9R12cEf^3|A z?HDV=w@(OLSFdJZ*t3U$;p|ydO|Ks_Gd#G$auApZ7BB&cJHLN2R-V|*!SL$`L^DVe z48y>e_e>0@wy}el)6tV$3kUcAYBiJJ3`|`A816m!$KYVc!0_$`6T_P)%nWzVvoQSm z#aIlqs1HRWRI?%|K>)EC$csSy9f(f>@eyb`{RmSF5MTfvB)oWs%O|`50000 0) + path = reLastDir.exec(path)[1]; + + path += backDir[2]; + } + + else if(src.indexOf("/") != -1) + { + // "./some/path" + if(/^\.\/./.test(src)) + { + path += src.substring(2); + } + // "/some/path" + else if(/^\/./.test(src)) + { + var domain = /^(\w+:\/\/[^\/]+)/.exec(path); + path = domain[1] + src; + } + // "some/path" + else + { + path += src; + } + } + } + } + + FBL.Env.isChromeExtension = script && script.getAttribute("extension") == "Chrome"; + if (FBL.Env.isChromeExtension) + { + path = productionDir; + FBL.Env.bookmarkletOutdated = false; + script = {innerHTML: "{showIconWhenHidden:false}"}; + } + + isGetFirebugSite = reGetFirebugSite.test(path); + + if (isGetFirebugSite && path.indexOf("/releases/lite/") == -1) + { + // See Issue 4587 - If we are loading the script from getfirebug.com shortcut, like + // https://getfirebug.com/firebug-lite.js, then we must manually add the full path, + // otherwise the Env.Location will hold the wrong path, which will in turn lead to + // undesirable effects like the problem in Issue 4587 + path += "releases/lite/" + (fileName == "firebug-lite-beta.js" ? "beta/" : "latest/"); + } + + var m = path && path.match(/([^\/]+)\/$/) || null; + + if (path && m) + { + var Env = FBL.Env; + + // Always use the local skin when running in the same domain + // See Issue 3554: Firebug Lite should use local images when loaded locally + Env.useLocalSkin = path.indexOf(location.protocol + "//" + location.host + "/") == 0 && + // but we cannot use the locan skin when loaded from getfirebug.com, otherwise + // the bookmarklet won't work when visiting getfirebug.com + !isGetFirebugSite; + + // detecting development and debug modes via file name + if (fileName == "firebug-lite-dev.js") + { + Env.isDevelopmentMode = true; + Env.isDebugMode = true; + } + else if (fileName == "firebug-lite-debug.js") + { + Env.isDebugMode = true; + } + + // process the + if (Env.browser.document.documentElement.getAttribute("debug") == "true") + { + Env.Options.startOpened = true; + } + + // process the Script URL Options + if (fileOptions) + { + var options = fileOptions.split(","); + + for (var i = 0, length = options.length; i < length; i++) + { + var option = options[i]; + var name, value; + + if (option.indexOf("=") != -1) + { + var parts = option.split("="); + name = parts[0]; + value = eval(unescape(parts[1])); + } + else + { + name = option; + value = true; + } + + if (name == "debug") + { + Env.isDebugMode = !!value; + } + else if (name in Env.Options) + { + Env.Options[name] = value; + } + else + { + Env[name] = value; + } + } + } + + // process the Script JSON Options + if (hasSrcAttribute) + { + var innerOptions = FBL.trim(script.innerHTML); + if (innerOptions) + { + var innerOptionsObject = eval("(" + innerOptions + ")"); + + for (var name in innerOptionsObject) + { + var value = innerOptionsObject[name]; + + if (name == "debug") + { + Env.isDebugMode = !!value; + } + else if (name in Env.Options) + { + Env.Options[name] = value; + } + else + { + Env[name] = value; + } + } + } + } + + if (!Env.Options.saveCookies) + FBL.Store.remove("FirebugLite"); + + // process the Debug Mode + if (Env.isDebugMode) + { + Env.Options.startOpened = true; + Env.Options.enableTrace = true; + Env.Options.disableWhenFirebugActive = false; + } + + var loc = Env.Location; + var isProductionRelease = path.indexOf(productionDir) != -1; + + loc.sourceDir = path; + loc.baseDir = path.substr(0, path.length - m[1].length - 1); + loc.skinDir = (isProductionRelease ? path : loc.baseDir) + "skin/" + Env.skin + "/"; + loc.skin = loc.skinDir + "firebug.html"; + loc.app = path + fileName; + } + else + { + throw new Error("Firebug Error: Library path not found"); + } +}; + +// ************************************************************************************************ +// Basics + +this.bind = function() // fn, thisObject, args => thisObject.fn(args, arguments); +{ + var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); + return function() { return fn.apply(object, arrayInsert(cloneArray(args), 0, arguments)); }; +}; + +this.bindFixed = function() // fn, thisObject, args => thisObject.fn(args); +{ + var args = cloneArray(arguments), fn = args.shift(), object = args.shift(); + return function() { return fn.apply(object, args); }; +}; + +this.extend = function(l, r) +{ + var newOb = {}; + for (var n in l) + newOb[n] = l[n]; + for (var n in r) + newOb[n] = r[n]; + return newOb; +}; + +this.descend = function(prototypeParent, childProperties) +{ + function protoSetter() {}; + protoSetter.prototype = prototypeParent; + var newOb = new protoSetter(); + for (var n in childProperties) + newOb[n] = childProperties[n]; + return newOb; +}; + +this.append = function(l, r) +{ + for (var n in r) + l[n] = r[n]; + + return l; +}; + +this.keys = function(map) // At least sometimes the keys will be on user-level window objects +{ + var keys = []; + try + { + for (var name in map) // enumeration is safe + keys.push(name); // name is string, safe + } + catch (exc) + { + // Sometimes we get exceptions trying to iterate properties + } + + return keys; // return is safe +}; + +this.values = function(map) +{ + var values = []; + try + { + for (var name in map) + { + try + { + values.push(map[name]); + } + catch (exc) + { + // Sometimes we get exceptions trying to access properties + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.values FAILED ", exc); + } + + } + } + catch (exc) + { + // Sometimes we get exceptions trying to iterate properties + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.values FAILED ", exc); + } + + return values; +}; + +this.remove = function(list, item) +{ + for (var i = 0; i < list.length; ++i) + { + if (list[i] == item) + { + list.splice(i, 1); + break; + } + } +}; + +this.sliceArray = function(array, index) +{ + var slice = []; + for (var i = index; i < array.length; ++i) + slice.push(array[i]); + + return slice; +}; + +function cloneArray(array, fn) +{ + var newArray = []; + + if (fn) + for (var i = 0; i < array.length; ++i) + newArray.push(fn(array[i])); + else + for (var i = 0; i < array.length; ++i) + newArray.push(array[i]); + + return newArray; +} + +function extendArray(array, array2) +{ + var newArray = []; + newArray.push.apply(newArray, array); + newArray.push.apply(newArray, array2); + return newArray; +} + +this.extendArray = extendArray; +this.cloneArray = cloneArray; + +function arrayInsert(array, index, other) +{ + for (var i = 0; i < other.length; ++i) + array.splice(i+index, 0, other[i]); + + return array; +} + +// ************************************************************************************************ + +this.createStyleSheet = function(doc, url) +{ + //TODO: xxxpedro + //var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); + var style = this.createElement("link"); + style.setAttribute("charset","utf-8"); + style.firebugIgnore = true; + style.setAttribute("rel", "stylesheet"); + style.setAttribute("type", "text/css"); + style.setAttribute("href", url); + + //TODO: xxxpedro + //style.innerHTML = this.getResource(url); + return style; +}; + +this.addStyleSheet = function(doc, style) +{ + var heads = doc.getElementsByTagName("head"); + if (heads.length) + heads[0].appendChild(style); + else + doc.documentElement.appendChild(style); +}; + +this.appendStylesheet = function(doc, uri) +{ + // Make sure the stylesheet is not appended twice. + if (this.$(uri, doc)) + return; + + var styleSheet = this.createStyleSheet(doc, uri); + styleSheet.setAttribute("id", uri); + this.addStyleSheet(doc, styleSheet); +}; + +this.addScript = function(doc, id, src) +{ + var element = doc.createElementNS("http://www.w3.org/1999/xhtml", "html:script"); + element.setAttribute("type", "text/javascript"); + element.setAttribute("id", id); + if (!FBTrace.DBG_CONSOLE) + FBL.unwrapObject(element).firebugIgnore = true; + + element.innerHTML = src; + if (doc.documentElement) + doc.documentElement.appendChild(element); + else + { + // See issue 1079, the svg test case gives this error + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.addScript doc has no documentElement:", doc); + } + return element; +}; + + +// ************************************************************************************************ + +this.getStyle = this.isIE ? + function(el, name) + { + return el.currentStyle[name] || el.style[name] || undefined; + } + : + function(el, name) + { + return el.ownerDocument.defaultView.getComputedStyle(el,null)[name] + || el.style[name] || undefined; + }; + + +// ************************************************************************************************ +// Whitespace and Entity conversions + +var entityConversionLists = this.entityConversionLists = { + normal : { + whitespace : { + '\t' : '\u200c\u2192', + '\n' : '\u200c\u00b6', + '\r' : '\u200c\u00ac', + ' ' : '\u200c\u00b7' + } + }, + reverse : { + whitespace : { + ' ' : '\t', + ' ' : '\n', + '\u200c\u2192' : '\t', + '\u200c\u00b6' : '\n', + '\u200c\u00ac' : '\r', + '\u200c\u00b7' : ' ' + } + } +}; + +var normal = entityConversionLists.normal, + reverse = entityConversionLists.reverse; + +function addEntityMapToList(ccode, entity) +{ + var lists = Array.prototype.slice.call(arguments, 2), + len = lists.length, + ch = String.fromCharCode(ccode); + for (var i = 0; i < len; i++) + { + var list = lists[i]; + normal[list]=normal[list] || {}; + normal[list][ch] = '&' + entity + ';'; + reverse[list]=reverse[list] || {}; + reverse[list]['&' + entity + ';'] = ch; + } +}; + +var e = addEntityMapToList, + white = 'whitespace', + text = 'text', + attr = 'attributes', + css = 'css', + editor = 'editor'; + +e(0x0022, 'quot', attr, css); +e(0x0026, 'amp', attr, text, css); +e(0x0027, 'apos', css); +e(0x003c, 'lt', attr, text, css); +e(0x003e, 'gt', attr, text, css); +e(0xa9, 'copy', text, editor); +e(0xae, 'reg', text, editor); +e(0x2122, 'trade', text, editor); + +// See http://en.wikipedia.org/wiki/Dash +e(0x2012, '#8210', attr, text, editor); // figure dash +e(0x2013, 'ndash', attr, text, editor); // en dash +e(0x2014, 'mdash', attr, text, editor); // em dash +e(0x2015, '#8213', attr, text, editor); // horizontal bar + +e(0x00a0, 'nbsp', attr, text, white, editor); +e(0x2002, 'ensp', attr, text, white, editor); +e(0x2003, 'emsp', attr, text, white, editor); +e(0x2009, 'thinsp', attr, text, white, editor); +e(0x200c, 'zwnj', attr, text, white, editor); +e(0x200d, 'zwj', attr, text, white, editor); +e(0x200e, 'lrm', attr, text, white, editor); +e(0x200f, 'rlm', attr, text, white, editor); +e(0x200b, '#8203', attr, text, white, editor); // zero-width space (ZWSP) + +//************************************************************************************************ +// Entity escaping + +var entityConversionRegexes = { + normal : {}, + reverse : {} + }; + +var escapeEntitiesRegEx = { + normal : function(list) + { + var chars = []; + for ( var ch in list) + { + chars.push(ch); + } + return new RegExp('([' + chars.join('') + '])', 'gm'); + }, + reverse : function(list) + { + var chars = []; + for ( var ch in list) + { + chars.push(ch); + } + return new RegExp('(' + chars.join('|') + ')', 'gm'); + } +}; + +function getEscapeRegexp(direction, lists) +{ + var name = '', re; + var groups = [].concat(lists); + for (i = 0; i < groups.length; i++) + { + name += groups[i].group; + } + re = entityConversionRegexes[direction][name]; + if (!re) + { + var list = {}; + if (groups.length > 1) + { + for ( var i = 0; i < groups.length; i++) + { + var aList = entityConversionLists[direction][groups[i].group]; + for ( var item in aList) + list[item] = aList[item]; + } + } else if (groups.length==1) + { + list = entityConversionLists[direction][groups[0].group]; // faster for special case + } else { + list = {}; // perhaps should print out an error here? + } + re = entityConversionRegexes[direction][name] = escapeEntitiesRegEx[direction](list); + } + return re; +}; + +function createSimpleEscape(name, direction) +{ + return function(value) + { + var list = entityConversionLists[direction][name]; + return String(value).replace( + getEscapeRegexp(direction, { + group : name, + list : list + }), + function(ch) + { + return list[ch]; + } + ); + }; +}; + +function escapeGroupsForEntities(str, lists) +{ + lists = [].concat(lists); + var re = getEscapeRegexp('normal', lists), + split = String(str).split(re), + len = split.length, + results = [], + cur, r, i, ri = 0, l, list, last = ''; + if (!len) + return [ { + str : String(str), + group : '', + name : '' + } ]; + for (i = 0; i < len; i++) + { + cur = split[i]; + if (cur == '') + continue; + for (l = 0; l < lists.length; l++) + { + list = lists[l]; + r = entityConversionLists.normal[list.group][cur]; + // if (cur == ' ' && list.group == 'whitespace' && last == ' ') // only show for runs of more than one space + // r = ' '; + if (r) + { + results[ri] = { + 'str' : r, + 'class' : list['class'], + 'extra' : list.extra[cur] ? list['class'] + + list.extra[cur] : '' + }; + break; + } + } + // last=cur; + if (!r) + results[ri] = { + 'str' : cur, + 'class' : '', + 'extra' : '' + }; + ri++; + } + return results; +}; + +this.escapeGroupsForEntities = escapeGroupsForEntities; + + +function unescapeEntities(str, lists) +{ + var re = getEscapeRegexp('reverse', lists), + split = String(str).split(re), + len = split.length, + results = [], + cur, r, i, ri = 0, l, list; + if (!len) + return str; + lists = [].concat(lists); + for (i = 0; i < len; i++) + { + cur = split[i]; + if (cur == '') + continue; + for (l = 0; l < lists.length; l++) + { + list = lists[l]; + r = entityConversionLists.reverse[list.group][cur]; + if (r) + { + results[ri] = r; + break; + } + } + if (!r) + results[ri] = cur; + ri++; + } + return results.join('') || ''; +}; + + +// ************************************************************************************************ +// String escaping + +var escapeForTextNode = this.escapeForTextNode = createSimpleEscape('text', 'normal'); +var escapeForHtmlEditor = this.escapeForHtmlEditor = createSimpleEscape('editor', 'normal'); +var escapeForElementAttribute = this.escapeForElementAttribute = createSimpleEscape('attributes', 'normal'); +var escapeForCss = this.escapeForCss = createSimpleEscape('css', 'normal'); + +// deprecated compatibility functions +//this.deprecateEscapeHTML = createSimpleEscape('text', 'normal'); +//this.deprecatedUnescapeHTML = createSimpleEscape('text', 'reverse'); +//this.escapeHTML = deprecated("use appropriate escapeFor... function", this.deprecateEscapeHTML); +//this.unescapeHTML = deprecated("use appropriate unescapeFor... function", this.deprecatedUnescapeHTML); + +var escapeForSourceLine = this.escapeForSourceLine = createSimpleEscape('text', 'normal'); + +var unescapeWhitespace = createSimpleEscape('whitespace', 'reverse'); + +this.unescapeForTextNode = function(str) +{ + if (Firebug.showTextNodesWithWhitespace) + str = unescapeWhitespace(str); + if (!Firebug.showTextNodesWithEntities) + str = escapeForElementAttribute(str); + return str; +}; + +this.escapeNewLines = function(value) +{ + return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n"); +}; + +this.stripNewLines = function(value) +{ + return typeof(value) == "string" ? value.replace(/[\r\n]/g, " ") : value; +}; + +this.escapeJS = function(value) +{ + return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace('"', '\\"', "g"); +}; + +function escapeHTMLAttribute(value) +{ + function replaceChars(ch) + { + switch (ch) + { + case "&": + return "&"; + case "'": + return apos; + case '"': + return quot; + } + return "?"; + }; + var apos = "'", quot = """, around = '"'; + if( value.indexOf('"') == -1 ) { + quot = '"'; + apos = "'"; + } else if( value.indexOf("'") == -1 ) { + quot = '"'; + around = "'"; + } + return around + (String(value).replace(/[&'"]/g, replaceChars)) + around; +} + + +function escapeHTML(value) +{ + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); +} + +this.escapeHTML = escapeHTML; + +this.cropString = function(text, limit) +{ + text = text + ""; + + if (!limit) + var halfLimit = 50; + else + var halfLimit = limit / 2; + + if (text.length > limit) + return this.escapeNewLines(text.substr(0, halfLimit) + "..." + text.substr(text.length-halfLimit)); + else + return this.escapeNewLines(text); +}; + +this.isWhitespace = function(text) +{ + return !reNotWhitespace.exec(text); +}; + +this.splitLines = function(text) +{ + var reSplitLines2 = /.*(:?\r\n|\n|\r)?/mg; + var lines; + if (text.match) + { + lines = text.match(reSplitLines2); + } + else + { + var str = text+""; + lines = str.match(reSplitLines2); + } + lines.pop(); + return lines; +}; + + +// ************************************************************************************************ + +this.safeToString = function(ob) +{ + if (this.isIE) + { + try + { + // FIXME: xxxpedro this is failing in IE for the global "external" object + return ob + ""; + } + catch(E) + { + FBTrace.sysout("Lib.safeToString() failed for ", ob); + return ""; + } + } + + try + { + if (ob && "toString" in ob && typeof ob.toString == "function") + return ob.toString(); + } + catch (exc) + { + // xxxpedro it is not safe to use ob+""? + return ob + ""; + ///return "[an object with no toString() function]"; + } +}; + +// ************************************************************************************************ + +this.hasProperties = function(ob) +{ + try + { + for (var name in ob) + return true; + } catch (exc) {} + return false; +}; + +// ************************************************************************************************ +// String Util + +var reTrim = /^\s+|\s+$/g; +this.trim = function(s) +{ + return s.replace(reTrim, ""); +}; + + +// ************************************************************************************************ +// Empty + +this.emptyFn = function(){}; + + + +// ************************************************************************************************ +// Visibility + +this.isVisible = function(elt) +{ + /* + if (elt instanceof XULElement) + { + //FBTrace.sysout("isVisible elt.offsetWidth: "+elt.offsetWidth+" offsetHeight:"+ elt.offsetHeight+" localName:"+ elt.localName+" nameSpace:"+elt.nameSpaceURI+"\n"); + return (!elt.hidden && !elt.collapsed); + } + /**/ + + return this.getStyle(elt, "visibility") != "hidden" && + ( elt.offsetWidth > 0 || elt.offsetHeight > 0 + || elt.tagName in invisibleTags + || elt.namespaceURI == "http://www.w3.org/2000/svg" + || elt.namespaceURI == "http://www.w3.org/1998/Math/MathML" ); +}; + +this.collapse = function(elt, collapsed) +{ + // IE6 doesn't support the [collapsed] CSS selector. IE7 does support the selector, + // but it is causing a bug (the element disappears when you set the "collapsed" + // attribute, but it doesn't appear when you remove the attribute. So, for those + // cases, we need to use the class attribute. + if (this.isIElt8) + { + if (collapsed) + this.setClass(elt, "collapsed"); + else + this.removeClass(elt, "collapsed"); + } + else + elt.setAttribute("collapsed", collapsed ? "true" : "false"); +}; + +this.obscure = function(elt, obscured) +{ + if (obscured) + this.setClass(elt, "obscured"); + else + this.removeClass(elt, "obscured"); +}; + +this.hide = function(elt, hidden) +{ + elt.style.visibility = hidden ? "hidden" : "visible"; +}; + +this.clearNode = function(node) +{ + var nodeName = " " + node.nodeName.toLowerCase() + " "; + var ignoreTags = " table tbody thead tfoot th tr td "; + + // IE can't use innerHTML of table elements + if (this.isIE && ignoreTags.indexOf(nodeName) != -1) + this.eraseNode(node); + else + node.innerHTML = ""; +}; + +this.eraseNode = function(node) +{ + while (node.lastChild) + node.removeChild(node.lastChild); +}; + +// ************************************************************************************************ +// Window iteration + +this.iterateWindows = function(win, handler) +{ + if (!win || !win.document) + return; + + handler(win); + + if (win == top || !win.frames) return; // XXXjjb hack for chromeBug + + for (var i = 0; i < win.frames.length; ++i) + { + var subWin = win.frames[i]; + if (subWin != win) + this.iterateWindows(subWin, handler); + } +}; + +this.getRootWindow = function(win) +{ + for (; win; win = win.parent) + { + if (!win.parent || win == win.parent || !this.instanceOf(win.parent, "Window")) + return win; + } + return null; +}; + +// ************************************************************************************************ +// Graphics + +this.getClientOffset = function(elt) +{ + var addOffset = function addOffset(elt, coords, view) + { + var p = elt.offsetParent; + + ///var style = isIE ? elt.currentStyle : view.getComputedStyle(elt, ""); + var chrome = Firebug.chrome; + + if (elt.offsetLeft) + ///coords.x += elt.offsetLeft + parseInt(style.borderLeftWidth); + coords.x += elt.offsetLeft + chrome.getMeasurementInPixels(elt, "borderLeft"); + if (elt.offsetTop) + ///coords.y += elt.offsetTop + parseInt(style.borderTopWidth); + coords.y += elt.offsetTop + chrome.getMeasurementInPixels(elt, "borderTop"); + + if (p) + { + if (p.nodeType == 1) + addOffset(p, coords, view); + } + else + { + var otherView = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView; + // IE will fail when reading the frameElement property of a popup window. + // We don't need it anyway once it is outside the (popup) viewport, so we're + // ignoring the frameElement check when the window is a popup + if (!otherView.opener && otherView.frameElement) + addOffset(otherView.frameElement, coords, otherView); + } + }; + + var isIE = this.isIE; + var coords = {x: 0, y: 0}; + if (elt) + { + var view = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView; + addOffset(elt, coords, view); + } + + return coords; +}; + +this.getViewOffset = function(elt, singleFrame) +{ + function addOffset(elt, coords, view) + { + var p = elt.offsetParent; + coords.x += elt.offsetLeft - (p ? p.scrollLeft : 0); + coords.y += elt.offsetTop - (p ? p.scrollTop : 0); + + if (p) + { + if (p.nodeType == 1) + { + var parentStyle = view.getComputedStyle(p, ""); + if (parentStyle.position != "static") + { + coords.x += parseInt(parentStyle.borderLeftWidth); + coords.y += parseInt(parentStyle.borderTopWidth); + + if (p.localName == "TABLE") + { + coords.x += parseInt(parentStyle.paddingLeft); + coords.y += parseInt(parentStyle.paddingTop); + } + else if (p.localName == "BODY") + { + var style = view.getComputedStyle(elt, ""); + coords.x += parseInt(style.marginLeft); + coords.y += parseInt(style.marginTop); + } + } + else if (p.localName == "BODY") + { + coords.x += parseInt(parentStyle.borderLeftWidth); + coords.y += parseInt(parentStyle.borderTopWidth); + } + + var parent = elt.parentNode; + while (p != parent) + { + coords.x -= parent.scrollLeft; + coords.y -= parent.scrollTop; + parent = parent.parentNode; + } + addOffset(p, coords, view); + } + } + else + { + if (elt.localName == "BODY") + { + var style = view.getComputedStyle(elt, ""); + coords.x += parseInt(style.borderLeftWidth); + coords.y += parseInt(style.borderTopWidth); + + var htmlStyle = view.getComputedStyle(elt.parentNode, ""); + coords.x -= parseInt(htmlStyle.paddingLeft); + coords.y -= parseInt(htmlStyle.paddingTop); + } + + if (elt.scrollLeft) + coords.x += elt.scrollLeft; + if (elt.scrollTop) + coords.y += elt.scrollTop; + + var win = elt.ownerDocument.defaultView; + if (win && (!singleFrame && win.frameElement)) + addOffset(win.frameElement, coords, win); + } + + } + + var coords = {x: 0, y: 0}; + if (elt) + addOffset(elt, coords, elt.ownerDocument.defaultView); + + return coords; +}; + +this.getLTRBWH = function(elt) +{ + var bcrect, + dims = {"left": 0, "top": 0, "right": 0, "bottom": 0, "width": 0, "height": 0}; + + if (elt) + { + bcrect = elt.getBoundingClientRect(); + dims.left = bcrect.left; + dims.top = bcrect.top; + dims.right = bcrect.right; + dims.bottom = bcrect.bottom; + + if(bcrect.width) + { + dims.width = bcrect.width; + dims.height = bcrect.height; + } + else + { + dims.width = dims.right - dims.left; + dims.height = dims.bottom - dims.top; + } + } + return dims; +}; + +this.applyBodyOffsets = function(elt, clientRect) +{ + var od = elt.ownerDocument; + if (!od.body) + return clientRect; + + var style = od.defaultView.getComputedStyle(od.body, null); + + var pos = style.getPropertyValue('position'); + if(pos === 'absolute' || pos === 'relative') + { + var borderLeft = parseInt(style.getPropertyValue('border-left-width').replace('px', ''),10) || 0; + var borderTop = parseInt(style.getPropertyValue('border-top-width').replace('px', ''),10) || 0; + var paddingLeft = parseInt(style.getPropertyValue('padding-left').replace('px', ''),10) || 0; + var paddingTop = parseInt(style.getPropertyValue('padding-top').replace('px', ''),10) || 0; + var marginLeft = parseInt(style.getPropertyValue('margin-left').replace('px', ''),10) || 0; + var marginTop = parseInt(style.getPropertyValue('margin-top').replace('px', ''),10) || 0; + + var offsetX = borderLeft + paddingLeft + marginLeft; + var offsetY = borderTop + paddingTop + marginTop; + + clientRect.left -= offsetX; + clientRect.top -= offsetY; + clientRect.right -= offsetX; + clientRect.bottom -= offsetY; + } + + return clientRect; +}; + +this.getOffsetSize = function(elt) +{ + return {width: elt.offsetWidth, height: elt.offsetHeight}; +}; + +this.getOverflowParent = function(element) +{ + for (var scrollParent = element.parentNode; scrollParent; scrollParent = scrollParent.offsetParent) + { + if (scrollParent.scrollHeight > scrollParent.offsetHeight) + return scrollParent; + } +}; + +this.isScrolledToBottom = function(element) +{ + var onBottom = (element.scrollTop + element.offsetHeight) == element.scrollHeight; + if (FBTrace.DBG_CONSOLE) + FBTrace.sysout("isScrolledToBottom offsetHeight: "+element.offsetHeight +" onBottom:"+onBottom); + return onBottom; +}; + +this.scrollToBottom = function(element) +{ + element.scrollTop = element.scrollHeight; + + if (FBTrace.DBG_CONSOLE) + { + FBTrace.sysout("scrollToBottom reset scrollTop "+element.scrollTop+" = "+element.scrollHeight); + if (element.scrollHeight == element.offsetHeight) + FBTrace.sysout("scrollToBottom attempt to scroll non-scrollable element "+element, element); + } + + return (element.scrollTop == element.scrollHeight); +}; + +this.move = function(element, x, y) +{ + element.style.left = x + "px"; + element.style.top = y + "px"; +}; + +this.resize = function(element, w, h) +{ + element.style.width = w + "px"; + element.style.height = h + "px"; +}; + +this.linesIntoCenterView = function(element, scrollBox) // {before: int, after: int} +{ + if (!scrollBox) + scrollBox = this.getOverflowParent(element); + + if (!scrollBox) + return; + + var offset = this.getClientOffset(element); + + var topSpace = offset.y - scrollBox.scrollTop; + var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) + - (offset.y + element.offsetHeight); + + if (topSpace < 0 || bottomSpace < 0) + { + var split = (scrollBox.clientHeight/2); + var centerY = offset.y - split; + scrollBox.scrollTop = centerY; + topSpace = split; + bottomSpace = split - element.offsetHeight; + } + + return {before: Math.round((topSpace/element.offsetHeight) + 0.5), + after: Math.round((bottomSpace/element.offsetHeight) + 0.5) }; +}; + +this.scrollIntoCenterView = function(element, scrollBox, notX, notY) +{ + if (!element) + return; + + if (!scrollBox) + scrollBox = this.getOverflowParent(element); + + if (!scrollBox) + return; + + var offset = this.getClientOffset(element); + + if (!notY) + { + var topSpace = offset.y - scrollBox.scrollTop; + var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight) + - (offset.y + element.offsetHeight); + + if (topSpace < 0 || bottomSpace < 0) + { + var centerY = offset.y - (scrollBox.clientHeight/2); + scrollBox.scrollTop = centerY; + } + } + + if (!notX) + { + var leftSpace = offset.x - scrollBox.scrollLeft; + var rightSpace = (scrollBox.scrollLeft + scrollBox.clientWidth) + - (offset.x + element.clientWidth); + + if (leftSpace < 0 || rightSpace < 0) + { + var centerX = offset.x - (scrollBox.clientWidth/2); + scrollBox.scrollLeft = centerX; + } + } + if (FBTrace.DBG_SOURCEFILES) + FBTrace.sysout("lib.scrollIntoCenterView ","Element:"+element.innerHTML); +}; + + +// ************************************************************************************************ +// CSS + +var cssKeywordMap = null; +var cssPropNames = null; +var cssColorNames = null; +var imageRules = null; + +this.getCSSKeywordsByProperty = function(propName) +{ + if (!cssKeywordMap) + { + cssKeywordMap = {}; + + for (var name in this.cssInfo) + { + var list = []; + + var types = this.cssInfo[name]; + for (var i = 0; i < types.length; ++i) + { + var keywords = this.cssKeywords[types[i]]; + if (keywords) + list.push.apply(list, keywords); + } + + cssKeywordMap[name] = list; + } + } + + return propName in cssKeywordMap ? cssKeywordMap[propName] : []; +}; + +this.getCSSPropertyNames = function() +{ + if (!cssPropNames) + { + cssPropNames = []; + + for (var name in this.cssInfo) + cssPropNames.push(name); + } + + return cssPropNames; +}; + +this.isColorKeyword = function(keyword) +{ + if (keyword == "transparent") + return false; + + if (!cssColorNames) + { + cssColorNames = []; + + var colors = this.cssKeywords["color"]; + for (var i = 0; i < colors.length; ++i) + cssColorNames.push(colors[i].toLowerCase()); + + var systemColors = this.cssKeywords["systemColor"]; + for (var i = 0; i < systemColors.length; ++i) + cssColorNames.push(systemColors[i].toLowerCase()); + } + + return cssColorNames.indexOf ? // Array.indexOf is not available in IE + cssColorNames.indexOf(keyword.toLowerCase()) != -1 : + (" " + cssColorNames.join(" ") + " ").indexOf(" " + keyword.toLowerCase() + " ") != -1; +}; + +this.isImageRule = function(rule) +{ + if (!imageRules) + { + imageRules = []; + + for (var i in this.cssInfo) + { + var r = i.toLowerCase(); + var suffix = "image"; + if (r.match(suffix + "$") == suffix || r == "background") + imageRules.push(r); + } + } + + return imageRules.indexOf ? // Array.indexOf is not available in IE + imageRules.indexOf(rule.toLowerCase()) != -1 : + (" " + imageRules.join(" ") + " ").indexOf(" " + rule.toLowerCase() + " ") != -1; +}; + +this.copyTextStyles = function(fromNode, toNode, style) +{ + var view = this.isIE ? + fromNode.ownerDocument.parentWindow : + fromNode.ownerDocument.defaultView; + + if (view) + { + if (!style) + style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, ""); + + toNode.style.fontFamily = style.fontFamily; + + // TODO: xxxpedro need to create a FBL.getComputedStyle() because IE + // returns wrong computed styles for inherited properties (like font-*) + // + // Also would be good to create a FBL.getStyle() + toNode.style.fontSize = style.fontSize; + toNode.style.fontWeight = style.fontWeight; + toNode.style.fontStyle = style.fontStyle; + + return style; + } +}; + +this.copyBoxStyles = function(fromNode, toNode, style) +{ + var view = this.isIE ? + fromNode.ownerDocument.parentWindow : + fromNode.ownerDocument.defaultView; + + if (view) + { + if (!style) + style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, ""); + + toNode.style.marginTop = style.marginTop; + toNode.style.marginRight = style.marginRight; + toNode.style.marginBottom = style.marginBottom; + toNode.style.marginLeft = style.marginLeft; + toNode.style.borderTopWidth = style.borderTopWidth; + toNode.style.borderRightWidth = style.borderRightWidth; + toNode.style.borderBottomWidth = style.borderBottomWidth; + toNode.style.borderLeftWidth = style.borderLeftWidth; + + return style; + } +}; + +this.readBoxStyles = function(style) +{ + var styleNames = { + "margin-top": "marginTop", "margin-right": "marginRight", + "margin-left": "marginLeft", "margin-bottom": "marginBottom", + "border-top-width": "borderTop", "border-right-width": "borderRight", + "border-left-width": "borderLeft", "border-bottom-width": "borderBottom", + "padding-top": "paddingTop", "padding-right": "paddingRight", + "padding-left": "paddingLeft", "padding-bottom": "paddingBottom", + "z-index": "zIndex" + }; + + var styles = {}; + for (var styleName in styleNames) + styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0; + if (FBTrace.DBG_INSPECT) + FBTrace.sysout("readBoxStyles ", styles); + return styles; +}; + +this.getBoxFromStyles = function(style, element) +{ + var args = this.readBoxStyles(style); + args.width = element.offsetWidth + - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight); + args.height = element.offsetHeight + - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom); + return args; +}; + +this.getElementCSSSelector = function(element) +{ + var label = element.localName.toLowerCase(); + if (element.id) + label += "#" + element.id; + if (element.hasAttribute("class")) + label += "." + element.getAttribute("class").split(" ")[0]; + + return label; +}; + +this.getURLForStyleSheet= function(styleSheet) +{ + //http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet. For inline style sheets, the value of this attribute is null. + return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.URL); +}; + +this.getDocumentForStyleSheet = function(styleSheet) +{ + while (styleSheet.parentStyleSheet && !styleSheet.ownerNode) + { + styleSheet = styleSheet.parentStyleSheet; + } + if (styleSheet.ownerNode) + return styleSheet.ownerNode.ownerDocument; +}; + +/** + * Retrieves the instance number for a given style sheet. The instance number + * is sheet's index within the set of all other sheets whose URL is the same. + */ +this.getInstanceForStyleSheet = function(styleSheet, ownerDocument) +{ + // System URLs are always unique (or at least we are making this assumption) + if (FBL.isSystemStyleSheet(styleSheet)) + return 0; + + // ownerDocument is an optional hint for performance + if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: " + styleSheet.href + " " + styleSheet.media.mediaText + " " + (styleSheet.ownerNode && FBL.getElementXPath(styleSheet.ownerNode)), ownerDocument); + ownerDocument = ownerDocument || FBL.getDocumentForStyleSheet(styleSheet); + + var ret = 0, + styleSheets = ownerDocument.styleSheets, + href = styleSheet.href; + for (var i = 0; i < styleSheets.length; i++) + { + var curSheet = styleSheets[i]; + if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: compare href " + i + " " + curSheet.href + " " + curSheet.media.mediaText + " " + (curSheet.ownerNode && FBL.getElementXPath(curSheet.ownerNode))); + if (curSheet == styleSheet) + break; + if (curSheet.href == href) + ret++; + } + return ret; +}; + +// ************************************************************************************************ +// HTML and XML Serialization + + +var getElementType = this.getElementType = function(node) +{ + if (isElementXUL(node)) + return 'xul'; + else if (isElementSVG(node)) + return 'svg'; + else if (isElementMathML(node)) + return 'mathml'; + else if (isElementXHTML(node)) + return 'xhtml'; + else if (isElementHTML(node)) + return 'html'; +}; + +var getElementSimpleType = this.getElementSimpleType = function(node) +{ + if (isElementSVG(node)) + return 'svg'; + else if (isElementMathML(node)) + return 'mathml'; + else + return 'html'; +}; + +var isElementHTML = this.isElementHTML = function(node) +{ + return node.nodeName == node.nodeName.toUpperCase(); +}; + +var isElementXHTML = this.isElementXHTML = function(node) +{ + return node.nodeName == node.nodeName.toLowerCase(); +}; + +var isElementMathML = this.isElementMathML = function(node) +{ + return node.namespaceURI == 'http://www.w3.org/1998/Math/MathML'; +}; + +var isElementSVG = this.isElementSVG = function(node) +{ + return node.namespaceURI == 'http://www.w3.org/2000/svg'; +}; + +var isElementXUL = this.isElementXUL = function(node) +{ + return node instanceof XULElement; +}; + +this.isSelfClosing = function(element) +{ + if (isElementSVG(element) || isElementMathML(element)) + return true; + var tag = element.localName.toLowerCase(); + return (this.selfClosingTags.hasOwnProperty(tag)); +}; + +this.getElementHTML = function(element) +{ + var self=this; + function toHTML(elt) + { + if (elt.nodeType == Node.ELEMENT_NODE) + { + if (unwrapObject(elt).firebugIgnore) + return; + + html.push('<', elt.nodeName.toLowerCase()); + + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // Hide attributes set by Firebug + if (attr.localName.indexOf("firebug-") == 0) + continue; + + // MathML + if (attr.localName.indexOf("-moz-math") == 0) + { + // just hide for now + continue; + } + + html.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); + } + + if (elt.firstChild) + { + html.push('>'); + + var pureText=true; + for (var child = element.firstChild; child; child = child.nextSibling) + pureText=pureText && (child.nodeType == Node.TEXT_NODE); + + if (pureText) + html.push(escapeForHtmlEditor(elt.textContent)); + else { + for (var child = elt.firstChild; child; child = child.nextSibling) + toHTML(child); + } + + html.push(''); + } + else if (isElementSVG(elt) || isElementMathML(elt)) + { + html.push('/>'); + } + else if (self.isSelfClosing(elt)) + { + html.push((isElementXHTML(elt))?'/>':'>'); + } + else + { + html.push('>'); + } + } + else if (elt.nodeType == Node.TEXT_NODE) + html.push(escapeForTextNode(elt.textContent)); + else if (elt.nodeType == Node.CDATA_SECTION_NODE) + html.push(''); + else if (elt.nodeType == Node.COMMENT_NODE) + html.push(''); + } + + var html = []; + toHTML(element); + return html.join(""); +}; + +this.getElementXML = function(element) +{ + function toXML(elt) + { + if (elt.nodeType == Node.ELEMENT_NODE) + { + if (unwrapObject(elt).firebugIgnore) + return; + + xml.push('<', elt.nodeName.toLowerCase()); + + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // Hide attributes set by Firebug + if (attr.localName.indexOf("firebug-") == 0) + continue; + + // MathML + if (attr.localName.indexOf("-moz-math") == 0) + { + // just hide for now + continue; + } + + xml.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"'); + } + + if (elt.firstChild) + { + xml.push('>'); + + for (var child = elt.firstChild; child; child = child.nextSibling) + toXML(child); + + xml.push(''); + } + else + xml.push('/>'); + } + else if (elt.nodeType == Node.TEXT_NODE) + xml.push(elt.nodeValue); + else if (elt.nodeType == Node.CDATA_SECTION_NODE) + xml.push(''); + else if (elt.nodeType == Node.COMMENT_NODE) + xml.push(''); + } + + var xml = []; + toXML(element); + return xml.join(""); +}; + + +// ************************************************************************************************ +// CSS classes + +this.hasClass = function(node, name) // className, className, ... +{ + // TODO: xxxpedro when lib.hasClass is called with more than 2 arguments? + // this function can be optimized a lot if assumed 2 arguments only, + // which seems to be what happens 99% of the time + if (arguments.length == 2) + return (' '+node.className+' ').indexOf(' '+name+' ') != -1; + + if (!node || node.nodeType != 1) + return false; + else + { + for (var i=1; i= 0) + { + var size = name.length; + node.className = node.className.substr(0,index-1) + node.className.substr(index+size); + } + } +}; + +this.toggleClass = function(elt, name) +{ + if ((' '+elt.className+' ').indexOf(' '+name+' ') != -1) + ///if (this.hasClass(elt, name)) + this.removeClass(elt, name); + else + this.setClass(elt, name); +}; + +this.setClassTimed = function(elt, name, context, timeout) +{ + if (!timeout) + timeout = 1300; + + if (elt.__setClassTimeout) + context.clearTimeout(elt.__setClassTimeout); + else + this.setClass(elt, name); + + elt.__setClassTimeout = context.setTimeout(function() + { + delete elt.__setClassTimeout; + + FBL.removeClass(elt, name); + }, timeout); +}; + +this.cancelClassTimed = function(elt, name, context) +{ + if (elt.__setClassTimeout) + { + FBL.removeClass(elt, name); + context.clearTimeout(elt.__setClassTimeout); + delete elt.__setClassTimeout; + } +}; + + +// ************************************************************************************************ +// DOM queries + +this.$ = function(id, doc) +{ + if (doc) + return doc.getElementById(id); + else + { + return FBL.Firebug.chrome.document.getElementById(id); + } +}; + +this.$$ = function(selector, doc) +{ + if (doc || !FBL.Firebug.chrome) + return FBL.Firebug.Selector(selector, doc); + else + { + return FBL.Firebug.Selector(selector, FBL.Firebug.chrome.document); + } +}; + +this.getChildByClass = function(node) // ,classname, classname, classname... +{ + for (var i = 1; i < arguments.length; ++i) + { + var className = arguments[i]; + var child = node.firstChild; + node = null; + for (; child; child = child.nextSibling) + { + if (this.hasClass(child, className)) + { + node = child; + break; + } + } + } + + return node; +}; + +this.getAncestorByClass = function(node, className) +{ + for (var parent = node; parent; parent = parent.parentNode) + { + if (this.hasClass(parent, className)) + return parent; + } + + return null; +}; + + +this.getElementsByClass = function(node, className) +{ + var result = []; + + for (var child = node.firstChild; child; child = child.nextSibling) + { + if (this.hasClass(child, className)) + result.push(child); + } + + return result; +}; + +this.getElementByClass = function(node, className) // className, className, ... +{ + var args = cloneArray(arguments); args.splice(0, 1); + for (var child = node.firstChild; child; child = child.nextSibling) + { + var args1 = cloneArray(args); args1.unshift(child); + if (FBL.hasClass.apply(null, args1)) + return child; + else + { + var found = FBL.getElementByClass.apply(null, args1); + if (found) + return found; + } + } + + return null; +}; + +this.isAncestor = function(node, potentialAncestor) +{ + for (var parent = node; parent; parent = parent.parentNode) + { + if (parent == potentialAncestor) + return true; + } + + return false; +}; + +this.getNextElement = function(node) +{ + while (node && node.nodeType != 1) + node = node.nextSibling; + + return node; +}; + +this.getPreviousElement = function(node) +{ + while (node && node.nodeType != 1) + node = node.previousSibling; + + return node; +}; + +this.getBody = function(doc) +{ + if (doc.body) + return doc.body; + + var body = doc.getElementsByTagName("body")[0]; + if (body) + return body; + + return doc.firstChild; // For non-HTML docs +}; + +this.findNextDown = function(node, criteria) +{ + if (!node) + return null; + + for (var child = node.firstChild; child; child = child.nextSibling) + { + if (criteria(child)) + return child; + + var next = this.findNextDown(child, criteria); + if (next) + return next; + } +}; + +this.findPreviousUp = function(node, criteria) +{ + if (!node) + return null; + + for (var child = node.lastChild; child; child = child.previousSibling) + { + var next = this.findPreviousUp(child, criteria); + if (next) + return next; + + if (criteria(child)) + return child; + } +}; + +this.findNext = function(node, criteria, upOnly, maxRoot) +{ + if (!node) + return null; + + if (!upOnly) + { + var next = this.findNextDown(node, criteria); + if (next) + return next; + } + + for (var sib = node.nextSibling; sib; sib = sib.nextSibling) + { + if (criteria(sib)) + return sib; + + var next = this.findNextDown(sib, criteria); + if (next) + return next; + } + + if (node.parentNode && node.parentNode != maxRoot) + return this.findNext(node.parentNode, criteria, true); +}; + +this.findPrevious = function(node, criteria, downOnly, maxRoot) +{ + if (!node) + return null; + + for (var sib = node.previousSibling; sib; sib = sib.previousSibling) + { + var prev = this.findPreviousUp(sib, criteria); + if (prev) + return prev; + + if (criteria(sib)) + return sib; + } + + if (!downOnly) + { + var next = this.findPreviousUp(node, criteria); + if (next) + return next; + } + + if (node.parentNode && node.parentNode != maxRoot) + { + if (criteria(node.parentNode)) + return node.parentNode; + + return this.findPrevious(node.parentNode, criteria, true); + } +}; + +this.getNextByClass = function(root, state) +{ + var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); }; + return this.findNext(root, iter); +}; + +this.getPreviousByClass = function(root, state) +{ + var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); }; + return this.findPrevious(root, iter); +}; + +this.isElement = function(o) +{ + try { + return o && this.instanceOf(o, "Element"); + } + catch (ex) { + return false; + } +}; + + +// ************************************************************************************************ +// DOM Modification + +// TODO: xxxpedro use doc fragments in Context API +var appendFragment = null; + +this.appendInnerHTML = function(element, html, referenceElement) +{ + // if undefined, we must convert it to null otherwise it will throw an error in IE + // when executing element.insertBefore(firstChild, referenceElement) + referenceElement = referenceElement || null; + + var doc = element.ownerDocument; + + // doc.createRange not available in IE + if (doc.createRange) + { + var range = doc.createRange(); // a helper object + range.selectNodeContents(element); // the environment to interpret the html + + var fragment = range.createContextualFragment(html); // parse + var firstChild = fragment.firstChild; + element.insertBefore(fragment, referenceElement); + } + else + { + if (!appendFragment || appendFragment.ownerDocument != doc) + appendFragment = doc.createDocumentFragment(); + + var div = doc.createElement("div"); + div.innerHTML = html; + + var firstChild = div.firstChild; + while (div.firstChild) + appendFragment.appendChild(div.firstChild); + + element.insertBefore(appendFragment, referenceElement); + + div = null; + } + + return firstChild; +}; + + +// ************************************************************************************************ +// DOM creation + +this.createElement = function(tagName, properties) +{ + properties = properties || {}; + var doc = properties.document || FBL.Firebug.chrome.document; + + var element = doc.createElement(tagName); + + for(var name in properties) + { + if (name != "document") + { + element[name] = properties[name]; + } + } + + return element; +}; + +this.createGlobalElement = function(tagName, properties) +{ + properties = properties || {}; + var doc = FBL.Env.browser.document; + + var element = this.NS && doc.createElementNS ? + doc.createElementNS(FBL.NS, tagName) : + doc.createElement(tagName); + + for(var name in properties) + { + var propname = name; + if (FBL.isIE && name == "class") propname = "className"; + + if (name != "document") + { + element.setAttribute(propname, properties[name]); + } + } + + return element; +}; + +//************************************************************************************************ + +this.safeGetWindowLocation = function(window) +{ + try + { + if (window) + { + if (window.closed) + return "(window.closed)"; + if ("location" in window) + return window.location+""; + else + return "(no window.location)"; + } + else + return "(no context.window)"; + } + catch(exc) + { + if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ERRORS) + FBTrace.sysout("TabContext.getWindowLocation failed "+exc, exc); + FBTrace.sysout("TabContext.getWindowLocation failed window:", window); + return "(getWindowLocation: "+exc+")"; + } +}; + +// ************************************************************************************************ +// Events + +this.isLeftClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && // others + this.noKeyModifiers(event); +}; + +this.isMiddleClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 4 : // IE "click" and "dblclick" button model + event.button == 1) && + this.noKeyModifiers(event); +}; + +this.isRightClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 2 : // IE "click" and "dblclick" button model + event.button == 2) && + this.noKeyModifiers(event); +}; + +this.noKeyModifiers = function(event) +{ + return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey; +}; + +this.isControlClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isControl(event); +}; + +this.isShiftClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isShift(event); +}; + +this.isControl = function(event) +{ + return (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey; +}; + +this.isAlt = function(event) +{ + return event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey; +}; + +this.isAltClick = function(event) +{ + return (this.isIE && event.type != "click" && event.type != "dblclick" ? + event.button == 1 : // IE "click" and "dblclick" button model + event.button == 0) && + this.isAlt(event); +}; + +this.isControlShift = function(event) +{ + return (event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey; +}; + +this.isShift = function(event) +{ + return event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey; +}; + +this.addEvent = function(object, name, handler, useCapture) +{ + if (object.addEventListener) + object.addEventListener(name, handler, useCapture); + else + object.attachEvent("on"+name, handler); +}; + +this.removeEvent = function(object, name, handler, useCapture) +{ + try + { + if (object.removeEventListener) + object.removeEventListener(name, handler, useCapture); + else + object.detachEvent("on"+name, handler); + } + catch(e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("FBL.removeEvent error: ", object, name); + } +}; + +this.cancelEvent = function(e, preventDefault) +{ + if (!e) return; + + if (preventDefault) + { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; + } + + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.addGlobalEvent = function(name, handler) +{ + var doc = this.Firebug.browser.document; + var frames = this.Firebug.browser.window.frames; + + this.addEvent(doc, name, handler); + + if (this.Firebug.chrome.type == "popup") + this.addEvent(this.Firebug.chrome.document, name, handler); + + for (var i = 0, frame; frame = frames[i]; i++) + { + try + { + this.addEvent(frame.document, name, handler); + } + catch(E) + { + // Avoid acess denied + } + } +}; + +this.removeGlobalEvent = function(name, handler) +{ + var doc = this.Firebug.browser.document; + var frames = this.Firebug.browser.window.frames; + + this.removeEvent(doc, name, handler); + + if (this.Firebug.chrome.type == "popup") + this.removeEvent(this.Firebug.chrome.document, name, handler); + + for (var i = 0, frame; frame = frames[i]; i++) + { + try + { + this.removeEvent(frame.document, name, handler); + } + catch(E) + { + // Avoid acess denied + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.dispatch = function(listeners, name, args) +{ + if (!listeners) return; + + try + {/**/ + if (typeof listeners.length != "undefined") + { + if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to "+listeners.length+" listeners"); + + for (var i = 0; i < listeners.length; ++i) + { + var listener = listeners[i]; + if ( listener[name] ) + listener[name].apply(listener, args); + } + } + else + { + if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to listeners of an object"); + + for (var prop in listeners) + { + var listener = listeners[prop]; + if ( listener[name] ) + listener[name].apply(listener, args); + } + } + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout(" Exception in lib.dispatch "+ name, exc); + //FBTrace.dumpProperties(" Exception in lib.dispatch listener", listener); + } + } + /**/ +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var disableTextSelectionHandler = function(event) +{ + FBL.cancelEvent(event, true); + + return false; +}; + +this.disableTextSelection = function(e) +{ + if (typeof e.onselectstart != "undefined") // IE + this.addEvent(e, "selectstart", disableTextSelectionHandler); + + else // others + { + e.style.cssText = "user-select: none; -khtml-user-select: none; -moz-user-select: none;"; + + // canceling the event in FF will prevent the menu popups to close when clicking over + // text-disabled elements + if (!this.isFirefox) + this.addEvent(e, "mousedown", disableTextSelectionHandler); + } + + e.style.cursor = "default"; +}; + +this.restoreTextSelection = function(e) +{ + if (typeof e.onselectstart != "undefined") // IE + this.removeEvent(e, "selectstart", disableTextSelectionHandler); + + else // others + { + e.style.cssText = "cursor: default;"; + + // canceling the event in FF will prevent the menu popups to close when clicking over + // text-disabled elements + if (!this.isFirefox) + this.removeEvent(e, "mousedown", disableTextSelectionHandler); + } +}; + +// ************************************************************************************************ +// DOM Events + +var eventTypes = +{ + composition: [ + "composition", + "compositionstart", + "compositionend" ], + contextmenu: [ + "contextmenu" ], + drag: [ + "dragenter", + "dragover", + "dragexit", + "dragdrop", + "draggesture" ], + focus: [ + "focus", + "blur" ], + form: [ + "submit", + "reset", + "change", + "select", + "input" ], + key: [ + "keydown", + "keyup", + "keypress" ], + load: [ + "load", + "beforeunload", + "unload", + "abort", + "error" ], + mouse: [ + "mousedown", + "mouseup", + "click", + "dblclick", + "mouseover", + "mouseout", + "mousemove" ], + mutation: [ + "DOMSubtreeModified", + "DOMNodeInserted", + "DOMNodeRemoved", + "DOMNodeRemovedFromDocument", + "DOMNodeInsertedIntoDocument", + "DOMAttrModified", + "DOMCharacterDataModified" ], + paint: [ + "paint", + "resize", + "scroll" ], + scroll: [ + "overflow", + "underflow", + "overflowchanged" ], + text: [ + "text" ], + ui: [ + "DOMActivate", + "DOMFocusIn", + "DOMFocusOut" ], + xul: [ + "popupshowing", + "popupshown", + "popuphiding", + "popuphidden", + "close", + "command", + "broadcast", + "commandupdate" ] +}; + +this.getEventFamily = function(eventType) +{ + if (!this.families) + { + this.families = {}; + + for (var family in eventTypes) + { + var types = eventTypes[family]; + for (var i = 0; i < types.length; ++i) + this.families[types[i]] = family; + } + } + + return this.families[eventType]; +}; + + +// ************************************************************************************************ +// URLs + +this.getFileName = function(url) +{ + var split = this.splitURLBase(url); + return split.name; +}; + +this.splitURLBase = function(url) +{ + if (this.isDataURL(url)) + return this.splitDataURL(url); + return this.splitURLTrue(url); +}; + +this.splitDataURL = function(url) +{ + var mark = url.indexOf(':', 3); + if (mark != 4) + return false; // the first 5 chars must be 'data:' + + var point = url.indexOf(',', mark+1); + if (point < mark) + return false; // syntax error + + var props = { encodedContent: url.substr(point+1) }; + + var metadataBuffer = url.substr(mark+1, point); + var metadata = metadataBuffer.split(';'); + for (var i = 0; i < metadata.length; i++) + { + var nv = metadata[i].split('='); + if (nv.length == 2) + props[nv[0]] = nv[1]; + } + + // Additional Firebug-specific properties + if (props.hasOwnProperty('fileName')) + { + var caller_URL = decodeURIComponent(props['fileName']); + var caller_split = this.splitURLTrue(caller_URL); + + if (props.hasOwnProperty('baseLineNumber')) // this means it's probably an eval() + { + props['path'] = caller_split.path; + props['line'] = props['baseLineNumber']; + var hint = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, ""); + props['name'] = 'eval->'+hint; + } + else + { + props['name'] = caller_split.name; + props['path'] = caller_split.path; + } + } + else + { + if (!props.hasOwnProperty('path')) + props['path'] = "data:"; + if (!props.hasOwnProperty('name')) + props['name'] = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, ""); + } + + return props; +}; + +this.splitURLTrue = function(url) +{ + var m = reSplitFile.exec(url); + if (!m) + return {name: url, path: url}; + else if (!m[2]) + return {path: m[1], name: m[1]}; + else + return {path: m[1], name: m[2]+m[3]}; +}; + +this.getFileExtension = function(url) +{ + if (!url) + return null; + + // Remove query string from the URL if any. + var queryString = url.indexOf("?"); + if (queryString != -1) + url = url.substr(0, queryString); + + // Now get the file extension. + var lastDot = url.lastIndexOf("."); + return url.substr(lastDot+1); +}; + +this.isSystemURL = function(url) +{ + if (!url) return true; + if (url.length == 0) return true; + if (url[0] == 'h') return false; + if (url.substr(0, 9) == "resource:") + return true; + else if (url.substr(0, 16) == "chrome://firebug") + return true; + else if (url == "XPCSafeJSObjectWrapper.cpp") + return true; + else if (url.substr(0, 6) == "about:") + return true; + else if (url.indexOf("firebug-service.js") != -1) + return true; + else + return false; +}; + +this.isSystemPage = function(win) +{ + try + { + var doc = win.document; + if (!doc) + return false; + + // Detect pages for pretty printed XML + if ((doc.styleSheets.length && doc.styleSheets[0].href + == "chrome://global/content/xml/XMLPrettyPrint.css") + || (doc.styleSheets.length > 1 && doc.styleSheets[1].href + == "chrome://browser/skin/feeds/subscribe.css")) + return true; + + return FBL.isSystemURL(win.location.href); + } + catch (exc) + { + // Sometimes documents just aren't ready to be manipulated here, but don't let that + // gum up the works + ERROR("tabWatcher.isSystemPage document not ready:"+ exc); + return false; + } +}; + +this.isSystemStyleSheet = function(sheet) +{ + var href = sheet && sheet.href; + return href && FBL.isSystemURL(href); +}; + +this.getURIHost = function(uri) +{ + try + { + if (uri) + return uri.host; + else + return ""; + } + catch (exc) + { + return ""; + } +}; + +this.isLocalURL = function(url) +{ + if (url.substr(0, 5) == "file:") + return true; + else if (url.substr(0, 8) == "wyciwyg:") + return true; + else + return false; +}; + +this.isDataURL = function(url) +{ + return (url && url.substr(0,5) == "data:"); +}; + +this.getLocalPath = function(url) +{ + if (this.isLocalURL(url)) + { + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var file = fileHandler.getFileFromURLSpec(url); + return file.path; + } +}; + +this.getURLFromLocalFile = function(file) +{ + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler); + var URL = fileHandler.getURLSpecFromFile(file); + return URL; +}; + +this.getDataURLForContent = function(content, url) +{ + // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10, + var uri = "data:text/html;"; + uri += "fileName="+encodeURIComponent(url)+ ","; + uri += encodeURIComponent(content); + return uri; +}, + +this.getDomain = function(url) +{ + var m = /[^:]+:\/{1,3}([^\/]+)/.exec(url); + return m ? m[1] : ""; +}; + +this.getURLPath = function(url) +{ + var m = /[^:]+:\/{1,3}[^\/]+(\/.*?)$/.exec(url); + return m ? m[1] : ""; +}; + +this.getPrettyDomain = function(url) +{ + var m = /[^:]+:\/{1,3}(www\.)?([^\/]+)/.exec(url); + return m ? m[2] : ""; +}; + +this.absoluteURL = function(url, baseURL) +{ + return this.absoluteURLWithDots(url, baseURL).replace("/./", "/", "g"); +}; + +this.absoluteURLWithDots = function(url, baseURL) +{ + if (url[0] == "?") + return baseURL + url; + + var reURL = /(([^:]+:)\/{1,2}[^\/]*)(.*?)$/; + var m = reURL.exec(url); + if (m) + return url; + + var m = reURL.exec(baseURL); + if (!m) + return ""; + + var head = m[1]; + var tail = m[3]; + if (url.substr(0, 2) == "//") + return m[2] + url; + else if (url[0] == "/") + { + return head + url; + } + else if (tail[tail.length-1] == "/") + return baseURL + url; + else + { + var parts = tail.split("/"); + return head + parts.slice(0, parts.length-1).join("/") + "/" + url; + } +}; + +this.normalizeURL = function(url) // this gets called a lot, any performance improvement welcome +{ + if (!url) + return ""; + // Replace one or more characters that are not forward-slash followed by /.., by space. + if (url.length < 255) // guard against monsters. + { + // Replace one or more characters that are not forward-slash followed by /.., by space. + url = url.replace(/[^\/]+\/\.\.\//, "", "g"); + // Issue 1496, avoid # + url = url.replace(/#.*/,""); + // For some reason, JSDS reports file URLs like "file:/" instead of "file:///", so they + // don't match up with the URLs we get back from the DOM + url = url.replace(/file:\/([^\/])/g, "file:///$1"); + if (url.indexOf('chrome:')==0) + { + var m = reChromeCase.exec(url); // 1 is package name, 2 is path + if (m) + { + url = "chrome://"+m[1].toLowerCase()+"/"+m[2]; + } + } + } + return url; +}; + +this.denormalizeURL = function(url) +{ + return url.replace(/file:\/\/\//g, "file:/"); +}; + +this.parseURLParams = function(url) +{ + var q = url ? url.indexOf("?") : -1; + if (q == -1) + return []; + + var search = url.substr(q+1); + var h = search.lastIndexOf("#"); + if (h != -1) + search = search.substr(0, h); + + if (!search) + return []; + + return this.parseURLEncodedText(search); +}; + +this.parseURLEncodedText = function(text) +{ + var maxValueLength = 25000; + + var params = []; + + // Unescape '+' characters that are used to encode a space. + // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + text = text.replace(/\+/g, " "); + + var args = text.split("&"); + for (var i = 0; i < args.length; ++i) + { + try { + var parts = args[i].split("="); + if (parts.length == 2) + { + if (parts[1].length > maxValueLength) + parts[1] = this.$STR("LargeData"); + + params.push({name: decodeURIComponent(parts[0]), value: decodeURIComponent(parts[1])}); + } + else + params.push({name: decodeURIComponent(parts[0]), value: ""}); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("parseURLEncodedText EXCEPTION ", e); + FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]); + } + } + } + + params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; }); + + return params; +}; + +// TODO: xxxpedro lib. why loops in domplate are requiring array in parameters +// as in response/request headers and get/post parameters in Net module? +this.parseURLParamsArray = function(url) +{ + var q = url ? url.indexOf("?") : -1; + if (q == -1) + return []; + + var search = url.substr(q+1); + var h = search.lastIndexOf("#"); + if (h != -1) + search = search.substr(0, h); + + if (!search) + return []; + + return this.parseURLEncodedTextArray(search); +}; + +this.parseURLEncodedTextArray = function(text) +{ + var maxValueLength = 25000; + + var params = []; + + // Unescape '+' characters that are used to encode a space. + // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt + text = text.replace(/\+/g, " "); + + var args = text.split("&"); + for (var i = 0; i < args.length; ++i) + { + try { + var parts = args[i].split("="); + if (parts.length == 2) + { + if (parts[1].length > maxValueLength) + parts[1] = this.$STR("LargeData"); + + params.push({name: decodeURIComponent(parts[0]), value: [decodeURIComponent(parts[1])]}); + } + else + params.push({name: decodeURIComponent(parts[0]), value: [""]}); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("parseURLEncodedText EXCEPTION ", e); + FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]); + } + } + } + + params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; }); + + return params; +}; + +this.reEncodeURL = function(file, text) +{ + var lines = text.split("\n"); + var params = this.parseURLEncodedText(lines[lines.length-1]); + + var args = []; + for (var i = 0; i < params.length; ++i) + args.push(encodeURIComponent(params[i].name)+"="+encodeURIComponent(params[i].value)); + + var url = file.href; + url += (url.indexOf("?") == -1 ? "?" : "&") + args.join("&"); + + return url; +}; + +this.getResource = function(aURL) +{ + try + { + var channel=ioService.newChannel(aURL,null,null); + var input=channel.open(); + return FBL.readFromStream(input); + } + catch (e) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.getResource FAILS for "+aURL, e); + } +}; + +this.parseJSONString = function(jsonString, originURL) +{ + // See if this is a Prototype style *-secure request. + var regex = new RegExp(/^\/\*-secure-([\s\S]*)\*\/\s*$/); + var matches = regex.exec(jsonString); + + if (matches) + { + jsonString = matches[1]; + + if (jsonString[0] == "\\" && jsonString[1] == "n") + jsonString = jsonString.substr(2); + + if (jsonString[jsonString.length-2] == "\\" && jsonString[jsonString.length-1] == "n") + jsonString = jsonString.substr(0, jsonString.length-2); + } + + if (jsonString.indexOf("&&&START&&&")) + { + regex = new RegExp(/&&&START&&& (.+) &&&END&&&/); + matches = regex.exec(jsonString); + if (matches) + jsonString = matches[1]; + } + + // throw on the extra parentheses + jsonString = "(" + jsonString + ")"; + + ///var s = Components.utils.Sandbox(originURL); + var jsonObject = null; + + try + { + ///jsonObject = Components.utils.evalInSandbox(jsonString, s); + + //jsonObject = Firebug.context.eval(jsonString); + jsonObject = Firebug.context.evaluate(jsonString, null, null, function(){return null;}); + } + catch(e) + { + /*** + if (e.message.indexOf("is not defined")) + { + var parts = e.message.split(" "); + s[parts[0]] = function(str){ return str; }; + try { + jsonObject = Components.utils.evalInSandbox(jsonString, s); + } catch(ex) { + if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e); + return null; + } + } + else + {/**/ + if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) + FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e); + return null; + ///} + } + + return jsonObject; +}; + +// ************************************************************************************************ + +this.objectToString = function(object) +{ + try + { + return object+""; + } + catch (exc) + { + return null; + } +}; + +// ************************************************************************************************ +// Input Caret Position + +this.setSelectionRange = function(input, start, length) +{ + if (input.createTextRange) + { + var range = input.createTextRange(); + range.moveStart("character", start); + range.moveEnd("character", length - input.value.length); + range.select(); + } + else if (input.setSelectionRange) + { + input.setSelectionRange(start, length); + input.focus(); + } +}; + +// ************************************************************************************************ +// Input Selection Start / Caret Position + +this.getInputSelectionStart = function(input) +{ + if (document.selection) + { + var range = input.ownerDocument.selection.createRange(); + var text = range.text; + + //console.log("range", range.text); + + // if there is a selection, find the start position + if (text) + { + return input.value.indexOf(text); + } + // if there is no selection, find the caret position + else + { + range.moveStart("character", -input.value.length); + + return range.text.length; + } + } + else if (typeof input.selectionStart != "undefined") + return input.selectionStart; + + return 0; +}; + +// ************************************************************************************************ +// Opera Tab Fix + +function onOperaTabBlur(e) +{ + if (this.lastKey == 9) + this.focus(); +}; + +function onOperaTabKeyDown(e) +{ + this.lastKey = e.keyCode; +}; + +function onOperaTabFocus(e) +{ + this.lastKey = null; +}; + +this.fixOperaTabKey = function(el) +{ + el.onfocus = onOperaTabFocus; + el.onblur = onOperaTabBlur; + el.onkeydown = onOperaTabKeyDown; +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.Property = function(object, name) +{ + this.object = object; + this.name = name; + + this.getObject = function() + { + return object[name]; + }; +}; + +this.ErrorCopy = function(message) +{ + this.message = message; +}; + +function EventCopy(event) +{ + // Because event objects are destroyed arbitrarily by Gecko, we must make a copy of them to + // represent them long term in the inspector. + for (var name in event) + { + try { + this[name] = event[name]; + } catch (exc) { } + } +} + +this.EventCopy = EventCopy; + + +// ************************************************************************************************ +// Type Checking + +var toString = Object.prototype.toString; +var reFunction = /^\s*function(\s+[\w_$][\w\d_$]*)?\s*\(/; + +this.isArray = function(object) { + return toString.call(object) === '[object Array]'; +}; + +this.isFunction = function(object) { + if (!object) return false; + + try + { + // FIXME: xxxpedro this is failing in IE for the global "external" object + return toString.call(object) === "[object Function]" || + this.isIE && typeof object != "string" && reFunction.test(""+object); + } + catch (E) + { + FBTrace.sysout("Lib.isFunction() failed for ", object); + return false; + } +}; + + +// ************************************************************************************************ +// Instance Checking + +this.instanceOf = function(object, className) +{ + if (!object || typeof object != "object") + return false; + + // Try to use the native instanceof operator. We can only use it when we know + // exactly the window where the object is located at + if (object.ownerDocument) + { + // find the correct window of the object + var win = object.ownerDocument.defaultView || object.ownerDocument.parentWindow; + + // if the class is accessible in the window, uses the native instanceof operator + // if the instanceof evaluates to "true" we can assume it is a instance, but if it + // evaluates to "false" we must continue with the duck type detection below because + // the native object may be extended, thus breaking the instanceof result + // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended + if (className in win && object instanceof win[className]) + return true; + } + // If the object doesn't have the ownerDocument property, we'll try to look at + // the current context's window + else + { + // TODO: xxxpedro context + // Since we're not using yet a Firebug.context, we'll just use the top window + // (browser) as a reference + var win = Firebug.browser.window; + if (className in win) + return object instanceof win[className]; + } + + // get the duck type model from the cache + var cache = instanceCheckMap[className]; + if (!cache) + return false; + + // starts the hacky duck type detection + for(var n in cache) + { + var obj = cache[n]; + var type = typeof obj; + obj = type == "object" ? obj : [obj]; + + for(var name in obj) + { + // avoid problems with extended native objects + // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended + if (!obj.hasOwnProperty(name)) + continue; + + var value = obj[name]; + + if( n == "property" && !(value in object) || + n == "method" && !this.isFunction(object[value]) || + n == "value" && (""+object[name]).toLowerCase() != (""+value).toLowerCase() ) + return false; + } + } + + return true; +}; + +var instanceCheckMap = +{ + // DuckTypeCheck: + // { + // property: ["window", "document"], + // method: "setTimeout", + // value: {nodeType: 1} + // }, + + Window: + { + property: ["window", "document"], + method: "setTimeout" + }, + + Document: + { + property: ["body", "cookie"], + method: "getElementById" + }, + + Node: + { + property: "ownerDocument", + method: "appendChild" + }, + + Element: + { + property: "tagName", + value: {nodeType: 1} + }, + + Location: + { + property: ["hostname", "protocol"], + method: "assign" + }, + + HTMLImageElement: + { + property: "useMap", + value: + { + nodeType: 1, + tagName: "img" + } + }, + + HTMLAnchorElement: + { + property: "hreflang", + value: + { + nodeType: 1, + tagName: "a" + } + }, + + HTMLInputElement: + { + property: "form", + value: + { + nodeType: 1, + tagName: "input" + } + }, + + HTMLButtonElement: + { + // ? + }, + + HTMLFormElement: + { + method: "submit", + value: + { + nodeType: 1, + tagName: "form" + } + }, + + HTMLBodyElement: + { + + }, + + HTMLHtmlElement: + { + + }, + + CSSStyleRule: + { + property: ["selectorText", "style"] + } + +}; + + +// ************************************************************************************************ +// DOM Constants + +/* + +Problems: + + - IE does not have window.Node, window.Element, etc + - for (var name in Node.prototype) return nothing on FF + +*/ + + +var domMemberMap2 = {}; + +var domMemberMap2Sandbox = null; + +var getDomMemberMap2 = function(name) +{ + if (!domMemberMap2Sandbox) + { + var doc = Firebug.chrome.document; + var frame = doc.createElement("iframe"); + + frame.id = "FirebugSandbox"; + frame.style.display = "none"; + frame.src = "about:blank"; + + doc.body.appendChild(frame); + + domMemberMap2Sandbox = frame.window || frame.contentWindow; + } + + var props = []; + + //var object = domMemberMap2Sandbox[name]; + //object = object.prototype || object; + + var object = null; + + if (name == "Window") + object = domMemberMap2Sandbox.window; + + else if (name == "Document") + object = domMemberMap2Sandbox.document; + + else if (name == "HTMLScriptElement") + object = domMemberMap2Sandbox.document.createElement("script"); + + else if (name == "HTMLAnchorElement") + object = domMemberMap2Sandbox.document.createElement("a"); + + else if (name.indexOf("Element") != -1) + { + object = domMemberMap2Sandbox.document.createElement("div"); + } + + if (object) + { + //object = object.prototype || object; + + //props = 'addEventListener,document,location,navigator,window'.split(','); + + for (var n in object) + props.push(n); + } + /**/ + + return props; + return extendArray(props, domMemberMap[name]); +}; + +// xxxpedro experimental get DOM members +this.getDOMMembers = function(object) +{ + if (!domMemberCache) + { + FBL.domMemberCache = domMemberCache = {}; + + for (var name in domMemberMap) + { + var builtins = getDomMemberMap2(name); + var cache = domMemberCache[name] = {}; + + /* + if (name.indexOf("Element") != -1) + { + this.append(cache, this.getDOMMembers("Node")); + this.append(cache, this.getDOMMembers("Element")); + } + /**/ + + for (var i = 0; i < builtins.length; ++i) + cache[builtins[i]] = i; + } + } + + try + { + if (this.instanceOf(object, "Window")) + { return domMemberCache.Window; } + else if (this.instanceOf(object, "Document") || this.instanceOf(object, "XMLDocument")) + { return domMemberCache.Document; } + else if (this.instanceOf(object, "Location")) + { return domMemberCache.Location; } + else if (this.instanceOf(object, "HTMLImageElement")) + { return domMemberCache.HTMLImageElement; } + else if (this.instanceOf(object, "HTMLAnchorElement")) + { return domMemberCache.HTMLAnchorElement; } + else if (this.instanceOf(object, "HTMLInputElement")) + { return domMemberCache.HTMLInputElement; } + else if (this.instanceOf(object, "HTMLButtonElement")) + { return domMemberCache.HTMLButtonElement; } + else if (this.instanceOf(object, "HTMLFormElement")) + { return domMemberCache.HTMLFormElement; } + else if (this.instanceOf(object, "HTMLBodyElement")) + { return domMemberCache.HTMLBodyElement; } + else if (this.instanceOf(object, "HTMLHtmlElement")) + { return domMemberCache.HTMLHtmlElement; } + else if (this.instanceOf(object, "HTMLScriptElement")) + { return domMemberCache.HTMLScriptElement; } + else if (this.instanceOf(object, "HTMLTableElement")) + { return domMemberCache.HTMLTableElement; } + else if (this.instanceOf(object, "HTMLTableRowElement")) + { return domMemberCache.HTMLTableRowElement; } + else if (this.instanceOf(object, "HTMLTableCellElement")) + { return domMemberCache.HTMLTableCellElement; } + else if (this.instanceOf(object, "HTMLIFrameElement")) + { return domMemberCache.HTMLIFrameElement; } + else if (this.instanceOf(object, "SVGSVGElement")) + { return domMemberCache.SVGSVGElement; } + else if (this.instanceOf(object, "SVGElement")) + { return domMemberCache.SVGElement; } + else if (this.instanceOf(object, "Element")) + { return domMemberCache.Element; } + else if (this.instanceOf(object, "Text") || this.instanceOf(object, "CDATASection")) + { return domMemberCache.Text; } + else if (this.instanceOf(object, "Attr")) + { return domMemberCache.Attr; } + else if (this.instanceOf(object, "Node")) + { return domMemberCache.Node; } + else if (this.instanceOf(object, "Event") || this.instanceOf(object, "EventCopy")) + { return domMemberCache.Event; } + else + return {}; + } + catch(E) + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("lib.getDOMMembers FAILED ", E); + + return {}; + } +}; + + +/* +this.getDOMMembers = function(object) +{ + if (!domMemberCache) + { + domMemberCache = {}; + + for (var name in domMemberMap) + { + var builtins = domMemberMap[name]; + var cache = domMemberCache[name] = {}; + + for (var i = 0; i < builtins.length; ++i) + cache[builtins[i]] = i; + } + } + + try + { + if (this.instanceOf(object, "Window")) + { return domMemberCache.Window; } + else if (object instanceof Document || object instanceof XMLDocument) + { return domMemberCache.Document; } + else if (object instanceof Location) + { return domMemberCache.Location; } + else if (object instanceof HTMLImageElement) + { return domMemberCache.HTMLImageElement; } + else if (object instanceof HTMLAnchorElement) + { return domMemberCache.HTMLAnchorElement; } + else if (object instanceof HTMLInputElement) + { return domMemberCache.HTMLInputElement; } + else if (object instanceof HTMLButtonElement) + { return domMemberCache.HTMLButtonElement; } + else if (object instanceof HTMLFormElement) + { return domMemberCache.HTMLFormElement; } + else if (object instanceof HTMLBodyElement) + { return domMemberCache.HTMLBodyElement; } + else if (object instanceof HTMLHtmlElement) + { return domMemberCache.HTMLHtmlElement; } + else if (object instanceof HTMLScriptElement) + { return domMemberCache.HTMLScriptElement; } + else if (object instanceof HTMLTableElement) + { return domMemberCache.HTMLTableElement; } + else if (object instanceof HTMLTableRowElement) + { return domMemberCache.HTMLTableRowElement; } + else if (object instanceof HTMLTableCellElement) + { return domMemberCache.HTMLTableCellElement; } + else if (object instanceof HTMLIFrameElement) + { return domMemberCache.HTMLIFrameElement; } + else if (object instanceof SVGSVGElement) + { return domMemberCache.SVGSVGElement; } + else if (object instanceof SVGElement) + { return domMemberCache.SVGElement; } + else if (object instanceof Element) + { return domMemberCache.Element; } + else if (object instanceof Text || object instanceof CDATASection) + { return domMemberCache.Text; } + else if (object instanceof Attr) + { return domMemberCache.Attr; } + else if (object instanceof Node) + { return domMemberCache.Node; } + else if (object instanceof Event || object instanceof EventCopy) + { return domMemberCache.Event; } + else + return {}; + } + catch(E) + { + return {}; + } +}; +/**/ + +this.isDOMMember = function(object, propName) +{ + var members = this.getDOMMembers(object); + return members && propName in members; +}; + +var domMemberCache = null; +var domMemberMap = {}; + +domMemberMap.Window = +[ + "document", + "frameElement", + + "innerWidth", + "innerHeight", + "outerWidth", + "outerHeight", + "screenX", + "screenY", + "pageXOffset", + "pageYOffset", + "scrollX", + "scrollY", + "scrollMaxX", + "scrollMaxY", + + "status", + "defaultStatus", + + "parent", + "opener", + "top", + "window", + "content", + "self", + + "location", + "history", + "frames", + "navigator", + "screen", + "menubar", + "toolbar", + "locationbar", + "personalbar", + "statusbar", + "directories", + "scrollbars", + "fullScreen", + "netscape", + "java", + "console", + "Components", + "controllers", + "closed", + "crypto", + "pkcs11", + + "name", + "property", + "length", + + "sessionStorage", + "globalStorage", + + "setTimeout", + "setInterval", + "clearTimeout", + "clearInterval", + "addEventListener", + "removeEventListener", + "dispatchEvent", + "getComputedStyle", + "captureEvents", + "releaseEvents", + "routeEvent", + "enableExternalCapture", + "disableExternalCapture", + "moveTo", + "moveBy", + "resizeTo", + "resizeBy", + "scroll", + "scrollTo", + "scrollBy", + "scrollByLines", + "scrollByPages", + "sizeToContent", + "setResizable", + "getSelection", + "open", + "openDialog", + "close", + "alert", + "confirm", + "prompt", + "dump", + "focus", + "blur", + "find", + "back", + "forward", + "home", + "stop", + "print", + "atob", + "btoa", + "updateCommands", + "XPCNativeWrapper", + "GeckoActiveXObject", + "applicationCache" // FF3 +]; + +domMemberMap.Location = +[ + "href", + "protocol", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + + "assign", + "reload", + "replace" +]; + +domMemberMap.Node = +[ + "id", + "className", + + "nodeType", + "tagName", + "nodeName", + "localName", + "prefix", + "namespaceURI", + "nodeValue", + + "ownerDocument", + "parentNode", + "offsetParent", + "nextSibling", + "previousSibling", + "firstChild", + "lastChild", + "childNodes", + "attributes", + + "dir", + "baseURI", + "textContent", + "innerHTML", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "cloneNode", + "appendChild", + "insertBefore", + "replaceChild", + "removeChild", + "compareDocumentPosition", + "hasAttributes", + "hasChildNodes", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "getFeature", + "getUserData", + "setUserData" +]; + +domMemberMap.Document = extendArray(domMemberMap.Node, +[ + "documentElement", + "body", + "title", + "location", + "referrer", + "cookie", + "contentType", + "lastModified", + "characterSet", + "inputEncoding", + "xmlEncoding", + "xmlStandalone", + "xmlVersion", + "strictErrorChecking", + "documentURI", + "URL", + + "defaultView", + "doctype", + "implementation", + "styleSheets", + "images", + "links", + "forms", + "anchors", + "embeds", + "plugins", + "applets", + + "width", + "height", + + "designMode", + "compatMode", + "async", + "preferredStylesheetSet", + + "alinkColor", + "linkColor", + "vlinkColor", + "bgColor", + "fgColor", + "domain", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "captureEvents", + "releaseEvents", + "routeEvent", + "clear", + "open", + "close", + "execCommand", + "execCommandShowHelp", + "getElementsByName", + "getSelection", + "queryCommandEnabled", + "queryCommandIndeterm", + "queryCommandState", + "queryCommandSupported", + "queryCommandText", + "queryCommandValue", + "write", + "writeln", + "adoptNode", + "appendChild", + "removeChild", + "renameNode", + "cloneNode", + "compareDocumentPosition", + "createAttribute", + "createAttributeNS", + "createCDATASection", + "createComment", + "createDocumentFragment", + "createElement", + "createElementNS", + "createEntityReference", + "createEvent", + "createExpression", + "createNSResolver", + "createNodeIterator", + "createProcessingInstruction", + "createRange", + "createTextNode", + "createTreeWalker", + "domConfig", + "evaluate", + "evaluateFIXptr", + "evaluateXPointer", + "getAnonymousElementByAttribute", + "getAnonymousNodes", + "addBinding", + "removeBinding", + "getBindingParent", + "getBoxObjectFor", + "setBoxObjectFor", + "getElementById", + "getElementsByTagName", + "getElementsByTagNameNS", + "hasAttributes", + "hasChildNodes", + "importNode", + "insertBefore", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "load", + "loadBindingDocument", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "normalizeDocument", + "getFeature", + "getUserData", + "setUserData" +]); + +domMemberMap.Element = extendArray(domMemberMap.Node, +[ + "clientWidth", + "clientHeight", + "offsetLeft", + "offsetTop", + "offsetWidth", + "offsetHeight", + "scrollLeft", + "scrollTop", + "scrollWidth", + "scrollHeight", + + "style", + + "tabIndex", + "title", + "lang", + "align", + "spellcheck", + + "addEventListener", + "removeEventListener", + "dispatchEvent", + "focus", + "blur", + "cloneNode", + "appendChild", + "insertBefore", + "replaceChild", + "removeChild", + "compareDocumentPosition", + "getElementsByTagName", + "getElementsByTagNameNS", + "getAttribute", + "getAttributeNS", + "getAttributeNode", + "getAttributeNodeNS", + "setAttribute", + "setAttributeNS", + "setAttributeNode", + "setAttributeNodeNS", + "removeAttribute", + "removeAttributeNS", + "removeAttributeNode", + "hasAttribute", + "hasAttributeNS", + "hasAttributes", + "hasChildNodes", + "lookupNamespaceURI", + "lookupPrefix", + "normalize", + "isDefaultNamespace", + "isEqualNode", + "isSameNode", + "isSupported", + "getFeature", + "getUserData", + "setUserData" +]); + +domMemberMap.SVGElement = extendArray(domMemberMap.Element, +[ + "x", + "y", + "width", + "height", + "rx", + "ry", + "transform", + "href", + + "ownerSVGElement", + "viewportElement", + "farthestViewportElement", + "nearestViewportElement", + + "getBBox", + "getCTM", + "getScreenCTM", + "getTransformToElement", + "getPresentationAttribute", + "preserveAspectRatio" +]); + +domMemberMap.SVGSVGElement = extendArray(domMemberMap.Element, +[ + "x", + "y", + "width", + "height", + "rx", + "ry", + "transform", + + "viewBox", + "viewport", + "currentView", + "useCurrentView", + "pixelUnitToMillimeterX", + "pixelUnitToMillimeterY", + "screenPixelToMillimeterX", + "screenPixelToMillimeterY", + "currentScale", + "currentTranslate", + "zoomAndPan", + + "ownerSVGElement", + "viewportElement", + "farthestViewportElement", + "nearestViewportElement", + "contentScriptType", + "contentStyleType", + + "getBBox", + "getCTM", + "getScreenCTM", + "getTransformToElement", + "getEnclosureList", + "getIntersectionList", + "getViewboxToViewportTransform", + "getPresentationAttribute", + "getElementById", + "checkEnclosure", + "checkIntersection", + "createSVGAngle", + "createSVGLength", + "createSVGMatrix", + "createSVGNumber", + "createSVGPoint", + "createSVGRect", + "createSVGString", + "createSVGTransform", + "createSVGTransformFromMatrix", + "deSelectAll", + "preserveAspectRatio", + "forceRedraw", + "suspendRedraw", + "unsuspendRedraw", + "unsuspendRedrawAll", + "getCurrentTime", + "setCurrentTime", + "animationsPaused", + "pauseAnimations", + "unpauseAnimations" +]); + +domMemberMap.HTMLImageElement = extendArray(domMemberMap.Element, +[ + "src", + "naturalWidth", + "naturalHeight", + "width", + "height", + "x", + "y", + "name", + "alt", + "longDesc", + "lowsrc", + "border", + "complete", + "hspace", + "vspace", + "isMap", + "useMap" +]); + +domMemberMap.HTMLAnchorElement = extendArray(domMemberMap.Element, +[ + "name", + "target", + "accessKey", + "href", + "protocol", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", + "hreflang", + "coords", + "shape", + "text", + "type", + "rel", + "rev", + "charset" +]); + +domMemberMap.HTMLIFrameElement = extendArray(domMemberMap.Element, +[ + "contentDocument", + "contentWindow", + "frameBorder", + "height", + "longDesc", + "marginHeight", + "marginWidth", + "name", + "scrolling", + "src", + "width" +]); + +domMemberMap.HTMLTableElement = extendArray(domMemberMap.Element, +[ + "bgColor", + "border", + "caption", + "cellPadding", + "cellSpacing", + "frame", + "rows", + "rules", + "summary", + "tBodies", + "tFoot", + "tHead", + "width", + + "createCaption", + "createTFoot", + "createTHead", + "deleteCaption", + "deleteRow", + "deleteTFoot", + "deleteTHead", + "insertRow" +]); + +domMemberMap.HTMLTableRowElement = extendArray(domMemberMap.Element, +[ + "bgColor", + "cells", + "ch", + "chOff", + "rowIndex", + "sectionRowIndex", + "vAlign", + + "deleteCell", + "insertCell" +]); + +domMemberMap.HTMLTableCellElement = extendArray(domMemberMap.Element, +[ + "abbr", + "axis", + "bgColor", + "cellIndex", + "ch", + "chOff", + "colSpan", + "headers", + "height", + "noWrap", + "rowSpan", + "scope", + "vAlign", + "width" + +]); + +domMemberMap.HTMLScriptElement = extendArray(domMemberMap.Element, +[ + "src" +]); + +domMemberMap.HTMLButtonElement = extendArray(domMemberMap.Element, +[ + "accessKey", + "disabled", + "form", + "name", + "type", + "value", + + "click" +]); + +domMemberMap.HTMLInputElement = extendArray(domMemberMap.Element, +[ + "type", + "value", + "checked", + "accept", + "accessKey", + "alt", + "controllers", + "defaultChecked", + "defaultValue", + "disabled", + "form", + "maxLength", + "name", + "readOnly", + "selectionEnd", + "selectionStart", + "size", + "src", + "textLength", + "useMap", + + "click", + "select", + "setSelectionRange" +]); + +domMemberMap.HTMLFormElement = extendArray(domMemberMap.Element, +[ + "acceptCharset", + "action", + "author", + "elements", + "encoding", + "enctype", + "entry_id", + "length", + "method", + "name", + "post", + "target", + "text", + "url", + + "reset", + "submit" +]); + +domMemberMap.HTMLBodyElement = extendArray(domMemberMap.Element, +[ + "aLink", + "background", + "bgColor", + "link", + "text", + "vLink" +]); + +domMemberMap.HTMLHtmlElement = extendArray(domMemberMap.Element, +[ + "version" +]); + +domMemberMap.Text = extendArray(domMemberMap.Node, +[ + "data", + "length", + + "appendData", + "deleteData", + "insertData", + "replaceData", + "splitText", + "substringData" +]); + +domMemberMap.Attr = extendArray(domMemberMap.Node, +[ + "name", + "value", + "specified", + "ownerElement" +]); + +domMemberMap.Event = +[ + "type", + "target", + "currentTarget", + "originalTarget", + "explicitOriginalTarget", + "relatedTarget", + "rangeParent", + "rangeOffset", + "view", + + "keyCode", + "charCode", + "screenX", + "screenY", + "clientX", + "clientY", + "layerX", + "layerY", + "pageX", + "pageY", + + "detail", + "button", + "which", + "ctrlKey", + "shiftKey", + "altKey", + "metaKey", + + "eventPhase", + "timeStamp", + "bubbles", + "cancelable", + "cancelBubble", + + "isTrusted", + "isChar", + + "getPreventDefault", + "initEvent", + "initMouseEvent", + "initKeyEvent", + "initUIEvent", + "preventBubble", + "preventCapture", + "preventDefault", + "stopPropagation" +]; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.domConstantMap = +{ + "ELEMENT_NODE": 1, + "ATTRIBUTE_NODE": 1, + "TEXT_NODE": 1, + "CDATA_SECTION_NODE": 1, + "ENTITY_REFERENCE_NODE": 1, + "ENTITY_NODE": 1, + "PROCESSING_INSTRUCTION_NODE": 1, + "COMMENT_NODE": 1, + "DOCUMENT_NODE": 1, + "DOCUMENT_TYPE_NODE": 1, + "DOCUMENT_FRAGMENT_NODE": 1, + "NOTATION_NODE": 1, + + "DOCUMENT_POSITION_DISCONNECTED": 1, + "DOCUMENT_POSITION_PRECEDING": 1, + "DOCUMENT_POSITION_FOLLOWING": 1, + "DOCUMENT_POSITION_CONTAINS": 1, + "DOCUMENT_POSITION_CONTAINED_BY": 1, + "DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC": 1, + + "UNKNOWN_RULE": 1, + "STYLE_RULE": 1, + "CHARSET_RULE": 1, + "IMPORT_RULE": 1, + "MEDIA_RULE": 1, + "FONT_FACE_RULE": 1, + "PAGE_RULE": 1, + + "CAPTURING_PHASE": 1, + "AT_TARGET": 1, + "BUBBLING_PHASE": 1, + + "SCROLL_PAGE_UP": 1, + "SCROLL_PAGE_DOWN": 1, + + "MOUSEUP": 1, + "MOUSEDOWN": 1, + "MOUSEOVER": 1, + "MOUSEOUT": 1, + "MOUSEMOVE": 1, + "MOUSEDRAG": 1, + "CLICK": 1, + "DBLCLICK": 1, + "KEYDOWN": 1, + "KEYUP": 1, + "KEYPRESS": 1, + "DRAGDROP": 1, + "FOCUS": 1, + "BLUR": 1, + "SELECT": 1, + "CHANGE": 1, + "RESET": 1, + "SUBMIT": 1, + "SCROLL": 1, + "LOAD": 1, + "UNLOAD": 1, + "XFER_DONE": 1, + "ABORT": 1, + "ERROR": 1, + "LOCATE": 1, + "MOVE": 1, + "RESIZE": 1, + "FORWARD": 1, + "HELP": 1, + "BACK": 1, + "TEXT": 1, + + "ALT_MASK": 1, + "CONTROL_MASK": 1, + "SHIFT_MASK": 1, + "META_MASK": 1, + + "DOM_VK_TAB": 1, + "DOM_VK_PAGE_UP": 1, + "DOM_VK_PAGE_DOWN": 1, + "DOM_VK_UP": 1, + "DOM_VK_DOWN": 1, + "DOM_VK_LEFT": 1, + "DOM_VK_RIGHT": 1, + "DOM_VK_CANCEL": 1, + "DOM_VK_HELP": 1, + "DOM_VK_BACK_SPACE": 1, + "DOM_VK_CLEAR": 1, + "DOM_VK_RETURN": 1, + "DOM_VK_ENTER": 1, + "DOM_VK_SHIFT": 1, + "DOM_VK_CONTROL": 1, + "DOM_VK_ALT": 1, + "DOM_VK_PAUSE": 1, + "DOM_VK_CAPS_LOCK": 1, + "DOM_VK_ESCAPE": 1, + "DOM_VK_SPACE": 1, + "DOM_VK_END": 1, + "DOM_VK_HOME": 1, + "DOM_VK_PRINTSCREEN": 1, + "DOM_VK_INSERT": 1, + "DOM_VK_DELETE": 1, + "DOM_VK_0": 1, + "DOM_VK_1": 1, + "DOM_VK_2": 1, + "DOM_VK_3": 1, + "DOM_VK_4": 1, + "DOM_VK_5": 1, + "DOM_VK_6": 1, + "DOM_VK_7": 1, + "DOM_VK_8": 1, + "DOM_VK_9": 1, + "DOM_VK_SEMICOLON": 1, + "DOM_VK_EQUALS": 1, + "DOM_VK_A": 1, + "DOM_VK_B": 1, + "DOM_VK_C": 1, + "DOM_VK_D": 1, + "DOM_VK_E": 1, + "DOM_VK_F": 1, + "DOM_VK_G": 1, + "DOM_VK_H": 1, + "DOM_VK_I": 1, + "DOM_VK_J": 1, + "DOM_VK_K": 1, + "DOM_VK_L": 1, + "DOM_VK_M": 1, + "DOM_VK_N": 1, + "DOM_VK_O": 1, + "DOM_VK_P": 1, + "DOM_VK_Q": 1, + "DOM_VK_R": 1, + "DOM_VK_S": 1, + "DOM_VK_T": 1, + "DOM_VK_U": 1, + "DOM_VK_V": 1, + "DOM_VK_W": 1, + "DOM_VK_X": 1, + "DOM_VK_Y": 1, + "DOM_VK_Z": 1, + "DOM_VK_CONTEXT_MENU": 1, + "DOM_VK_NUMPAD0": 1, + "DOM_VK_NUMPAD1": 1, + "DOM_VK_NUMPAD2": 1, + "DOM_VK_NUMPAD3": 1, + "DOM_VK_NUMPAD4": 1, + "DOM_VK_NUMPAD5": 1, + "DOM_VK_NUMPAD6": 1, + "DOM_VK_NUMPAD7": 1, + "DOM_VK_NUMPAD8": 1, + "DOM_VK_NUMPAD9": 1, + "DOM_VK_MULTIPLY": 1, + "DOM_VK_ADD": 1, + "DOM_VK_SEPARATOR": 1, + "DOM_VK_SUBTRACT": 1, + "DOM_VK_DECIMAL": 1, + "DOM_VK_DIVIDE": 1, + "DOM_VK_F1": 1, + "DOM_VK_F2": 1, + "DOM_VK_F3": 1, + "DOM_VK_F4": 1, + "DOM_VK_F5": 1, + "DOM_VK_F6": 1, + "DOM_VK_F7": 1, + "DOM_VK_F8": 1, + "DOM_VK_F9": 1, + "DOM_VK_F10": 1, + "DOM_VK_F11": 1, + "DOM_VK_F12": 1, + "DOM_VK_F13": 1, + "DOM_VK_F14": 1, + "DOM_VK_F15": 1, + "DOM_VK_F16": 1, + "DOM_VK_F17": 1, + "DOM_VK_F18": 1, + "DOM_VK_F19": 1, + "DOM_VK_F20": 1, + "DOM_VK_F21": 1, + "DOM_VK_F22": 1, + "DOM_VK_F23": 1, + "DOM_VK_F24": 1, + "DOM_VK_NUM_LOCK": 1, + "DOM_VK_SCROLL_LOCK": 1, + "DOM_VK_COMMA": 1, + "DOM_VK_PERIOD": 1, + "DOM_VK_SLASH": 1, + "DOM_VK_BACK_QUOTE": 1, + "DOM_VK_OPEN_BRACKET": 1, + "DOM_VK_BACK_SLASH": 1, + "DOM_VK_CLOSE_BRACKET": 1, + "DOM_VK_QUOTE": 1, + "DOM_VK_META": 1, + + "SVG_ZOOMANDPAN_DISABLE": 1, + "SVG_ZOOMANDPAN_MAGNIFY": 1, + "SVG_ZOOMANDPAN_UNKNOWN": 1 +}; + +this.cssInfo = +{ + "background": ["bgRepeat", "bgAttachment", "bgPosition", "color", "systemColor", "none"], + "background-attachment": ["bgAttachment"], + "background-color": ["color", "systemColor"], + "background-image": ["none"], + "background-position": ["bgPosition"], + "background-repeat": ["bgRepeat"], + + "border": ["borderStyle", "thickness", "color", "systemColor", "none"], + "border-top": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-right": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-bottom": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-left": ["borderStyle", "borderCollapse", "color", "systemColor", "none"], + "border-collapse": ["borderCollapse"], + "border-color": ["color", "systemColor"], + "border-top-color": ["color", "systemColor"], + "border-right-color": ["color", "systemColor"], + "border-bottom-color": ["color", "systemColor"], + "border-left-color": ["color", "systemColor"], + "border-spacing": [], + "border-style": ["borderStyle"], + "border-top-style": ["borderStyle"], + "border-right-style": ["borderStyle"], + "border-bottom-style": ["borderStyle"], + "border-left-style": ["borderStyle"], + "border-width": ["thickness"], + "border-top-width": ["thickness"], + "border-right-width": ["thickness"], + "border-bottom-width": ["thickness"], + "border-left-width": ["thickness"], + + "bottom": ["auto"], + "caption-side": ["captionSide"], + "clear": ["clear", "none"], + "clip": ["auto"], + "color": ["color", "systemColor"], + "content": ["content"], + "counter-increment": ["none"], + "counter-reset": ["none"], + "cursor": ["cursor", "none"], + "direction": ["direction"], + "display": ["display", "none"], + "empty-cells": [], + "float": ["float", "none"], + "font": ["fontStyle", "fontVariant", "fontWeight", "fontFamily"], + + "font-family": ["fontFamily"], + "font-size": ["fontSize"], + "font-size-adjust": [], + "font-stretch": [], + "font-style": ["fontStyle"], + "font-variant": ["fontVariant"], + "font-weight": ["fontWeight"], + + "height": ["auto"], + "left": ["auto"], + "letter-spacing": [], + "line-height": [], + + "list-style": ["listStyleType", "listStylePosition", "none"], + "list-style-image": ["none"], + "list-style-position": ["listStylePosition"], + "list-style-type": ["listStyleType", "none"], + + "margin": [], + "margin-top": [], + "margin-right": [], + "margin-bottom": [], + "margin-left": [], + + "marker-offset": ["auto"], + "min-height": ["none"], + "max-height": ["none"], + "min-width": ["none"], + "max-width": ["none"], + + "outline": ["borderStyle", "color", "systemColor", "none"], + "outline-color": ["color", "systemColor"], + "outline-style": ["borderStyle"], + "outline-width": [], + + "overflow": ["overflow", "auto"], + "overflow-x": ["overflow", "auto"], + "overflow-y": ["overflow", "auto"], + + "padding": [], + "padding-top": [], + "padding-right": [], + "padding-bottom": [], + "padding-left": [], + + "position": ["position"], + "quotes": ["none"], + "right": ["auto"], + "table-layout": ["tableLayout", "auto"], + "text-align": ["textAlign"], + "text-decoration": ["textDecoration", "none"], + "text-indent": [], + "text-shadow": [], + "text-transform": ["textTransform", "none"], + "top": ["auto"], + "unicode-bidi": [], + "vertical-align": ["verticalAlign"], + "white-space": ["whiteSpace"], + "width": ["auto"], + "word-spacing": [], + "z-index": [], + + "-moz-appearance": ["mozAppearance"], + "-moz-border-radius": [], + "-moz-border-radius-bottomleft": [], + "-moz-border-radius-bottomright": [], + "-moz-border-radius-topleft": [], + "-moz-border-radius-topright": [], + "-moz-border-top-colors": ["color", "systemColor"], + "-moz-border-right-colors": ["color", "systemColor"], + "-moz-border-bottom-colors": ["color", "systemColor"], + "-moz-border-left-colors": ["color", "systemColor"], + "-moz-box-align": ["mozBoxAlign"], + "-moz-box-direction": ["mozBoxDirection"], + "-moz-box-flex": [], + "-moz-box-ordinal-group": [], + "-moz-box-orient": ["mozBoxOrient"], + "-moz-box-pack": ["mozBoxPack"], + "-moz-box-sizing": ["mozBoxSizing"], + "-moz-opacity": [], + "-moz-user-focus": ["userFocus", "none"], + "-moz-user-input": ["userInput"], + "-moz-user-modify": [], + "-moz-user-select": ["userSelect", "none"], + "-moz-background-clip": [], + "-moz-background-inline-policy": [], + "-moz-background-origin": [], + "-moz-binding": [], + "-moz-column-count": [], + "-moz-column-gap": [], + "-moz-column-width": [], + "-moz-image-region": [] +}; + +this.inheritedStyleNames = +{ + "border-collapse": 1, + "border-spacing": 1, + "border-style": 1, + "caption-side": 1, + "color": 1, + "cursor": 1, + "direction": 1, + "empty-cells": 1, + "font": 1, + "font-family": 1, + "font-size-adjust": 1, + "font-size": 1, + "font-style": 1, + "font-variant": 1, + "font-weight": 1, + "letter-spacing": 1, + "line-height": 1, + "list-style": 1, + "list-style-image": 1, + "list-style-position": 1, + "list-style-type": 1, + "quotes": 1, + "text-align": 1, + "text-decoration": 1, + "text-indent": 1, + "text-shadow": 1, + "text-transform": 1, + "white-space": 1, + "word-spacing": 1 +}; + +this.cssKeywords = +{ + "appearance": + [ + "button", + "button-small", + "checkbox", + "checkbox-container", + "checkbox-small", + "dialog", + "listbox", + "menuitem", + "menulist", + "menulist-button", + "menulist-textfield", + "menupopup", + "progressbar", + "radio", + "radio-container", + "radio-small", + "resizer", + "scrollbar", + "scrollbarbutton-down", + "scrollbarbutton-left", + "scrollbarbutton-right", + "scrollbarbutton-up", + "scrollbartrack-horizontal", + "scrollbartrack-vertical", + "separator", + "statusbar", + "tab", + "tab-left-edge", + "tabpanels", + "textfield", + "toolbar", + "toolbarbutton", + "toolbox", + "tooltip", + "treeheadercell", + "treeheadersortarrow", + "treeitem", + "treetwisty", + "treetwistyopen", + "treeview", + "window" + ], + + "systemColor": + [ + "ActiveBorder", + "ActiveCaption", + "AppWorkspace", + "Background", + "ButtonFace", + "ButtonHighlight", + "ButtonShadow", + "ButtonText", + "CaptionText", + "GrayText", + "Highlight", + "HighlightText", + "InactiveBorder", + "InactiveCaption", + "InactiveCaptionText", + "InfoBackground", + "InfoText", + "Menu", + "MenuText", + "Scrollbar", + "ThreeDDarkShadow", + "ThreeDFace", + "ThreeDHighlight", + "ThreeDLightShadow", + "ThreeDShadow", + "Window", + "WindowFrame", + "WindowText", + "-moz-field", + "-moz-fieldtext", + "-moz-workspace", + "-moz-visitedhyperlinktext", + "-moz-use-text-color" + ], + + "color": + [ + "AliceBlue", + "AntiqueWhite", + "Aqua", + "Aquamarine", + "Azure", + "Beige", + "Bisque", + "Black", + "BlanchedAlmond", + "Blue", + "BlueViolet", + "Brown", + "BurlyWood", + "CadetBlue", + "Chartreuse", + "Chocolate", + "Coral", + "CornflowerBlue", + "Cornsilk", + "Crimson", + "Cyan", + "DarkBlue", + "DarkCyan", + "DarkGoldenRod", + "DarkGray", + "DarkGreen", + "DarkKhaki", + "DarkMagenta", + "DarkOliveGreen", + "DarkOrange", + "DarkOrchid", + "DarkRed", + "DarkSalmon", + "DarkSeaGreen", + "DarkSlateBlue", + "DarkSlateGray", + "DarkTurquoise", + "DarkViolet", + "DeepPink", + "DarkSkyBlue", + "DimGray", + "DodgerBlue", + "Feldspar", + "FireBrick", + "FloralWhite", + "ForestGreen", + "Fuchsia", + "Gainsboro", + "GhostWhite", + "Gold", + "GoldenRod", + "Gray", + "Green", + "GreenYellow", + "HoneyDew", + "HotPink", + "IndianRed", + "Indigo", + "Ivory", + "Khaki", + "Lavender", + "LavenderBlush", + "LawnGreen", + "LemonChiffon", + "LightBlue", + "LightCoral", + "LightCyan", + "LightGoldenRodYellow", + "LightGrey", + "LightGreen", + "LightPink", + "LightSalmon", + "LightSeaGreen", + "LightSkyBlue", + "LightSlateBlue", + "LightSlateGray", + "LightSteelBlue", + "LightYellow", + "Lime", + "LimeGreen", + "Linen", + "Magenta", + "Maroon", + "MediumAquaMarine", + "MediumBlue", + "MediumOrchid", + "MediumPurple", + "MediumSeaGreen", + "MediumSlateBlue", + "MediumSpringGreen", + "MediumTurquoise", + "MediumVioletRed", + "MidnightBlue", + "MintCream", + "MistyRose", + "Moccasin", + "NavajoWhite", + "Navy", + "OldLace", + "Olive", + "OliveDrab", + "Orange", + "OrangeRed", + "Orchid", + "PaleGoldenRod", + "PaleGreen", + "PaleTurquoise", + "PaleVioletRed", + "PapayaWhip", + "PeachPuff", + "Peru", + "Pink", + "Plum", + "PowderBlue", + "Purple", + "Red", + "RosyBrown", + "RoyalBlue", + "SaddleBrown", + "Salmon", + "SandyBrown", + "SeaGreen", + "SeaShell", + "Sienna", + "Silver", + "SkyBlue", + "SlateBlue", + "SlateGray", + "Snow", + "SpringGreen", + "SteelBlue", + "Tan", + "Teal", + "Thistle", + "Tomato", + "Turquoise", + "Violet", + "VioletRed", + "Wheat", + "White", + "WhiteSmoke", + "Yellow", + "YellowGreen", + "transparent", + "invert" + ], + + "auto": + [ + "auto" + ], + + "none": + [ + "none" + ], + + "captionSide": + [ + "top", + "bottom", + "left", + "right" + ], + + "clear": + [ + "left", + "right", + "both" + ], + + "cursor": + [ + "auto", + "cell", + "context-menu", + "crosshair", + "default", + "help", + "pointer", + "progress", + "move", + "e-resize", + "all-scroll", + "ne-resize", + "nw-resize", + "n-resize", + "se-resize", + "sw-resize", + "s-resize", + "w-resize", + "ew-resize", + "ns-resize", + "nesw-resize", + "nwse-resize", + "col-resize", + "row-resize", + "text", + "vertical-text", + "wait", + "alias", + "copy", + "move", + "no-drop", + "not-allowed", + "-moz-alias", + "-moz-cell", + "-moz-copy", + "-moz-grab", + "-moz-grabbing", + "-moz-contextmenu", + "-moz-zoom-in", + "-moz-zoom-out", + "-moz-spinning" + ], + + "direction": + [ + "ltr", + "rtl" + ], + + "bgAttachment": + [ + "scroll", + "fixed" + ], + + "bgPosition": + [ + "top", + "center", + "bottom", + "left", + "right" + ], + + "bgRepeat": + [ + "repeat", + "repeat-x", + "repeat-y", + "no-repeat" + ], + + "borderStyle": + [ + "hidden", + "dotted", + "dashed", + "solid", + "double", + "groove", + "ridge", + "inset", + "outset", + "-moz-bg-inset", + "-moz-bg-outset", + "-moz-bg-solid" + ], + + "borderCollapse": + [ + "collapse", + "separate" + ], + + "overflow": + [ + "visible", + "hidden", + "scroll", + "-moz-scrollbars-horizontal", + "-moz-scrollbars-none", + "-moz-scrollbars-vertical" + ], + + "listStyleType": + [ + "disc", + "circle", + "square", + "decimal", + "decimal-leading-zero", + "lower-roman", + "upper-roman", + "lower-greek", + "lower-alpha", + "lower-latin", + "upper-alpha", + "upper-latin", + "hebrew", + "armenian", + "georgian", + "cjk-ideographic", + "hiragana", + "katakana", + "hiragana-iroha", + "katakana-iroha", + "inherit" + ], + + "listStylePosition": + [ + "inside", + "outside" + ], + + "content": + [ + "open-quote", + "close-quote", + "no-open-quote", + "no-close-quote", + "inherit" + ], + + "fontStyle": + [ + "normal", + "italic", + "oblique", + "inherit" + ], + + "fontVariant": + [ + "normal", + "small-caps", + "inherit" + ], + + "fontWeight": + [ + "normal", + "bold", + "bolder", + "lighter", + "inherit" + ], + + "fontSize": + [ + "xx-small", + "x-small", + "small", + "medium", + "large", + "x-large", + "xx-large", + "smaller", + "larger" + ], + + "fontFamily": + [ + "Arial", + "Comic Sans MS", + "Georgia", + "Tahoma", + "Verdana", + "Times New Roman", + "Trebuchet MS", + "Lucida Grande", + "Helvetica", + "serif", + "sans-serif", + "cursive", + "fantasy", + "monospace", + "caption", + "icon", + "menu", + "message-box", + "small-caption", + "status-bar", + "inherit" + ], + + "display": + [ + "block", + "inline", + "inline-block", + "list-item", + "marker", + "run-in", + "compact", + "table", + "inline-table", + "table-row-group", + "table-column", + "table-column-group", + "table-header-group", + "table-footer-group", + "table-row", + "table-cell", + "table-caption", + "-moz-box", + "-moz-compact", + "-moz-deck", + "-moz-grid", + "-moz-grid-group", + "-moz-grid-line", + "-moz-groupbox", + "-moz-inline-block", + "-moz-inline-box", + "-moz-inline-grid", + "-moz-inline-stack", + "-moz-inline-table", + "-moz-marker", + "-moz-popup", + "-moz-runin", + "-moz-stack" + ], + + "position": + [ + "static", + "relative", + "absolute", + "fixed", + "inherit" + ], + + "float": + [ + "left", + "right" + ], + + "textAlign": + [ + "left", + "right", + "center", + "justify" + ], + + "tableLayout": + [ + "fixed" + ], + + "textDecoration": + [ + "underline", + "overline", + "line-through", + "blink" + ], + + "textTransform": + [ + "capitalize", + "lowercase", + "uppercase", + "inherit" + ], + + "unicodeBidi": + [ + "normal", + "embed", + "bidi-override" + ], + + "whiteSpace": + [ + "normal", + "pre", + "nowrap" + ], + + "verticalAlign": + [ + "baseline", + "sub", + "super", + "top", + "text-top", + "middle", + "bottom", + "text-bottom", + "inherit" + ], + + "thickness": + [ + "thin", + "medium", + "thick" + ], + + "userFocus": + [ + "ignore", + "normal" + ], + + "userInput": + [ + "disabled", + "enabled" + ], + + "userSelect": + [ + "normal" + ], + + "mozBoxSizing": + [ + "content-box", + "padding-box", + "border-box" + ], + + "mozBoxAlign": + [ + "start", + "center", + "end", + "baseline", + "stretch" + ], + + "mozBoxDirection": + [ + "normal", + "reverse" + ], + + "mozBoxOrient": + [ + "horizontal", + "vertical" + ], + + "mozBoxPack": + [ + "start", + "center", + "end" + ] +}; + +this.nonEditableTags = +{ + "HTML": 1, + "HEAD": 1, + "html": 1, + "head": 1 +}; + +this.innerEditableTags = +{ + "BODY": 1, + "body": 1 +}; + +this.selfClosingTags = +{ // End tags for void elements are forbidden http://wiki.whatwg.org/wiki/HTML_vs._XHTML + "meta": 1, + "link": 1, + "area": 1, + "base": 1, + "col": 1, + "input": 1, + "img": 1, + "br": 1, + "hr": 1, + "param":1, + "embed":1 +}; + +var invisibleTags = this.invisibleTags = +{ + "HTML": 1, + "HEAD": 1, + "TITLE": 1, + "META": 1, + "LINK": 1, + "STYLE": 1, + "SCRIPT": 1, + "NOSCRIPT": 1, + "BR": 1, + "PARAM": 1, + "COL": 1, + + "html": 1, + "head": 1, + "title": 1, + "meta": 1, + "link": 1, + "style": 1, + "script": 1, + "noscript": 1, + "br": 1, + "param": 1, + "col": 1 + /* + "window": 1, + "browser": 1, + "frame": 1, + "tabbrowser": 1, + "WINDOW": 1, + "BROWSER": 1, + "FRAME": 1, + "TABBROWSER": 1, + */ +}; + + +if (typeof KeyEvent == "undefined") { + this.KeyEvent = { + DOM_VK_CANCEL: 3, + DOM_VK_HELP: 6, + DOM_VK_BACK_SPACE: 8, + DOM_VK_TAB: 9, + DOM_VK_CLEAR: 12, + DOM_VK_RETURN: 13, + DOM_VK_ENTER: 14, + DOM_VK_SHIFT: 16, + DOM_VK_CONTROL: 17, + DOM_VK_ALT: 18, + DOM_VK_PAUSE: 19, + DOM_VK_CAPS_LOCK: 20, + DOM_VK_ESCAPE: 27, + DOM_VK_SPACE: 32, + DOM_VK_PAGE_UP: 33, + DOM_VK_PAGE_DOWN: 34, + DOM_VK_END: 35, + DOM_VK_HOME: 36, + DOM_VK_LEFT: 37, + DOM_VK_UP: 38, + DOM_VK_RIGHT: 39, + DOM_VK_DOWN: 40, + DOM_VK_PRINTSCREEN: 44, + DOM_VK_INSERT: 45, + DOM_VK_DELETE: 46, + DOM_VK_0: 48, + DOM_VK_1: 49, + DOM_VK_2: 50, + DOM_VK_3: 51, + DOM_VK_4: 52, + DOM_VK_5: 53, + DOM_VK_6: 54, + DOM_VK_7: 55, + DOM_VK_8: 56, + DOM_VK_9: 57, + DOM_VK_SEMICOLON: 59, + DOM_VK_EQUALS: 61, + DOM_VK_A: 65, + DOM_VK_B: 66, + DOM_VK_C: 67, + DOM_VK_D: 68, + DOM_VK_E: 69, + DOM_VK_F: 70, + DOM_VK_G: 71, + DOM_VK_H: 72, + DOM_VK_I: 73, + DOM_VK_J: 74, + DOM_VK_K: 75, + DOM_VK_L: 76, + DOM_VK_M: 77, + DOM_VK_N: 78, + DOM_VK_O: 79, + DOM_VK_P: 80, + DOM_VK_Q: 81, + DOM_VK_R: 82, + DOM_VK_S: 83, + DOM_VK_T: 84, + DOM_VK_U: 85, + DOM_VK_V: 86, + DOM_VK_W: 87, + DOM_VK_X: 88, + DOM_VK_Y: 89, + DOM_VK_Z: 90, + DOM_VK_CONTEXT_MENU: 93, + DOM_VK_NUMPAD0: 96, + DOM_VK_NUMPAD1: 97, + DOM_VK_NUMPAD2: 98, + DOM_VK_NUMPAD3: 99, + DOM_VK_NUMPAD4: 100, + DOM_VK_NUMPAD5: 101, + DOM_VK_NUMPAD6: 102, + DOM_VK_NUMPAD7: 103, + DOM_VK_NUMPAD8: 104, + DOM_VK_NUMPAD9: 105, + DOM_VK_MULTIPLY: 106, + DOM_VK_ADD: 107, + DOM_VK_SEPARATOR: 108, + DOM_VK_SUBTRACT: 109, + DOM_VK_DECIMAL: 110, + DOM_VK_DIVIDE: 111, + DOM_VK_F1: 112, + DOM_VK_F2: 113, + DOM_VK_F3: 114, + DOM_VK_F4: 115, + DOM_VK_F5: 116, + DOM_VK_F6: 117, + DOM_VK_F7: 118, + DOM_VK_F8: 119, + DOM_VK_F9: 120, + DOM_VK_F10: 121, + DOM_VK_F11: 122, + DOM_VK_F12: 123, + DOM_VK_F13: 124, + DOM_VK_F14: 125, + DOM_VK_F15: 126, + DOM_VK_F16: 127, + DOM_VK_F17: 128, + DOM_VK_F18: 129, + DOM_VK_F19: 130, + DOM_VK_F20: 131, + DOM_VK_F21: 132, + DOM_VK_F22: 133, + DOM_VK_F23: 134, + DOM_VK_F24: 135, + DOM_VK_NUM_LOCK: 144, + DOM_VK_SCROLL_LOCK: 145, + DOM_VK_COMMA: 188, + DOM_VK_PERIOD: 190, + DOM_VK_SLASH: 191, + DOM_VK_BACK_QUOTE: 192, + DOM_VK_OPEN_BRACKET: 219, + DOM_VK_BACK_SLASH: 220, + DOM_VK_CLOSE_BRACKET: 221, + DOM_VK_QUOTE: 222, + DOM_VK_META: 224 + }; +} + + +// ************************************************************************************************ +// Ajax + +/** + * @namespace + */ +this.Ajax = +{ + + requests: [], + transport: null, + states: ["Uninitialized","Loading","Loaded","Interactive","Complete"], + + initialize: function() + { + this.transport = FBL.getNativeXHRObject(); + }, + + getXHRObject: function() + { + var xhrObj = false; + try + { + xhrObj = new XMLHttpRequest(); + } + catch(e) + { + var progid = [ + "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", + "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP" + ]; + + for ( var i=0; i < progid.length; ++i ) { + try + { + xhrObj = new ActiveXObject(progid[i]); + } + catch(e) + { + continue; + } + break; + } + } + finally + { + return xhrObj; + } + }, + + + /** + * Create a AJAX request. + * + * @name request + * @param {Object} options request options + * @param {String} options.url URL to be requested + * @param {String} options.type Request type ("get" ou "post"). Default is "get". + * @param {Boolean} options.async Asynchronous flag. Default is "true". + * @param {String} options.dataType Data type ("text", "html", "xml" or "json"). Default is "text". + * @param {String} options.contentType Content-type of the data being sent. Default is "application/x-www-form-urlencoded". + * @param {Function} options.onLoading onLoading callback + * @param {Function} options.onLoaded onLoaded callback + * @param {Function} options.onInteractive onInteractive callback + * @param {Function} options.onComplete onComplete callback + * @param {Function} options.onUpdate onUpdate callback + * @param {Function} options.onSuccess onSuccess callback + * @param {Function} options.onFailure onFailure callback + */ + request: function(options) + { + // process options + var o = FBL.extend( + { + // default values + type: "get", + async: true, + dataType: "text", + contentType: "application/x-www-form-urlencoded" + }, + options || {} + ); + + this.requests.push(o); + + var s = this.getState(); + if (s == "Uninitialized" || s == "Complete" || s == "Loaded") + this.sendRequest(); + }, + + serialize: function(data) + { + var r = [""], rl = 0; + if (data) { + if (typeof data == "string") r[rl++] = data; + + else if (data.innerHTML && data.elements) { + for (var i=0,el,l=(el=data.elements).length; i < l; i++) + if (el[i].name) { + r[rl++] = encodeURIComponent(el[i].name); + r[rl++] = "="; + r[rl++] = encodeURIComponent(el[i].value); + r[rl++] = "&"; + } + + } else + for(var param in data) { + r[rl++] = encodeURIComponent(param); + r[rl++] = "="; + r[rl++] = encodeURIComponent(data[param]); + r[rl++] = "&"; + } + } + return r.join("").replace(/&$/, ""); + }, + + sendRequest: function() + { + var t = FBL.Ajax.transport, r = FBL.Ajax.requests.shift(), data; + + // open XHR object + t.open(r.type, r.url, r.async); + + //setRequestHeaders(); + + // indicates that it is a XHR request to the server + t.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // if data is being sent, sets the appropriate content-type + if (data = FBL.Ajax.serialize(r.data)) + t.setRequestHeader("Content-Type", r.contentType); + + /** @ignore */ + // onreadystatechange handler + t.onreadystatechange = function() + { + FBL.Ajax.onStateChange(r); + }; + + // send the request + t.send(data); + }, + + /** + * Handles the state change + */ + onStateChange: function(options) + { + var fn, o = options, t = this.transport; + var state = this.getState(t); + + if (fn = o["on" + state]) fn(this.getResponse(o), o); + + if (state == "Complete") + { + var success = t.status == 200, response = this.getResponse(o); + + if (fn = o["onUpdate"]) + fn(response, o); + + if (fn = o["on" + (success ? "Success" : "Failure")]) + fn(response, o); + + t.onreadystatechange = FBL.emptyFn; + + if (this.requests.length > 0) + setTimeout(this.sendRequest, 10); + } + }, + + /** + * gets the appropriate response value according the type + */ + getResponse: function(options) + { + var t = this.transport, type = options.dataType; + + if (t.status != 200) return t.statusText; + else if (type == "text") return t.responseText; + else if (type == "html") return t.responseText; + else if (type == "xml") return t.responseXML; + else if (type == "json") return eval("(" + t.responseText + ")"); + }, + + /** + * returns the current state of the XHR object + */ + getState: function() + { + return this.states[this.transport.readyState]; + } + +}; + + +// ************************************************************************************************ +// Cookie, from http://www.quirksmode.org/js/cookies.html + +this.createCookie = function(name,value,days) +{ + if ('cookie' in document) + { + if (days) + { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else + var expires = ""; + + document.cookie = name+"="+value+expires+"; path=/"; + } +}; + +this.readCookie = function (name) +{ + if ('cookie' in document) + { + var nameEQ = name + "="; + var ca = document.cookie.split(';'); + + for(var i=0; i < ca.length; i++) + { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); + } + } + + return null; +}; + +this.removeCookie = function(name) +{ + this.createCookie(name, "", -1); +}; + + +// ************************************************************************************************ +// http://www.mister-pixel.com/#Content__state=is_that_simple +var fixIE6BackgroundImageCache = function(doc) +{ + doc = doc || document; + try + { + doc.execCommand("BackgroundImageCache", false, true); + } + catch(E) + { + + } +}; + +// ************************************************************************************************ +// calculatePixelsPerInch + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; + +var calculatePixelsPerInch = function calculatePixelsPerInch(doc, body) +{ + var inch = FBL.createGlobalElement("div"); + inch.style.cssText = resetStyle + "width:1in; height:1in; position:absolute; top:-1234px; left:-1234px;"; + body.appendChild(inch); + + FBL.pixelsPerInch = { + x: inch.offsetWidth, + y: inch.offsetHeight + }; + + body.removeChild(inch); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.SourceLink = function(url, line, type, object, instance) +{ + this.href = url; + this.instance = instance; + this.line = line; + this.type = type; + this.object = object; +}; + +this.SourceLink.prototype = +{ + toString: function() + { + return this.href; + }, + toJSON: function() // until 3.1... + { + return "{\"href\":\""+this.href+"\", "+ + (this.line?("\"line\":"+this.line+","):"")+ + (this.type?(" \"type\":\""+this.type+"\","):"")+ + "}"; + } + +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +this.SourceText = function(lines, owner) +{ + this.lines = lines; + this.owner = owner; +}; + +this.SourceText.getLineAsHTML = function(lineNo) +{ + return escapeForSourceLine(this.lines[lineNo-1]); +}; + + +// ************************************************************************************************ +}).apply(FBL); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_i18n */ function() { with (FBL) { +// ************************************************************************************************ + +// TODO: xxxpedro localization +var oSTR = +{ + "NoMembersWarning": "There are no properties to show for this object.", + + "EmptyStyleSheet": "There are no rules in this stylesheet.", + "EmptyElementCSS": "This element has no style rules.", + "AccessRestricted": "Access to restricted URI denied.", + + "net.label.Parameters": "Parameters", + "net.label.Source": "Source", + "URLParameters": "Params", + + "EditStyle": "Edit Element Style...", + "NewRule": "New Rule...", + + "NewProp": "New Property...", + "EditProp": 'Edit "%s"', + "DeleteProp": 'Delete "%s"', + "DisableProp": 'Disable "%s"' +}; + +// ************************************************************************************************ + +FBL.$STR = function(name) +{ + return oSTR.hasOwnProperty(name) ? oSTR[name] : name; +}; + +FBL.$STRF = function(name, args) +{ + if (!oSTR.hasOwnProperty(name)) return name; + + var format = oSTR[name]; + var objIndex = 0; + + var parts = parseFormat(format); + var trialIndex = objIndex; + var objects = args; + + for (var i= 0; i < parts.length; i++) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + if (++trialIndex > objects.length) // then too few parameters for format, assume unformatted. + { + format = ""; + objIndex = -1; + parts.length = 0; + break; + } + } + + } + + var result = []; + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + result.push(""+args.shift()); + } + else + result.push(part); + } + + return result.join(""); +}; + +// ************************************************************************************************ + +var parseFormat = function parseFormat(format) +{ + var parts = []; + if (format.length <= 0) + return parts; + + var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/; + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + if (m[0].substr(0, 2) == "%%") + { + parts.push(format.substr(0, m.index)); + parts.push(m[0].substr(1)); + } + else + { + var type = m[8] ? m[8] : m[5]; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + var rep = null; + switch (type) + { + case "s": + rep = FirebugReps.Text; + break; + case "f": + case "i": + case "d": + rep = FirebugReps.Number; + break; + case "o": + rep = null; + break; + } + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({rep: rep, precision: precision, type: ("%" + type)}); + } + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + return parts; +}; + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_firebug */ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Internals + +var modules = []; +var panelTypes = []; +var panelTypeMap = {}; +var reps = []; + +var parentPanelMap = {}; + + +// ************************************************************************************************ +// Firebug + +/** + * @namespace describe Firebug + * @exports FBL.Firebug as Firebug + */ +FBL.Firebug = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + version: "Firebug Lite 1.4.0", + revision: "$Revision$", + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + modules: modules, + panelTypes: panelTypes, + panelTypeMap: panelTypeMap, + reps: reps, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Initialization + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.initialize", "initializing application"); + + Firebug.browser = new Context(Env.browser); + Firebug.context = Firebug.browser; + + Firebug.loadPrefs(); + Firebug.context.persistedState.isOpen = false; + + // Document must be cached before chrome initialization + cacheDocument(); + + if (Firebug.Inspector && Firebug.Inspector.create) + Firebug.Inspector.create(); + + if (FBL.CssAnalyzer && FBL.CssAnalyzer.processAllStyleSheets) + FBL.CssAnalyzer.processAllStyleSheets(Firebug.browser.document); + + FirebugChrome.initialize(); + + dispatch(modules, "initialize", []); + + if (Firebug.disableResourceFetching) + Firebug.Console.logFormatted(["Some Firebug Lite features are not working because " + + "resource fetching is disabled. To enabled it set the Firebug Lite option " + + "\"disableResourceFetching\" to \"false\". More info at " + + "http://getfirebug.com/firebuglite#Options"], + Firebug.context, "warn"); + + if (Env.onLoad) + { + var onLoad = Env.onLoad; + delete Env.onLoad; + + setTimeout(onLoad, 200); + } + }, + + shutdown: function() + { + if (Firebug.saveCookies) + Firebug.savePrefs(); + + if (Firebug.Inspector) + Firebug.Inspector.destroy(); + + dispatch(modules, "shutdown", []); + + var chromeMap = FirebugChrome.chromeMap; + + for (var name in chromeMap) + { + if (chromeMap.hasOwnProperty(name)) + { + try + { + chromeMap[name].destroy(); + } + catch(E) + { + if (FBTrace.DBG_ERRORS) FBTrace.sysout("chrome.destroy() failed to: " + name); + } + } + } + + Firebug.Lite.Cache.Element.clear(); + Firebug.Lite.Cache.StyleSheet.clear(); + + Firebug.browser = null; + Firebug.context = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Registration + + registerModule: function() + { + modules.push.apply(modules, arguments); + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule"); + }, + + registerPanel: function() + { + panelTypes.push.apply(panelTypes, arguments); + + for (var i = 0, panelType; panelType = arguments[i]; ++i) + { + panelTypeMap[panelType.prototype.name] = arguments[i]; + + if (panelType.prototype.parentPanel) + parentPanelMap[panelType.prototype.parentPanel] = 1; + } + + if (FBTrace.DBG_INITIALIZE) + for (var i = 0; i < arguments.length; ++i) + FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name); + }, + + registerRep: function() + { + reps.push.apply(reps, arguments); + }, + + unregisterRep: function() + { + for (var i = 0; i < arguments.length; ++i) + remove(reps, arguments[i]); + }, + + setDefaultReps: function(funcRep, rep) + { + FBL.defaultRep = rep; + FBL.defaultFuncRep = funcRep; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Reps + + getRep: function(object) + { + var type = typeof object; + if (isIE && isFunction(object)) + type = "function"; + + for (var i = 0; i < reps.length; ++i) + { + var rep = reps[i]; + try + { + if (rep.supportsObject(object, type)) + { + if (FBTrace.DBG_DOM) + FBTrace.sysout("getRep type: "+type+" object: "+object, rep); + return rep; + } + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("firebug.getRep FAILS: ", exc.message || exc); + FBTrace.sysout("firebug.getRep reps["+i+"/"+reps.length+"]: Rep="+reps[i].className); + // TODO: xxxpedro add trace to FBTrace logs like in Firebug + //firebug.trace(); + } + } + } + + return (type == 'function') ? defaultFuncRep : defaultRep; + }, + + getRepObject: function(node) + { + var target = null; + for (var child = node; child; child = child.parentNode) + { + if (hasClass(child, "repTarget")) + target = child; + + if (child.repObject) + { + if (!target && hasClass(child, "repIgnore")) + break; + else + return child.repObject; + } + } + }, + + getRepNode: function(node) + { + for (var child = node; child; child = child.parentNode) + { + if (child.repObject) + return child; + } + }, + + getElementByRepObject: function(element, object) + { + for (var child = element.firstChild; child; child = child.nextSibling) + { + if (child.repObject == object) + return child; + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Preferences + + getPref: function(name) + { + return Firebug[name]; + }, + + setPref: function(name, value) + { + Firebug[name] = value; + + Firebug.savePrefs(); + }, + + setPrefs: function(prefs) + { + for (var name in prefs) + { + if (prefs.hasOwnProperty(name)) + Firebug[name] = prefs[name]; + } + + Firebug.savePrefs(); + }, + + restorePrefs: function() + { + var Options = Env.DefaultOptions; + + for (var name in Options) + { + Firebug[name] = Options[name]; + } + }, + + loadPrefs: function() + { + this.restorePrefs(); + + var prefs = Store.get("FirebugLite") || {}; + var options = prefs.options; + var persistedState = prefs.persistedState || FBL.defaultPersistedState; + + for (var name in options) + { + if (options.hasOwnProperty(name)) + Firebug[name] = options[name]; + } + + if (Firebug.context && persistedState) + Firebug.context.persistedState = persistedState; + }, + + savePrefs: function() + { + var prefs = { + options: {} + }; + + var EnvOptions = Env.Options; + var options = prefs.options; + for (var name in EnvOptions) + { + if (EnvOptions.hasOwnProperty(name)) + { + options[name] = Firebug[name]; + } + } + + var persistedState = Firebug.context.persistedState; + if (!persistedState) + { + persistedState = Firebug.context.persistedState = FBL.defaultPersistedState; + } + + prefs.persistedState = persistedState; + + Store.set("FirebugLite", prefs); + }, + + erasePrefs: function() + { + Store.remove("FirebugLite"); + this.restorePrefs(); + } +}; + +Firebug.restorePrefs(); + +// xxxpedro should we remove this? +window.Firebug = FBL.Firebug; + +if (!Env.Options.enablePersistent || + Env.Options.enablePersistent && Env.isChromeContext || + Env.isDebugMode) + Env.browser.window.Firebug = FBL.Firebug; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Other methods + +FBL.cacheDocument = function cacheDocument() +{ + var ElementCache = Firebug.Lite.Cache.Element; + var els = Firebug.browser.document.getElementsByTagName("*"); + for (var i=0, l=els.length, el; iFirebug.registerModule method. There is always one instance of a module object + * per browser window. + * @extends Firebug.Listener + */ +Firebug.Module = extend(new Firebug.Listener(), +/** @extend Firebug.Module */ +{ + /** + * Called when the window is opened. + */ + initialize: function() + { + }, + + /** + * Called when the window is closed. + */ + shutdown: function() + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Called when a new context is created but before the page is loaded. + */ + initContext: function(context) + { + }, + + /** + * Called after a context is detached to a separate window; + */ + reattachContext: function(browser, context) + { + }, + + /** + * Called when a context is destroyed. Module may store info on persistedState for reloaded pages. + */ + destroyContext: function(context, persistedState) + { + }, + + // Called when a FF tab is create or activated (user changes FF tab) + // Called after context is created or with context == null (to abort?) + showContext: function(browser, context) + { + }, + + /** + * Called after a context's page gets DOMContentLoaded + */ + loadedContext: function(context) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + showPanel: function(browser, panel) + { + }, + + showSidePanel: function(browser, panel) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + updateOption: function(name, value) + { + }, + + getObjectByURL: function(context, url) + { + } +}); + +// ************************************************************************************************ +// Panel + +/** + * @panel Base class for all panels. Every derived panel must define a constructor and + * register with "Firebug.registerPanel" method. An instance of the panel + * object is created by the framework for each browser tab where Firebug is activated. + */ +Firebug.Panel = +{ + name: "HelloWorld", + title: "Hello World!", + + parentPanel: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + options: { + hasCommandLine: false, + hasStatusBar: false, + hasToolButtons: false, + + // Pre-rendered panels are those included in the skin file (firebug.html) + isPreRendered: false, + innerHTMLSync: false + + /* + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // To be used by external extensions + panelHTML: "", + panelCSS: "", + + toolButtonsHTML: "" + /**/ + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + tabNode: null, + panelNode: null, + sidePanelNode: null, + statusBarNode: null, + toolButtonsNode: null, + + panelBarNode: null, + + sidePanelBarBoxNode: null, + sidePanelBarNode: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + sidePanelBar: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + searchable: false, + editable: true, + order: 2147483647, + statusSeparator: "<", + + create: function(context, doc) + { + this.hasSidePanel = parentPanelMap.hasOwnProperty(this.name); + + this.panelBarNode = $("fbPanelBar1"); + this.sidePanelBarBoxNode = $("fbPanelBar2"); + + if (this.hasSidePanel) + { + this.sidePanelBar = extend({}, PanelBar); + this.sidePanelBar.create(this); + } + + var options = this.options = extend(Firebug.Panel.options, this.options); + var panelId = "fb" + this.name; + + if (options.isPreRendered) + { + this.panelNode = $(panelId); + + this.tabNode = $(panelId + "Tab"); + this.tabNode.style.display = "block"; + + if (options.hasToolButtons) + { + this.toolButtonsNode = $(panelId + "Buttons"); + } + + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + this.statusBarNode = $(panelId + "StatusBar"); + } + } + else + { + var containerSufix = this.parentPanel ? "2" : "1"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Create Panel + var panelNode = this.panelNode = createElement("div", { + id: panelId, + className: "fbPanel" + }); + + $("fbPanel" + containerSufix).appendChild(panelNode); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Create Panel Tab + var tabHTML = '' + + this.title + ''; + + var tabNode = this.tabNode = createElement("a", { + id: panelId + "Tab", + className: "fbTab fbHover", + innerHTML: tabHTML + }); + + if (isIE6) + { + tabNode.href = "javascript:void(0)"; + } + + var panelBarNode = this.parentPanel ? + Firebug.chrome.getPanel(this.parentPanel).sidePanelBarNode : + this.panelBarNode; + + panelBarNode.appendChild(tabNode); + tabNode.style.display = "block"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create ToolButtons + if (options.hasToolButtons) + { + this.toolButtonsNode = createElement("span", { + id: panelId + "Buttons", + className: "fbToolbarButtons" + }); + + $("fbToolbarButtons").appendChild(this.toolButtonsNode); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create StatusBar + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + + this.statusBarNode = createElement("span", { + id: panelId + "StatusBar", + className: "fbToolbarButtons fbStatusBar" + }); + + this.statusBarBox.appendChild(this.statusBarNode); + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create SidePanel + } + + this.containerNode = this.panelNode.parentNode; + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.create", this.name); + + // xxxpedro contextMenu + this.onContextMenu = bind(this.onContextMenu, this); + + /* + this.context = context; + this.document = doc; + + this.panelNode = doc.createElement("div"); + this.panelNode.ownerPanel = this; + + setClass(this.panelNode, "panelNode panelNode-"+this.name+" contextUID="+context.uid); + doc.body.appendChild(this.panelNode); + + if (FBTrace.DBG_INITIALIZE) + FBTrace.sysout("firebug.initialize panelNode for "+this.name+"\n"); + + this.initializeNode(this.panelNode); + /**/ + }, + + destroy: function(state) // Panel may store info on state + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.destroy", this.name); + + if (this.hasSidePanel) + { + this.sidePanelBar.destroy(); + this.sidePanelBar = null; + } + + this.options = null; + this.name = null; + this.parentPanel = null; + + this.tabNode = null; + this.panelNode = null; + this.containerNode = null; + + this.toolButtonsNode = null; + this.statusBarBox = null; + this.statusBarNode = null; + + //if (this.panelNode) + // delete this.panelNode.ownerPanel; + + //this.destroyNode(); + }, + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.initialize", this.name); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (this.hasSidePanel) + { + this.sidePanelBar.initialize(); + } + + var options = this.options = extend(Firebug.Panel.options, this.options); + var panelId = "fb" + this.name; + + this.panelNode = $(panelId); + + this.tabNode = $(panelId + "Tab"); + this.tabNode.style.display = "block"; + + if (options.hasStatusBar) + { + this.statusBarBox = $("fbStatusBarBox"); + this.statusBarNode = $(panelId + "StatusBar"); + } + + if (options.hasToolButtons) + { + this.toolButtonsNode = $(panelId + "Buttons"); + } + + this.containerNode = this.panelNode.parentNode; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // restore persistent state + this.containerNode.scrollTop = this.lastScrollTop; + + // xxxpedro contextMenu + addEvent(this.containerNode, "contextmenu", this.onContextMenu); + + + /// TODO: xxxpedro infoTip Hack + Firebug.chrome.currentPanel = + Firebug.chrome.selectedPanel && Firebug.chrome.selectedPanel.sidePanelBar ? + Firebug.chrome.selectedPanel.sidePanelBar.selectedPanel : + Firebug.chrome.selectedPanel; + + Firebug.showInfoTips = true; + if (Firebug.InfoTip) + Firebug.InfoTip.initializeBrowser(Firebug.chrome); + }, + + shutdown: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.shutdown", this.name); + + /// TODO: xxxpedro infoTip Hack + if (Firebug.InfoTip) + Firebug.InfoTip.uninitializeBrowser(Firebug.chrome); + + if (Firebug.chrome.largeCommandLineVisible) + Firebug.chrome.hideLargeCommandLine(); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (this.hasSidePanel) + { + // TODO: xxxpedro firebug1.3a6 + // new PanelBar mechanism will need to call shutdown to hide the panels (so it + // doesn't appears in other panel's sidePanelBar. Therefore, we need to implement + // a "remember selected panel" feature in the sidePanelBar + //this.sidePanelBar.shutdown(); + } + + // store persistent state + this.lastScrollTop = this.containerNode.scrollTop; + + // xxxpedro contextMenu + removeEvent(this.containerNode, "contextmenu", this.onContextMenu); + }, + + detach: function(oldChrome, newChrome) + { + if (oldChrome && oldChrome.selectedPanel && oldChrome.selectedPanel.name == this.name) + this.lastScrollTop = oldChrome.selectedPanel.containerNode.scrollTop; + }, + + reattach: function(doc) + { + if (this.options.innerHTMLSync) + this.synchronizeUI(); + }, + + synchronizeUI: function() + { + this.containerNode.scrollTop = this.lastScrollTop || 0; + }, + + show: function(state) + { + var options = this.options; + + if (options.hasStatusBar) + { + this.statusBarBox.style.display = "inline"; + this.statusBarNode.style.display = "inline"; + } + + if (options.hasToolButtons) + { + this.toolButtonsNode.style.display = "inline"; + } + + this.panelNode.style.display = "block"; + + this.visible = true; + + if (!this.parentPanel) + Firebug.chrome.layout(this); + }, + + hide: function(state) + { + var options = this.options; + + if (options.hasStatusBar) + { + this.statusBarBox.style.display = "none"; + this.statusBarNode.style.display = "none"; + } + + if (options.hasToolButtons) + { + this.toolButtonsNode.style.display = "none"; + } + + this.panelNode.style.display = "none"; + + this.visible = false; + }, + + watchWindow: function(win) + { + }, + + unwatchWindow: function(win) + { + }, + + updateOption: function(name, value) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Toolbar helpers + */ + showToolbarButtons: function(buttonsId, show) + { + try + { + if (!this.context.browser) // XXXjjb this is bug. Somehow the panel context is not FirebugContext. + { + if (FBTrace.DBG_ERRORS) + FBTrace.sysout("firebug.Panel showToolbarButtons this.context has no browser, this:", this); + + return; + } + var buttons = this.context.browser.chrome.$(buttonsId); + if (buttons) + collapse(buttons, show ? "false" : "true"); + } + catch (exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.dumpProperties("firebug.Panel showToolbarButtons FAILS", exc); + if (!this.context.browser)FBTrace.dumpStack("firebug.Panel showToolbarButtons no browser"); + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /** + * Returns a number indicating the view's ability to inspect the object. + * + * Zero means not supported, and higher numbers indicate specificity. + */ + supportsObject: function(object) + { + return 0; + }, + + hasObject: function(object) // beyond type testing, is this object selectable? + { + return false; + }, + + select: function(object, forceUpdate) + { + if (!object) + object = this.getDefaultSelection(this.context); + + if(FBTrace.DBG_PANELS) + FBTrace.sysout("firebug.select "+this.name+" forceUpdate: "+forceUpdate+" "+object+((object==this.selection)?"==":"!=")+this.selection); + + if (forceUpdate || object != this.selection) + { + this.selection = object; + this.updateSelection(object); + + // TODO: xxxpedro + // XXXjoe This is kind of cheating, but, feh. + //Firebug.chrome.onPanelSelect(object, this); + //if (uiListeners.length > 0) + // dispatch(uiListeners, "onPanelSelect", [object, this]); // TODO: make Firebug.chrome a uiListener + } + }, + + updateSelection: function(object) + { + }, + + markChange: function(skipSelf) + { + if (this.dependents) + { + if (skipSelf) + { + for (var i = 0; i < this.dependents.length; ++i) + { + var panelName = this.dependents[i]; + if (panelName != this.name) + this.context.invalidatePanels(panelName); + } + } + else + this.context.invalidatePanels.apply(this.context, this.dependents); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + startInspecting: function() + { + }, + + stopInspecting: function(object, cancelled) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + search: function(text, reverse) + { + }, + + /** + * Retrieves the search options that this modules supports. + * This is used by the search UI to present the proper options. + */ + getSearchOptionsMenuItems: function() + { + return [ + Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive") + ]; + }, + + /** + * Navigates to the next document whose match parameter returns true. + */ + navigateToNextDocument: function(match, reverse) + { + // This is an approximation of the UI that is displayed by the location + // selector. This should be close enough, although it may be better + // to simply generate the sorted list within the module, rather than + // sorting within the UI. + var self = this; + function compare(a, b) { + var locA = self.getObjectDescription(a); + var locB = self.getObjectDescription(b); + if(locA.path > locB.path) + return 1; + if(locA.path < locB.path) + return -1; + if(locA.name > locB.name) + return 1; + if(locA.name < locB.name) + return -1; + return 0; + } + var allLocs = this.getLocationList().sort(compare); + for (var curPos = 0; curPos < allLocs.length && allLocs[curPos] != this.location; curPos++); + + function transformIndex(index) { + if (reverse) { + // For the reverse case we need to implement wrap around. + var intermediate = curPos - index - 1; + return (intermediate < 0 ? allLocs.length : 0) + intermediate; + } else { + return (curPos + index + 1) % allLocs.length; + } + }; + + for (var next = 0; next < allLocs.length - 1; next++) + { + var object = allLocs[transformIndex(next)]; + + if (match(object)) + { + this.navigate(object); + return object; + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // Called when "Options" clicked. Return array of + // {label: 'name', nol10n: true, type: "checkbox", checked: , command:function to set } + getOptionsMenuItems: function() + { + return null; + }, + + /* + * Called by chrome.onContextMenu to build the context menu when this panel has focus. + * See also FirebugRep for a similar function also called by onContextMenu + * Extensions may monkey patch and chain off this call + * @param object: the 'realObject', a model value, eg a DOM property + * @param target: the HTML element clicked on. + * @return an array of menu items. + */ + getContextMenuItems: function(object, target) + { + return []; + }, + + getBreakOnMenuItems: function() + { + return []; + }, + + getEditor: function(target, value) + { + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getDefaultSelection: function() + { + return null; + }, + + browseObject: function(object) + { + }, + + getPopupObject: function(target) + { + return Firebug.getRepObject(target); + }, + + getTooltipObject: function(target) + { + return Firebug.getRepObject(target); + }, + + showInfoTip: function(infoTip, x, y) + { + + }, + + getObjectPath: function(object) + { + return null; + }, + + // An array of objects that can be passed to getObjectLocation. + // The list of things a panel can show, eg sourceFiles. + // Only shown if panel.location defined and supportsObject true + getLocationList: function() + { + return null; + }, + + getDefaultLocation: function() + { + return null; + }, + + getObjectLocation: function(object) + { + return ""; + }, + + // Text for the location list menu eg script panel source file list + // return.path: group/category label, return.name: item label + getObjectDescription: function(object) + { + var url = this.getObjectLocation(object); + return FBL.splitURLBase(url); + }, + + /* + * UI signal that a tab needs attention, eg Script panel is currently stopped on a breakpoint + * @param: show boolean, true turns on. + */ + highlight: function(show) + { + var tab = this.getTab(); + if (!tab) + return; + + if (show) + tab.setAttribute("highlight", "true"); + else + tab.removeAttribute("highlight"); + }, + + getTab: function() + { + var chrome = Firebug.chrome; + + var tab = chrome.$("fbPanelBar2").getTab(this.name); + if (!tab) + tab = chrome.$("fbPanelBar1").getTab(this.name); + return tab; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Support for Break On Next + + /** + * Called by the framework when the user clicks on the Break On Next button. + * @param {Boolean} armed Set to true if the Break On Next feature is + * to be armed for action and set to false if the Break On Next should be disarmed. + * If 'armed' is true, then the next call to shouldBreakOnNext should be |true|. + */ + breakOnNext: function(armed) + { + }, + + /** + * Called when a panel is selected/displayed. The method should return true + * if the Break On Next feature is currently armed for this panel. + */ + shouldBreakOnNext: function() + { + return false; + }, + + /** + * Returns labels for Break On Next tooltip (one for enabled and one for disabled state). + * @param {Boolean} enabled Set to true if the Break On Next feature is + * currently activated for this panel. + */ + getBreakOnNextTooltip: function(enabled) + { + return null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // xxxpedro contextMenu + onContextMenu: function(event) + { + if (!this.getContextMenuItems) + return; + + cancelEvent(event, true); + + var target = event.target || event.srcElement; + + var menu = this.getContextMenuItems(this.selection, target); + if (!menu) + return; + + var contextMenu = new Menu( + { + id: "fbPanelContextMenu", + + items: menu + }); + + contextMenu.show(event.clientX, event.clientY); + + return true; + + /* + // TODO: xxxpedro move code to somewhere. code to get cross-browser + // window to screen coordinates + var box = Firebug.browser.getElementPosition(Firebug.chrome.node); + + var screenY = 0; + + // Firefox + if (typeof window.mozInnerScreenY != "undefined") + { + screenY = window.mozInnerScreenY; + } + // Chrome + else if (typeof window.innerHeight != "undefined") + { + screenY = window.outerHeight - window.innerHeight; + } + // IE + else if (typeof window.screenTop != "undefined") + { + screenY = window.screenTop; + } + + contextMenu.show(event.screenX-box.left, event.screenY-screenY-box.top); + /**/ + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * MeasureBox + * To get pixels size.width and size.height: + *
  • this.startMeasuring(view);
  • + *
  • var size = this.measureText(lineNoCharsSpacer);
  • + *
  • this.stopMeasuring();
  • + *
+ * + * @namespace + */ +Firebug.MeasureBox = +{ + startMeasuring: function(target) + { + if (!this.measureBox) + { + this.measureBox = target.ownerDocument.createElement("span"); + this.measureBox.className = "measureBox"; + } + + copyTextStyles(target, this.measureBox); + target.ownerDocument.body.appendChild(this.measureBox); + }, + + getMeasuringElement: function() + { + return this.measureBox; + }, + + measureText: function(value) + { + this.measureBox.innerHTML = value ? escapeForSourceLine(value) : "m"; + return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1}; + }, + + measureInputText: function(value) + { + value = value ? escapeForTextNode(value) : "m"; + if (!Firebug.showTextNodesWithWhitespace) + value = value.replace(/\t/g,'mmmmmm').replace(/\ /g,'m'); + this.measureBox.innerHTML = value; + return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1}; + }, + + getBox: function(target) + { + var style = this.measureBox.ownerDocument.defaultView.getComputedStyle(this.measureBox, ""); + var box = getBoxFromStyles(style, this.measureBox); + return box; + }, + + stopMeasuring: function() + { + this.measureBox.parentNode.removeChild(this.measureBox); + } +}; + + +// ************************************************************************************************ +if (FBL.domplate) Firebug.Rep = domplate( +{ + className: "", + inspectable: true, + + supportsObject: function(object, type) + { + return false; + }, + + inspectObject: function(object, context) + { + Firebug.chrome.select(object); + }, + + browseObject: function(object, context) + { + }, + + persistObject: function(object, context) + { + }, + + getRealObject: function(object, context) + { + return object; + }, + + getTitle: function(object) + { + var label = safeToString(object); + + var re = /\[object (.*?)\]/; + var m = re.exec(label); + + ///return m ? m[1] : label; + + // if the label is in the "[object TYPE]" format return its type + if (m) + { + return m[1]; + } + // if it is IE we need to handle some special cases + else if ( + // safeToString() fails to recognize some objects in IE + isIE && + // safeToString() returns "[object]" for some objects like window.Image + (label == "[object]" || + // safeToString() returns undefined for some objects like window.clientInformation + typeof object == "object" && typeof label == "undefined") + ) + { + return "Object"; + } + else + { + return label; + } + }, + + getTooltip: function(object) + { + return null; + }, + + getContextMenuItems: function(object, target, context) + { + return []; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Convenience for domplates + + STR: function(name) + { + return $STR(name); + }, + + cropString: function(text) + { + return cropString(text); + }, + + cropMultipleLines: function(text, limit) + { + return cropMultipleLines(text, limit); + }, + + toLowerCase: function(text) + { + return text ? text.toLowerCase() : text; + }, + + plural: function(n) + { + return n == 1 ? "" : "s"; + } +}); + +// ************************************************************************************************ + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /** @scope s_gui */ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Controller + +/**@namespace*/ +FBL.Controller = { + + controllers: null, + controllerContext: null, + + initialize: function(context) + { + this.controllers = []; + this.controllerContext = context || Firebug.chrome; + }, + + shutdown: function() + { + this.removeControllers(); + + //this.controllers = null; + //this.controllerContext = null; + }, + + addController: function() + { + for (var i=0, arg; arg=arguments[i]; i++) + { + // If the first argument is a string, make a selector query + // within the controller node context + if (typeof arg[0] == "string") + { + arg[0] = $$(arg[0], this.controllerContext); + } + + // bind the handler to the proper context + var handler = arg[2]; + arg[2] = bind(handler, this); + // save the original handler as an extra-argument, so we can + // look for it later, when removing a particular controller + arg[3] = handler; + + this.controllers.push(arg); + addEvent.apply(this, arg); + } + }, + + removeController: function() + { + for (var i=0, arg; arg=arguments[i]; i++) + { + for (var j=0, c; c=this.controllers[j]; j++) + { + if (arg[0] == c[0] && arg[1] == c[1] && arg[2] == c[3]) + removeEvent.apply(this, c); + } + } + }, + + removeControllers: function() + { + for (var i=0, c; c=this.controllers[i]; i++) + { + removeEvent.apply(this, c); + } + } +}; + + +// ************************************************************************************************ +// PanelBar + +/**@namespace*/ +FBL.PanelBar = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + panelMap: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + selectedPanel: null, + parentPanelName: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function(ownerPanel) + { + this.panelMap = {}; + this.ownerPanel = ownerPanel; + + if (ownerPanel) + { + ownerPanel.sidePanelBarNode = createElement("span"); + ownerPanel.sidePanelBarNode.style.display = "none"; + ownerPanel.sidePanelBarBoxNode.appendChild(ownerPanel.sidePanelBarNode); + } + + var panels = Firebug.panelTypes; + for (var i=0, p; p=panels[i]; i++) + { + if ( // normal Panel of the Chrome's PanelBar + !ownerPanel && !p.prototype.parentPanel || + // Child Panel of the current Panel's SidePanelBar + ownerPanel && p.prototype.parentPanel && + ownerPanel.name == p.prototype.parentPanel) + { + this.addPanel(p.prototype.name); + } + } + }, + + destroy: function() + { + PanelBar.shutdown.call(this); + + for (var name in this.panelMap) + { + this.removePanel(name); + + var panel = this.panelMap[name]; + panel.destroy(); + + this.panelMap[name] = null; + delete this.panelMap[name]; + } + + this.panelMap = null; + this.ownerPanel = null; + }, + + initialize: function() + { + if (this.ownerPanel) + this.ownerPanel.sidePanelBarNode.style.display = "inline"; + + for(var name in this.panelMap) + { + (function(self, name){ + + // tab click handler + var onTabClick = function onTabClick() + { + self.selectPanel(name); + return false; + }; + + Firebug.chrome.addController([self.panelMap[name].tabNode, "mousedown", onTabClick]); + + })(this, name); + } + }, + + shutdown: function() + { + var selectedPanel = this.selectedPanel; + + if (selectedPanel) + { + removeClass(selectedPanel.tabNode, "fbSelectedTab"); + selectedPanel.hide(); + selectedPanel.shutdown(); + } + + if (this.ownerPanel) + this.ownerPanel.sidePanelBarNode.style.display = "none"; + + this.selectedPanel = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + addPanel: function(panelName, parentPanel) + { + var PanelType = Firebug.panelTypeMap[panelName]; + var panel = this.panelMap[panelName] = new PanelType(); + + panel.create(); + }, + + removePanel: function(panelName) + { + var panel = this.panelMap[panelName]; + if (panel.hasOwnProperty(panelName)) + panel.destroy(); + }, + + selectPanel: function(panelName) + { + var selectedPanel = this.selectedPanel; + var panel = this.panelMap[panelName]; + + if (panel && selectedPanel != panel) + { + if (selectedPanel) + { + removeClass(selectedPanel.tabNode, "fbSelectedTab"); + selectedPanel.shutdown(); + selectedPanel.hide(); + } + + if (!panel.parentPanel) + Firebug.context.persistedState.selectedPanelName = panelName; + + this.selectedPanel = panel; + + setClass(panel.tabNode, "fbSelectedTab"); + panel.show(); + panel.initialize(); + } + }, + + getPanel: function(panelName) + { + var panel = this.panelMap[panelName]; + + return panel; + } + +}; + +//************************************************************************************************ +// Button + +/** + * options.element + * options.caption + * options.title + * + * options.owner + * options.className + * options.pressedClassName + * + * options.onPress + * options.onUnpress + * options.onClick + * + * @class + * @extends FBL.Controller + * + */ + +FBL.Button = function(options) +{ + options = options || {}; + + append(this, options); + + this.state = "unpressed"; + this.display = "unpressed"; + + if (this.element) + { + this.container = this.element.parentNode; + } + else + { + this.shouldDestroy = true; + + this.container = this.owner.getPanel().toolButtonsNode; + + this.element = createElement("a", { + className: this.baseClassName + " " + this.className + " fbHover", + innerHTML: this.caption + }); + + if (this.title) + this.element.title = this.title; + + this.container.appendChild(this.element); + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +Button.prototype = extend(Controller, +/**@extend FBL.Button.prototype*/ +{ + type: "normal", + caption: "caption", + title: null, + + className: "", // custom class + baseClassName: "fbButton", // control class + pressedClassName: "fbBtnPressed", // control pressed class + + element: null, + container: null, + owner: null, + + state: null, + display: null, + + destroy: function() + { + this.shutdown(); + + // only remove if it is a dynamically generated button (not pre-rendered) + if (this.shouldDestroy) + this.container.removeChild(this.element); + + this.element = null; + this.container = null; + this.owner = null; + }, + + initialize: function() + { + Controller.initialize.apply(this); + + var element = this.element; + + this.addController([element, "mousedown", this.handlePress]); + + if (this.type == "normal") + this.addController( + [element, "mouseup", this.handleUnpress], + [element, "mouseout", this.handleUnpress], + [element, "click", this.handleClick] + ); + }, + + shutdown: function() + { + Controller.shutdown.apply(this); + }, + + restore: function() + { + this.changeState("unpressed"); + }, + + changeState: function(state) + { + this.state = state; + this.changeDisplay(state); + }, + + changeDisplay: function(display) + { + if (display != this.display) + { + if (display == "pressed") + { + setClass(this.element, this.pressedClassName); + } + else if (display == "unpressed") + { + removeClass(this.element, this.pressedClassName); + } + this.display = display; + } + }, + + handlePress: function(event) + { + cancelEvent(event, true); + + if (this.type == "normal") + { + this.changeDisplay("pressed"); + this.beforeClick = true; + } + else if (this.type == "toggle") + { + if (this.state == "pressed") + { + this.changeState("unpressed"); + + if (this.onUnpress) + this.onUnpress.apply(this.owner, arguments); + } + else + { + this.changeState("pressed"); + + if (this.onPress) + this.onPress.apply(this.owner, arguments); + } + + if (this.onClick) + this.onClick.apply(this.owner, arguments); + } + + return false; + }, + + handleUnpress: function(event) + { + cancelEvent(event, true); + + if (this.beforeClick) + this.changeDisplay("unpressed"); + + return false; + }, + + handleClick: function(event) + { + cancelEvent(event, true); + + if (this.type == "normal") + { + if (this.onClick) + this.onClick.apply(this.owner); + + this.changeState("unpressed"); + } + + this.beforeClick = false; + + return false; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** + * @class + * @extends FBL.Button + */ +FBL.IconButton = function() +{ + Button.apply(this, arguments); +}; + +IconButton.prototype = extend(Button.prototype, +/**@extend FBL.IconButton.prototype*/ +{ + baseClassName: "fbIconButton", + pressedClassName: "fbIconPressed" +}); + + +//************************************************************************************************ +// Menu + +var menuItemProps = {"class": "$item.className", type: "$item.type", value: "$item.value", + _command: "$item.command"}; + +if (isIE6) + menuItemProps.href = "javascript:void(0)"; + +// Allow GUI to be loaded even when Domplate module is not installed. +if (FBL.domplate) +var MenuPlate = domplate(Firebug.Rep, +{ + tag: + DIV({"class": "fbMenu fbShadow"}, + DIV({"class": "fbMenuContent fbShadowContent"}, + FOR("item", "$object.items|memberIterator", + TAG("$item.tag", {item: "$item"}) + ) + ) + ), + + itemTag: + A(menuItemProps, + "$item.label" + ), + + checkBoxTag: + A(extend(menuItemProps, {checked : "$item.checked"}), + + "$item.label" + ), + + radioButtonTag: + A(extend(menuItemProps, {selected : "$item.selected"}), + + "$item.label" + ), + + groupTag: + A(extend(menuItemProps, {child: "$item.child"}), + "$item.label" + ), + + shortcutTag: + A(menuItemProps, + "$item.label", + SPAN({"class": "fbMenuShortcutKey"}, + "$item.key" + ) + ), + + separatorTag: + SPAN({"class": "fbMenuSeparator"}), + + memberIterator: function(items) + { + var result = []; + + for (var i=0, length=items.length; i width || el.scrollHeight > height)) + { + width = el.scrollWidth; + height = el.scrollHeight; + } + + return {width: width, height: height}; + }, + + getWindowScrollPosition: function() + { + var top=0, left=0, el; + + if(typeof this.window.pageYOffset == "number") + { + top = this.window.pageYOffset; + left = this.window.pageXOffset; + } + else if((el=this.document.body) && (el.scrollTop || el.scrollLeft)) + { + top = el.scrollTop; + left = el.scrollLeft; + } + else if((el=this.document.documentElement) && (el.scrollTop || el.scrollLeft)) + { + top = el.scrollTop; + left = el.scrollLeft; + } + + return {top:top, left:left}; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Element Methods + + getElementFromPoint: function(x, y) + { + if (shouldFixElementFromPoint) + { + var scroll = this.getWindowScrollPosition(); + return this.document.elementFromPoint(x + scroll.left, y + scroll.top); + } + else + return this.document.elementFromPoint(x, y); + }, + + getElementPosition: function(el) + { + var left = 0; + var top = 0; + + do + { + left += el.offsetLeft; + top += el.offsetTop; + } + while (el = el.offsetParent); + + return {left:left, top:top}; + }, + + getElementBox: function(el) + { + var result = {}; + + if (el.getBoundingClientRect) + { + var rect = el.getBoundingClientRect(); + + // fix IE problem with offset when not in fullscreen mode + var offset = isIE ? this.document.body.clientTop || this.document.documentElement.clientTop: 0; + + var scroll = this.getWindowScrollPosition(); + + result.top = Math.round(rect.top - offset + scroll.top); + result.left = Math.round(rect.left - offset + scroll.left); + result.height = Math.round(rect.bottom - rect.top); + result.width = Math.round(rect.right - rect.left); + } + else + { + var position = this.getElementPosition(el); + + result.top = position.top; + result.left = position.left; + result.height = el.offsetHeight; + result.width = el.offsetWidth; + } + + return result; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Measurement Methods + + getMeasurement: function(el, name) + { + var result = {value: 0, unit: "px"}; + + var cssValue = this.getStyle(el, name); + + if (!cssValue) return result; + if (cssValue.toLowerCase() == "auto") return result; + + var reMeasure = /(\d+\.?\d*)(.*)/; + var m = cssValue.match(reMeasure); + + if (m) + { + result.value = m[1]-0; + result.unit = m[2].toLowerCase(); + } + + return result; + }, + + getMeasurementInPixels: function(el, name) + { + if (!el) return null; + + var m = this.getMeasurement(el, name); + var value = m.value; + var unit = m.unit; + + if (unit == "px") + return value; + + else if (unit == "pt") + return this.pointsToPixels(name, value); + + else if (unit == "em") + return this.emToPixels(el, value); + + else if (unit == "%") + return this.percentToPixels(el, value); + + else if (unit == "ex") + return this.exToPixels(el, value); + + // TODO: add other units. Maybe create a better general way + // to calculate measurements in different units. + }, + + getMeasurementBox1: function(el, name) + { + var sufixes = ["Top", "Left", "Bottom", "Right"]; + var result = []; + + for(var i=0, sufix; sufix=sufixes[i]; i++) + result[i] = Math.round(this.getMeasurementInPixels(el, name + sufix)); + + return {top:result[0], left:result[1], bottom:result[2], right:result[3]}; + }, + + getMeasurementBox: function(el, name) + { + var result = []; + var sufixes = name == "border" ? + ["TopWidth", "LeftWidth", "BottomWidth", "RightWidth"] : + ["Top", "Left", "Bottom", "Right"]; + + if (isIE) + { + var propName, cssValue; + var autoMargin = null; + + for(var i=0, sufix; sufix=sufixes[i]; i++) + { + propName = name + sufix; + + cssValue = el.currentStyle[propName] || el.style[propName]; + + if (cssValue == "auto") + { + if (!autoMargin) + autoMargin = this.getCSSAutoMarginBox(el); + + result[i] = autoMargin[sufix.toLowerCase()]; + } + else + result[i] = this.getMeasurementInPixels(el, propName); + + } + + } + else + { + for(var i=0, sufix; sufix=sufixes[i]; i++) + result[i] = this.getMeasurementInPixels(el, name + sufix); + } + + return {top:result[0], left:result[1], bottom:result[2], right:result[3]}; + }, + + getCSSAutoMarginBox: function(el) + { + if (isIE && " meta title input script link a ".indexOf(" "+el.nodeName.toLowerCase()+" ") != -1) + return {top:0, left:0, bottom:0, right:0}; + /**/ + + if (isIE && " h1 h2 h3 h4 h5 h6 h7 ul p ".indexOf(" "+el.nodeName.toLowerCase()+" ") == -1) + return {top:0, left:0, bottom:0, right:0}; + /**/ + + var offsetTop = 0; + if (false && isIEStantandMode) + { + var scrollSize = Firebug.browser.getWindowScrollSize(); + offsetTop = scrollSize.height; + } + + var box = this.document.createElement("div"); + //box.style.cssText = "margin:0; padding:1px; border: 0; position:static; overflow:hidden; visibility: hidden;"; + box.style.cssText = "margin:0; padding:1px; border: 0; visibility: hidden;"; + + var clone = el.cloneNode(false); + var text = this.document.createTextNode(" "); + clone.appendChild(text); + + box.appendChild(clone); + + this.document.body.appendChild(box); + + var marginTop = clone.offsetTop - box.offsetTop - 1; + var marginBottom = box.offsetHeight - clone.offsetHeight - 2 - marginTop; + + var marginLeft = clone.offsetLeft - box.offsetLeft - 1; + var marginRight = box.offsetWidth - clone.offsetWidth - 2 - marginLeft; + + this.document.body.removeChild(box); + + return {top:marginTop+offsetTop, left:marginLeft, bottom:marginBottom-offsetTop, right:marginRight}; + }, + + getFontSizeInPixels: function(el) + { + var size = this.getMeasurement(el, "fontSize"); + + if (size.unit == "px") return size.value; + + // get font size, the dirty way + var computeDirtyFontSize = function(el, calibration) + { + var div = this.document.createElement("div"); + var divStyle = offscreenStyle; + + if (calibration) + divStyle += " font-size:"+calibration+"px;"; + + div.style.cssText = divStyle; + div.innerHTML = "A"; + el.appendChild(div); + + var value = div.offsetHeight; + el.removeChild(div); + return value; + }; + + /* + var calibrationBase = 200; + var calibrationValue = computeDirtyFontSize(el, calibrationBase); + var rate = calibrationBase / calibrationValue; + /**/ + + // the "dirty technique" fails in some environments, so we're using a static value + // based in some tests. + var rate = 200 / 225; + + var value = computeDirtyFontSize(el); + + return value * rate; + }, + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Unit Funtions + + pointsToPixels: function(name, value, returnFloat) + { + var axis = /Top$|Bottom$/.test(name) ? "y" : "x"; + + var result = value * pixelsPerInch[axis] / 72; + + return returnFloat ? result : Math.round(result); + }, + + emToPixels: function(el, value) + { + if (!el) return null; + + var fontSize = this.getFontSizeInPixels(el); + + return Math.round(value * fontSize); + }, + + exToPixels: function(el, value) + { + if (!el) return null; + + // get ex value, the dirty way + var div = this.document.createElement("div"); + div.style.cssText = offscreenStyle + "width:"+value + "ex;"; + + el.appendChild(div); + var value = div.offsetWidth; + el.removeChild(div); + + return value; + }, + + percentToPixels: function(el, value) + { + if (!el) return null; + + // get % value, the dirty way + var div = this.document.createElement("div"); + div.style.cssText = offscreenStyle + "width:"+value + "%;"; + + el.appendChild(div); + var value = div.offsetWidth; + el.removeChild(div); + + return value; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getStyle: isIE ? function(el, name) + { + return el.currentStyle[name] || el.style[name] || undefined; + } + : function(el, name) + { + return this.document.defaultView.getComputedStyle(el,null)[name] + || el.style[name] || undefined; + } + +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope ns-chrome*/ function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Globals + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Window Options + +var WindowDefaultOptions = + { + type: "frame", + id: "FirebugUI" + //height: 350 // obsolete + }, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Instantiated objects + + commandLine, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Interface Elements Cache + + fbTop, + fbContent, + fbContentStyle, + fbBottom, + fbBtnInspect, + + fbToolbar, + + fbPanelBox1, + fbPanelBox1Style, + fbPanelBox2, + fbPanelBox2Style, + fbPanelBar2Box, + fbPanelBar2BoxStyle, + + fbHSplitter, + fbVSplitter, + fbVSplitterStyle, + + fbPanel1, + fbPanel1Style, + fbPanel2, + fbPanel2Style, + + fbConsole, + fbConsoleStyle, + fbHTML, + + fbCommandLine, + fbLargeCommandLine, + fbLargeCommandButtons, + +//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Cached size values + + topHeight, + topPartialHeight, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + chromeRedrawSkipRate = isIE ? 75 : isOpera ? 80 : 75, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastSelectedPanelName, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focusCommandLineState = 0, + lastFocusedPanelName, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastHSplitterMouseMove = 0, + onHSplitterMouseMoveBuffer = null, + onHSplitterMouseMoveTimer = null, + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + lastVSplitterMouseMove = 0; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + +// ************************************************************************************************ +// FirebugChrome + +FBL.defaultPersistedState = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + isOpen: false, + height: 300, + sidePanelWidth: 350, + + selectedPanelName: "Console", + selectedHTMLElementId: null, + + htmlSelectionStack: [] + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +}; + +/**@namespace*/ +FBL.FirebugChrome = +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + //isOpen: false, + //height: 300, + //sidePanelWidth: 350, + + //selectedPanelName: "Console", + //selectedHTMLElementId: null, + + chromeMap: {}, + + htmlSelectionStack: [], + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.create", "creating chrome window"); + + createChromeWindow(); + }, + + initialize: function() + { + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.initialize", "initializing chrome window"); + + if (Env.chrome.type == "frame" || Env.chrome.type == "div") + ChromeMini.create(Env.chrome); + + var chrome = Firebug.chrome = new Chrome(Env.chrome); + FirebugChrome.chromeMap[chrome.type] = chrome; + + addGlobalEvent("keydown", onGlobalKeyDown); + + if (Env.Options.enablePersistent && chrome.type == "popup") + { + // TODO: xxxpedro persist - revise chrome synchronization when in persistent mode + var frame = FirebugChrome.chromeMap.frame; + if (frame) + frame.close(); + + //chrome.reattach(frame, chrome); + //TODO: xxxpedro persist synchronize? + chrome.initialize(); + } + }, + + clone: function(FBChrome) + { + for (var name in FBChrome) + { + var prop = FBChrome[name]; + if (FBChrome.hasOwnProperty(name) && !isFunction(prop)) + { + this[name] = prop; + } + } + } +}; + + + +// ************************************************************************************************ +// Chrome Window Creation + +var createChromeWindow = function(options) +{ + options = extend(WindowDefaultOptions, options || {}); + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Locals + + var browserWin = Env.browser.window; + var browserContext = new Context(browserWin); + var prefs = Store.get("FirebugLite"); + var persistedState = prefs && prefs.persistedState || defaultPersistedState; + + var chrome = {}, + + context = options.context || Env.browser, + + type = chrome.type = Env.Options.enablePersistent ? + "popup" : + options.type, + + isChromeFrame = type == "frame", + + useLocalSkin = Env.useLocalSkin, + + url = useLocalSkin ? + Env.Location.skin : + "about:blank", + + // document.body not available in XML+XSL documents in Firefox + body = context.document.getElementsByTagName("body")[0], + + formatNode = function(node) + { + if (!Env.isDebugMode) + { + node.firebugIgnore = true; + } + + var browserWinSize = browserContext.getWindowSize(); + var height = persistedState.height || 300; + + height = Math.min(browserWinSize.height, height); + height = Math.max(200, height); + + node.style.border = "0"; + node.style.visibility = "hidden"; + node.style.zIndex = "2147483647"; // MAX z-index = 2147483647 + node.style.position = noFixedPosition ? "absolute" : "fixed"; + node.style.width = "100%"; // "102%"; IE auto margin bug + node.style.left = "0"; + node.style.bottom = noFixedPosition ? "-1px" : "0"; + node.style.height = height + "px"; + + // avoid flickering during chrome rendering + //if (isFirefox) + // node.style.display = "none"; + }, + + createChromeDiv = function() + { + //Firebug.Console.warn("Firebug Lite GUI is working in 'windowless mode'. It may behave slower and receive interferences from the page in which it is installed."); + + var node = chrome.node = createGlobalElement("div"), + style = createGlobalElement("style"), + + css = FirebugChrome.Skin.CSS + /* + .replace(/;/g, " !important;") + .replace(/!important\s!important/g, "!important") + .replace(/display\s*:\s*(\w+)\s*!important;/g, "display:$1;")*/, + + // reset some styles to minimize interference from the main page's style + rules = ".fbBody *{margin:0;padding:0;font-size:11px;line-height:13px;color:inherit;}" + + // load the chrome styles + css + + // adjust some remaining styles + ".fbBody #fbHSplitter{position:absolute !important;} .fbBody #fbHTML span{line-height:14px;} .fbBody .lineNo div{line-height:inherit !important;}"; + /* + if (isIE) + { + // IE7 CSS bug (FbChrome table bigger than its parent div) + rules += ".fbBody table.fbChrome{position: static !important;}"; + }/**/ + + style.type = "text/css"; + + if (style.styleSheet) + style.styleSheet.cssText = rules; + else + style.appendChild(context.document.createTextNode(rules)); + + document.getElementsByTagName("head")[0].appendChild(style); + + node.className = "fbBody"; + node.style.overflow = "hidden"; + node.innerHTML = getChromeDivTemplate(); + + if (isIE) + { + // IE7 CSS bug (FbChrome table bigger than its parent div) + setTimeout(function(){ + node.firstChild.style.height = "1px"; + node.firstChild.style.position = "static"; + },0); + /**/ + } + + formatNode(node); + + body.appendChild(node); + + chrome.window = window; + chrome.document = document; + onChromeLoad(chrome); + }; + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + try + { + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the Chrome as a "div" (windowless mode) + if (type == "div") + { + createChromeDiv(); + return; + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // cretate the Chrome as an "iframe" + else if (isChromeFrame) + { + // Create the Chrome Frame + var node = chrome.node = createGlobalElement("iframe"); + node.setAttribute("src", url); + node.setAttribute("frameBorder", "0"); + + formatNode(node); + + body.appendChild(node); + + // must set the id after appending to the document, otherwise will cause an + // strange error in IE, making the iframe load the page in which the bookmarklet + // was created (like getfirebug.com), before loading the injected UI HTML, + // generating an "Access Denied" error. + node.id = options.id; + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the Chrome as a "popup" + else + { + var height = persistedState.popupHeight || 300; + var browserWinSize = browserContext.getWindowSize(); + + var browserWinLeft = typeof browserWin.screenX == "number" ? + browserWin.screenX : browserWin.screenLeft; + + var popupLeft = typeof persistedState.popupLeft == "number" ? + persistedState.popupLeft : browserWinLeft; + + var browserWinTop = typeof browserWin.screenY == "number" ? + browserWin.screenY : browserWin.screenTop; + + var popupTop = typeof persistedState.popupTop == "number" ? + persistedState.popupTop : + Math.max( + 0, + Math.min( + browserWinTop + browserWinSize.height - height, + // Google Chrome bug + screen.availHeight - height - 61 + ) + ); + + var popupWidth = typeof persistedState.popupWidth == "number" ? + persistedState.popupWidth : + Math.max( + 0, + Math.min( + browserWinSize.width, + // Opera opens popup in a new tab if it's too big! + screen.availWidth-10 + ) + ); + + var popupHeight = typeof persistedState.popupHeight == "number" ? + persistedState.popupHeight : 300; + + var options = [ + "true,top=", popupTop, + ",left=", popupLeft, + ",height=", popupHeight, + ",width=", popupWidth, + ",resizable" + ].join(""), + + node = chrome.node = context.window.open( + url, + "popup", + options + ); + + if (node) + { + try + { + node.focus(); + } + catch(E) + { + alert("Firebug Error: Firebug popup was blocked."); + return; + } + } + else + { + alert("Firebug Error: Firebug popup was blocked."); + return; + } + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Inject the interface HTML if it is not using the local skin + + if (!useLocalSkin) + { + var tpl = getChromeTemplate(!isChromeFrame), + doc = isChromeFrame ? node.contentWindow.document : node.document; + + doc.write(tpl); + doc.close(); + } + + //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Wait the Window to be loaded + + var win, + + waitDelay = useLocalSkin ? isChromeFrame ? 200 : 300 : 100, + + waitForWindow = function() + { + if ( // Frame loaded... OR + isChromeFrame && (win=node.contentWindow) && + node.contentWindow.document.getElementById("fbCommandLine") || + + // Popup loaded + !isChromeFrame && (win=node.window) && node.document && + node.document.getElementById("fbCommandLine") ) + { + chrome.window = win.window; + chrome.document = win.document; + + // Prevent getting the wrong chrome height in FF when opening a popup + setTimeout(function(){ + onChromeLoad(chrome); + }, useLocalSkin ? 200 : 0); + } + else + setTimeout(waitForWindow, waitDelay); + }; + + waitForWindow(); + } + catch(e) + { + var msg = e.message || e; + + if (/access/i.test(msg)) + { + // Firebug Lite could not create a window for its Graphical User Interface due to + // a access restriction. This happens in some pages, when loading via bookmarklet. + // In such cases, the only way is to load the GUI in a "windowless mode". + + if (isChromeFrame) + body.removeChild(node); + else if(type == "popup") + node.close(); + + // Load the GUI in a "windowless mode" + createChromeDiv(); + } + else + { + alert("Firebug Error: Firebug GUI could not be created."); + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var onChromeLoad = function onChromeLoad(chrome) +{ + Env.chrome = chrome; + + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Chrome onChromeLoad", "chrome window loaded"); + + if (Env.Options.enablePersistent) + { + // TODO: xxxpedro persist - make better chrome synchronization when in persistent mode + Env.FirebugChrome = FirebugChrome; + + chrome.window.Firebug = chrome.window.Firebug || {}; + chrome.window.Firebug.SharedEnv = Env; + + if (Env.isDevelopmentMode) + { + Env.browser.window.FBDev.loadChromeApplication(chrome); + } + else + { + var doc = chrome.document; + var script = doc.createElement("script"); + script.src = Env.Location.app + "#remote,persist"; + doc.getElementsByTagName("head")[0].appendChild(script); + } + } + else + { + if (chrome.type == "frame" || chrome.type == "div") + { + // initialize the chrome application + setTimeout(function(){ + FBL.Firebug.initialize(); + },0); + } + else if (chrome.type == "popup") + { + var oldChrome = FirebugChrome.chromeMap.frame; + + var newChrome = new Chrome(chrome); + + // TODO: xxxpedro sync detach reattach attach + dispatch(newChrome.panelMap, "detach", [oldChrome, newChrome]); + + newChrome.reattach(oldChrome, newChrome); + } + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var getChromeDivTemplate = function() +{ + return FirebugChrome.Skin.HTML; +}; + +var getChromeTemplate = function(isPopup) +{ + var tpl = FirebugChrome.Skin; + var r = [], i = -1; + + r[++i] = ''; + r[++i] = ''; + r[++i] = Firebug.version; + + /* + r[++i] = ''; + /**/ + + r[++i] = ''; + /**/ + + r[++i] = ''; + r[++i] = tpl.HTML; + r[++i] = ''; + + return r.join(""); +}; + + +// ************************************************************************************************ +// Chrome Class + +/**@class*/ +var Chrome = function Chrome(chrome) +{ + var type = chrome.type; + var Base = type == "frame" || type == "div" ? ChromeFrameBase : ChromePopupBase; + + append(this, Base); // inherit from base class (ChromeFrameBase or ChromePopupBase) + append(this, chrome); // inherit chrome window properties + append(this, new Context(chrome.window)); // inherit from Context class + + FirebugChrome.chromeMap[type] = this; + Firebug.chrome = this; + Env.chrome = chrome.window; + + this.commandLineVisible = false; + this.sidePanelVisible = false; + + this.create(); + + return this; +}; + +// ************************************************************************************************ +// ChromeBase + +/** + * @namespace + * @extends FBL.Controller + * @extends FBL.PanelBar + **/ +var ChromeBase = {}; +append(ChromeBase, Controller); +append(ChromeBase, PanelBar); +append(ChromeBase, +/**@extend ns-chrome-ChromeBase*/ +{ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited properties + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited from createChrome function + + node: null, + type: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // inherited from Context.prototype + + document: null, + window: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // value properties + + sidePanelVisible: false, + commandLineVisible: false, + largeCommandLineVisible: false, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // object properties + + inspectButton: null, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + create: function() + { + PanelBar.create.call(this); + + if (Firebug.Inspector) + this.inspectButton = new Button({ + type: "toggle", + element: $("fbChrome_btInspect"), + owner: Firebug.Inspector, + + onPress: Firebug.Inspector.startInspecting, + onUnpress: Firebug.Inspector.stopInspecting + }); + }, + + destroy: function() + { + if(Firebug.Inspector) + this.inspectButton.destroy(); + + PanelBar.destroy.call(this); + + this.shutdown(); + }, + + testMenu: function() + { + var firebugMenu = new Menu( + { + id: "fbFirebugMenu", + + items: + [ + { + label: "Open Firebug", + type: "shortcut", + key: isFirefox ? "Shift+F12" : "F12", + checked: true, + command: "toggleChrome" + }, + { + label: "Open Firebug in New Window", + type: "shortcut", + key: isFirefox ? "Ctrl+Shift+F12" : "Ctrl+F12", + command: "openPopup" + }, + { + label: "Inspect Element", + type: "shortcut", + key: "Ctrl+Shift+C", + command: "toggleInspect" + }, + { + label: "Command Line", + type: "shortcut", + key: "Ctrl+Shift+L", + command: "focusCommandLine" + }, + "-", + { + label: "Options", + type: "group", + child: "fbFirebugOptionsMenu" + }, + "-", + { + label: "Firebug Lite Website...", + command: "visitWebsite" + }, + { + label: "Discussion Group...", + command: "visitDiscussionGroup" + }, + { + label: "Issue Tracker...", + command: "visitIssueTracker" + } + ], + + onHide: function() + { + iconButton.restore(); + }, + + toggleChrome: function() + { + Firebug.chrome.toggle(); + }, + + openPopup: function() + { + Firebug.chrome.toggle(true, true); + }, + + toggleInspect: function() + { + Firebug.Inspector.toggleInspect(); + }, + + focusCommandLine: function() + { + Firebug.chrome.focusCommandLine(); + }, + + visitWebsite: function() + { + this.visit("http://getfirebug.com/lite.html"); + }, + + visitDiscussionGroup: function() + { + this.visit("http://groups.google.com/group/firebug"); + }, + + visitIssueTracker: function() + { + this.visit("http://code.google.com/p/fbug/issues/list"); + }, + + visit: function(url) + { + window.open(url); + } + + }); + + /**@private*/ + var firebugOptionsMenu = + { + id: "fbFirebugOptionsMenu", + + getItems: function() + { + var cookiesDisabled = !Firebug.saveCookies; + + return [ + { + label: "Start Opened", + type: "checkbox", + value: "startOpened", + checked: Firebug.startOpened, + disabled: cookiesDisabled + }, + { + label: "Start in New Window", + type: "checkbox", + value: "startInNewWindow", + checked: Firebug.startInNewWindow, + disabled: cookiesDisabled + }, + { + label: "Show Icon When Hidden", + type: "checkbox", + value: "showIconWhenHidden", + checked: Firebug.showIconWhenHidden, + disabled: cookiesDisabled + }, + { + label: "Override Console Object", + type: "checkbox", + value: "overrideConsole", + checked: Firebug.overrideConsole, + disabled: cookiesDisabled + }, + { + label: "Ignore Firebug Elements", + type: "checkbox", + value: "ignoreFirebugElements", + checked: Firebug.ignoreFirebugElements, + disabled: cookiesDisabled + }, + { + label: "Disable When Firebug Active", + type: "checkbox", + value: "disableWhenFirebugActive", + checked: Firebug.disableWhenFirebugActive, + disabled: cookiesDisabled + }, + { + label: "Disable XHR Listener", + type: "checkbox", + value: "disableXHRListener", + checked: Firebug.disableXHRListener, + disabled: cookiesDisabled + }, + { + label: "Disable Resource Fetching", + type: "checkbox", + value: "disableResourceFetching", + checked: Firebug.disableResourceFetching, + disabled: cookiesDisabled + }, + { + label: "Enable Trace Mode", + type: "checkbox", + value: "enableTrace", + checked: Firebug.enableTrace, + disabled: cookiesDisabled + }, + { + label: "Enable Persistent Mode (experimental)", + type: "checkbox", + value: "enablePersistent", + checked: Firebug.enablePersistent, + disabled: cookiesDisabled + }, + "-", + { + label: "Reset All Firebug Options", + command: "restorePrefs", + disabled: cookiesDisabled + } + ]; + }, + + onCheck: function(target, value, checked) + { + Firebug.setPref(value, checked); + }, + + restorePrefs: function(target) + { + Firebug.erasePrefs(); + + if (target) + this.updateMenu(target); + }, + + updateMenu: function(target) + { + var options = getElementsByClass(target.parentNode, "fbMenuOption"); + + var firstOption = options[0]; + var enabled = Firebug.saveCookies; + if (enabled) + Menu.check(firstOption); + else + Menu.uncheck(firstOption); + + if (enabled) + Menu.check(options[0]); + else + Menu.uncheck(options[0]); + + for (var i = 1, length = options.length; i < length; i++) + { + var option = options[i]; + + var value = option.getAttribute("value"); + var pref = Firebug[value]; + + if (pref) + Menu.check(option); + else + Menu.uncheck(option); + + if (enabled) + Menu.enable(option); + else + Menu.disable(option); + } + } + }; + + Menu.register(firebugOptionsMenu); + + var menu = firebugMenu; + + var testMenuClick = function(event) + { + //console.log("testMenuClick"); + cancelEvent(event, true); + + var target = event.target || event.srcElement; + + if (menu.isVisible) + menu.hide(); + else + { + var offsetLeft = isIE6 ? 1 : -4, // IE6 problem with fixed position + + chrome = Firebug.chrome, + + box = chrome.getElementBox(target), + + offset = chrome.type == "div" ? + chrome.getElementPosition(chrome.node) : + {top: 0, left: 0}; + + menu.show( + box.left + offsetLeft - offset.left, + box.top + box.height -5 - offset.top + ); + } + + return false; + }; + + var iconButton = new IconButton({ + type: "toggle", + element: $("fbFirebugButton"), + + onClick: testMenuClick + }); + + iconButton.initialize(); + + //addEvent($("fbToolbarIcon"), "click", testMenuClick); + }, + + initialize: function() + { + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (Env.bookmarkletOutdated) + Firebug.Console.logFormatted([ + "A new bookmarklet version is available. " + + "Please visit http://getfirebug.com/firebuglite#Install and update it." + ], Firebug.context, "warn"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (Firebug.Console) + Firebug.Console.flush(); + + if (Firebug.Trace) + FBTrace.flush(Firebug.Trace); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.chrome.initialize", "initializing chrome application"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize inherited classes + Controller.initialize.call(this); + PanelBar.initialize.call(this); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // create the interface elements cache + + fbTop = $("fbTop"); + fbContent = $("fbContent"); + fbContentStyle = fbContent.style; + fbBottom = $("fbBottom"); + fbBtnInspect = $("fbBtnInspect"); + + fbToolbar = $("fbToolbar"); + + fbPanelBox1 = $("fbPanelBox1"); + fbPanelBox1Style = fbPanelBox1.style; + fbPanelBox2 = $("fbPanelBox2"); + fbPanelBox2Style = fbPanelBox2.style; + fbPanelBar2Box = $("fbPanelBar2Box"); + fbPanelBar2BoxStyle = fbPanelBar2Box.style; + + fbHSplitter = $("fbHSplitter"); + fbVSplitter = $("fbVSplitter"); + fbVSplitterStyle = fbVSplitter.style; + + fbPanel1 = $("fbPanel1"); + fbPanel1Style = fbPanel1.style; + fbPanel2 = $("fbPanel2"); + fbPanel2Style = fbPanel2.style; + + fbConsole = $("fbConsole"); + fbConsoleStyle = fbConsole.style; + fbHTML = $("fbHTML"); + + fbCommandLine = $("fbCommandLine"); + fbLargeCommandLine = $("fbLargeCommandLine"); + fbLargeCommandButtons = $("fbLargeCommandButtons"); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // static values cache + topHeight = fbTop.offsetHeight; + topPartialHeight = fbToolbar.offsetHeight; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + disableTextSelection($("fbToolbar")); + disableTextSelection($("fbPanelBarBox")); + disableTextSelection($("fbPanelBar1")); + disableTextSelection($("fbPanelBar2")); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Add the "javascript:void(0)" href attributes used to make the hover effect in IE6 + if (isIE6 && Firebug.Selector) + { + // TODO: xxxpedro change to getElementsByClass + var as = $$(".fbHover"); + for (var i=0, a; a=as[i]; i++) + { + a.setAttribute("href", "javascript:void(0)"); + } + } + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // initialize all panels + /* + var panelMap = Firebug.panelTypes; + for (var i=0, p; p=panelMap[i]; i++) + { + if (!p.parentPanel) + { + this.addPanel(p.prototype.name); + } + } + /**/ + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + if(Firebug.Inspector) + this.inspectButton.initialize(); + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + this.addController( + [$("fbLargeCommandLineIcon"), "click", this.showLargeCommandLine] + ); + + // ************************************************************************************************ + + // Select the first registered panel + // TODO: BUG IE7 + var self = this; + setTimeout(function(){ + self.selectPanel(Firebug.context.persistedState.selectedPanelName); + + if (Firebug.context.persistedState.selectedPanelName == "Console" && Firebug.CommandLine) + Firebug.chrome.focusCommandLine(); + },0); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + //this.draw(); + + + + + + + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + var onPanelMouseDown = function onPanelMouseDown(event) + { + //console.log("onPanelMouseDown", event.target || event.srcElement, event); + + var target = event.target || event.srcElement; + + if (FBL.isLeftClick(event)) + { + var editable = FBL.getAncestorByClass(target, "editable"); + + // if an editable element has been clicked then start editing + if (editable) + { + Firebug.Editor.startEditing(editable); + FBL.cancelEvent(event); + } + // if any other element has been clicked then stop editing + else + { + if (!hasClass(target, "textEditorInner")) + Firebug.Editor.stopEditing(); + } + } + else if (FBL.isMiddleClick(event) && Firebug.getRepNode(target)) + { + // Prevent auto-scroll when middle-clicking a rep object + FBL.cancelEvent(event); + } + }; + + Firebug.getElementPanel = function(element) + { + var panelNode = getAncestorByClass(element, "fbPanel"); + var id = panelNode.id.substr(2); + + var panel = Firebug.chrome.panelMap[id]; + + if (!panel) + { + if (Firebug.chrome.selectedPanel.sidePanelBar) + panel = Firebug.chrome.selectedPanel.sidePanelBar.panelMap[id]; + } + + return panel; + }; + + + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // TODO: xxxpedro port to Firebug + + // Improved window key code event listener. Only one "keydown" event will be attached + // to the window, and the onKeyCodeListen() function will delegate which listeners + // should be called according to the event.keyCode fired. + var onKeyCodeListenersMap = []; + var onKeyCodeListen = function(event) + { + for (var keyCode in onKeyCodeListenersMap) + { + var listeners = onKeyCodeListenersMap[keyCode]; + + for (var i = 0, listener; listener = listeners[i]; i++) + { + var filter = listener.filter || FBL.noKeyModifiers; + + if (event.keyCode == keyCode && (!filter || filter(event))) + { + listener.listener(); + FBL.cancelEvent(event, true); + return false; + } + } + } + }; + + addEvent(Firebug.chrome.document, "keydown", onKeyCodeListen); + + /** + * @name keyCodeListen + * @memberOf FBL.FirebugChrome + */ + Firebug.chrome.keyCodeListen = function(key, filter, listener, capture) + { + var keyCode = KeyEvent["DOM_VK_"+key]; + + if (!onKeyCodeListenersMap[keyCode]) + onKeyCodeListenersMap[keyCode] = []; + + onKeyCodeListenersMap[keyCode].push({ + filter: filter, + listener: listener + }); + + return keyCode; + }; + + /** + * @name keyIgnore + * @memberOf FBL.FirebugChrome + */ + Firebug.chrome.keyIgnore = function(keyCode) + { + onKeyCodeListenersMap[keyCode] = null; + delete onKeyCodeListenersMap[keyCode]; + }; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + /**/ + // move to shutdown + //removeEvent(Firebug.chrome.document, "keydown", listener[0]); + + + /* + Firebug.chrome.keyCodeListen = function(key, filter, listener, capture) + { + if (!filter) + filter = FBL.noKeyModifiers; + + var keyCode = KeyEvent["DOM_VK_"+key]; + + var fn = function fn(event) + { + if (event.keyCode == keyCode && (!filter || filter(event))) + { + listener(); + FBL.cancelEvent(event, true); + return false; + } + } + + addEvent(Firebug.chrome.document, "keydown", fn); + + return [fn, capture]; + }; + + Firebug.chrome.keyIgnore = function(listener) + { + removeEvent(Firebug.chrome.document, "keydown", listener[0]); + }; + /**/ + + + this.addController( + [fbPanel1, "mousedown", onPanelMouseDown], + [fbPanel2, "mousedown", onPanelMouseDown] + ); +/**/ + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + + // menus can be used without domplate + if (FBL.domplate) + this.testMenu(); + /**/ + + //test XHR + /* + setTimeout(function(){ + + FBL.Ajax.request({url: "../content/firebug/boot.js"}); + FBL.Ajax.request({url: "../content/firebug/boot.js.invalid"}); + + },1000); + /**/ + }, + + shutdown: function() + { + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + if(Firebug.Inspector) + this.inspectButton.shutdown(); + + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + // ************************************************************************************************ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + // remove disableTextSelection event handlers + restoreTextSelection($("fbToolbar")); + restoreTextSelection($("fbPanelBarBox")); + restoreTextSelection($("fbPanelBar1")); + restoreTextSelection($("fbPanelBar2")); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // shutdown inherited classes + Controller.shutdown.call(this); + PanelBar.shutdown.call(this); + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Remove the interface elements cache (this must happen after calling + // the shutdown method of all dependent components to avoid errors) + + fbTop = null; + fbContent = null; + fbContentStyle = null; + fbBottom = null; + fbBtnInspect = null; + + fbToolbar = null; + + fbPanelBox1 = null; + fbPanelBox1Style = null; + fbPanelBox2 = null; + fbPanelBox2Style = null; + fbPanelBar2Box = null; + fbPanelBar2BoxStyle = null; + + fbHSplitter = null; + fbVSplitter = null; + fbVSplitterStyle = null; + + fbPanel1 = null; + fbPanel1Style = null; + fbPanel2 = null; + + fbConsole = null; + fbConsoleStyle = null; + fbHTML = null; + + fbCommandLine = null; + fbLargeCommandLine = null; + fbLargeCommandButtons = null; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // static values cache + + topHeight = null; + topPartialHeight = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + toggle: function(forceOpen, popup) + { + if(popup) + { + this.detach(); + } + else + { + if (isOpera && Firebug.chrome.type == "popup" && Firebug.chrome.node.closed) + { + var frame = FirebugChrome.chromeMap.frame; + frame.reattach(); + + FirebugChrome.chromeMap.popup = null; + + frame.open(); + + return; + } + + // If the context is a popup, ignores the toggle process + if (Firebug.chrome.type == "popup") return; + + var shouldOpen = forceOpen || !Firebug.context.persistedState.isOpen; + + if(shouldOpen) + this.open(); + else + this.close(); + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + detach: function() + { + if(!FirebugChrome.chromeMap.popup) + { + this.close(); + createChromeWindow({type: "popup"}); + } + }, + + reattach: function(oldChrome, newChrome) + { + Firebug.browser.window.Firebug = Firebug; + + // chrome synchronization + var newPanelMap = newChrome.panelMap; + var oldPanelMap = oldChrome.panelMap; + + var panel; + for(var name in newPanelMap) + { + // TODO: xxxpedro innerHTML + panel = newPanelMap[name]; + if (panel.options.innerHTMLSync) + panel.panelNode.innerHTML = oldPanelMap[name].panelNode.innerHTML; + } + + Firebug.chrome = newChrome; + + // TODO: xxxpedro sync detach reattach attach + //dispatch(Firebug.chrome.panelMap, "detach", [oldChrome, newChrome]); + + if (newChrome.type == "popup") + { + newChrome.initialize(); + //dispatch(Firebug.modules, "initialize", []); + } + else + { + // TODO: xxxpedro only needed in persistent + // should use FirebugChrome.clone, but popup FBChrome + // isn't acessible + Firebug.context.persistedState.selectedPanelName = oldChrome.selectedPanel.name; + } + + dispatch(newPanelMap, "reattach", [oldChrome, newChrome]); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + draw: function() + { + var size = this.getSize(); + + // Height related values + var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0, + + y = Math.max(size.height /* chrome height */, topHeight), + + heightValue = Math.max(y - topHeight - commandLineHeight /* fixed height */, 0), + + height = heightValue + "px", + + // Width related values + sideWidthValue = Firebug.chrome.sidePanelVisible ? Firebug.context.persistedState.sidePanelWidth : 0, + + width = Math.max(size.width /* chrome width */ - sideWidthValue, 0) + "px"; + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Height related rendering + fbPanelBox1Style.height = height; + fbPanel1Style.height = height; + + if (isIE || isOpera) + { + // Fix IE and Opera problems with auto resizing the verticall splitter + fbVSplitterStyle.height = Math.max(y - topPartialHeight - commandLineHeight, 0) + "px"; + } + //xxxpedro FF2 only? + /* + else if (isFirefox) + { + // Fix Firefox problem with table rows with 100% height (fit height) + fbContentStyle.maxHeight = Math.max(y - fixedHeight, 0)+ "px"; + }/**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Width related rendering + fbPanelBox1Style.width = width; + fbPanel1Style.width = width; + + // SidePanel rendering + if (Firebug.chrome.sidePanelVisible) + { + sideWidthValue = Math.max(sideWidthValue - 6, 0); + + var sideWidth = sideWidthValue + "px"; + + fbPanelBox2Style.width = sideWidth; + + fbVSplitterStyle.right = sideWidth; + + if (Firebug.chrome.largeCommandLineVisible) + { + fbLargeCommandLine = $("fbLargeCommandLine"); + + fbLargeCommandLine.style.height = heightValue - 4 + "px"; + fbLargeCommandLine.style.width = sideWidthValue - 2 + "px"; + + fbLargeCommandButtons = $("fbLargeCommandButtons"); + fbLargeCommandButtons.style.width = sideWidth; + } + else + { + fbPanel2Style.height = height; + fbPanel2Style.width = sideWidth; + + fbPanelBar2BoxStyle.width = sideWidth; + } + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + getSize: function() + { + return this.type == "div" ? + { + height: this.node.offsetHeight, + width: this.node.offsetWidth + } + : + this.getWindowSize(); + }, + + resize: function() + { + var self = this; + + // avoid partial resize when maximizing window + setTimeout(function(){ + self.draw(); + + if (noFixedPosition && (self.type == "frame" || self.type == "div")) + self.fixIEPosition(); + }, 0); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + layout: function(panel) + { + if (FBTrace.DBG_CHROME) FBTrace.sysout("Chrome.layout", ""); + + var options = panel.options; + + changeCommandLineVisibility(options.hasCommandLine); + changeSidePanelVisibility(panel.hasSidePanel); + + Firebug.chrome.draw(); + }, + + showLargeCommandLine: function(hideToggleIcon) + { + var chrome = Firebug.chrome; + + if (!chrome.largeCommandLineVisible) + { + chrome.largeCommandLineVisible = true; + + if (chrome.selectedPanel.options.hasCommandLine) + { + if (Firebug.CommandLine) + Firebug.CommandLine.blur(); + + changeCommandLineVisibility(false); + } + + changeSidePanelVisibility(true); + + fbLargeCommandLine.style.display = "block"; + fbLargeCommandButtons.style.display = "block"; + + fbPanel2Style.display = "none"; + fbPanelBar2BoxStyle.display = "none"; + + chrome.draw(); + + fbLargeCommandLine.focus(); + + if (Firebug.CommandLine) + Firebug.CommandLine.setMultiLine(true); + } + }, + + hideLargeCommandLine: function() + { + if (Firebug.chrome.largeCommandLineVisible) + { + Firebug.chrome.largeCommandLineVisible = false; + + if (Firebug.CommandLine) + Firebug.CommandLine.setMultiLine(false); + + fbLargeCommandLine.blur(); + + fbPanel2Style.display = "block"; + fbPanelBar2BoxStyle.display = "block"; + + fbLargeCommandLine.style.display = "none"; + fbLargeCommandButtons.style.display = "none"; + + changeSidePanelVisibility(false); + + if (Firebug.chrome.selectedPanel.options.hasCommandLine) + changeCommandLineVisibility(true); + + Firebug.chrome.draw(); + + } + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + focusCommandLine: function() + { + var selectedPanelName = this.selectedPanel.name, panelToSelect; + + if (focusCommandLineState == 0 || selectedPanelName != "Console") + { + focusCommandLineState = 0; + lastFocusedPanelName = selectedPanelName; + + panelToSelect = "Console"; + } + if (focusCommandLineState == 1) + { + panelToSelect = lastFocusedPanelName; + } + + this.selectPanel(panelToSelect); + + try + { + if (Firebug.CommandLine) + { + if (panelToSelect == "Console") + Firebug.CommandLine.focus(); + else + Firebug.CommandLine.blur(); + } + } + catch(e) + { + //TODO: xxxpedro trace error + } + + focusCommandLineState = ++focusCommandLineState % 2; + } + +}); + +// ************************************************************************************************ +// ChromeFrameBase + +/** + * @namespace + * @extends ns-chrome-ChromeBase + */ +var ChromeFrameBase = extend(ChromeBase, +/**@extend ns-chrome-ChromeFrameBase*/ +{ + create: function() + { + ChromeBase.create.call(this); + + // restore display for the anti-flicker trick + if (isFirefox) + this.node.style.display = "block"; + + if (Env.Options.startInNewWindow) + { + this.close(); + this.toggle(true, true); + return; + } + + if (Env.Options.startOpened) + this.open(); + else + this.close(); + }, + + destroy: function() + { + var size = Firebug.chrome.getWindowSize(); + + Firebug.context.persistedState.height = size.height; + + if (Firebug.saveCookies) + Firebug.savePrefs(); + + removeGlobalEvent("keydown", onGlobalKeyDown); + + ChromeBase.destroy.call(this); + + this.document = null; + delete this.document; + + this.window = null; + delete this.window; + + this.node.parentNode.removeChild(this.node); + this.node = null; + delete this.node; + }, + + initialize: function() + { + //FBTrace.sysout("Frame", "initialize();") + ChromeBase.initialize.call(this); + + this.addController( + [Firebug.browser.window, "resize", this.resize], + [$("fbWindow_btClose"), "click", this.close], + [$("fbWindow_btDetach"), "click", this.detach], + [$("fbWindow_btDeactivate"), "click", this.deactivate] + ); + + if (!Env.Options.enablePersistent) + this.addController([Firebug.browser.window, "unload", Firebug.shutdown]); + + if (noFixedPosition) + { + this.addController( + [Firebug.browser.window, "scroll", this.fixIEPosition] + ); + } + + fbVSplitter.onmousedown = onVSplitterMouseDown; + fbHSplitter.onmousedown = onHSplitterMouseDown; + + this.isInitialized = true; + }, + + shutdown: function() + { + fbVSplitter.onmousedown = null; + fbHSplitter.onmousedown = null; + + ChromeBase.shutdown.apply(this); + + this.isInitialized = false; + }, + + reattach: function() + { + var frame = FirebugChrome.chromeMap.frame; + + ChromeBase.reattach(FirebugChrome.chromeMap.popup, this); + }, + + open: function() + { + if (!Firebug.context.persistedState.isOpen) + { + Firebug.context.persistedState.isOpen = true; + + if (Env.isChromeExtension) + localStorage.setItem("Firebug", "1,1"); + + var node = this.node; + + node.style.visibility = "hidden"; // Avoid flickering + + if (Firebug.showIconWhenHidden) + { + if (ChromeMini.isInitialized) + { + ChromeMini.shutdown(); + } + + } + else + node.style.display = "block"; + + var main = $("fbChrome"); + + // IE6 throws an error when setting this property! why? + //main.style.display = "table"; + main.style.display = ""; + + var self = this; + /// TODO: xxxpedro FOUC + node.style.visibility = "visible"; + setTimeout(function(){ + ///node.style.visibility = "visible"; + + //dispatch(Firebug.modules, "initialize", []); + self.initialize(); + + if (noFixedPosition) + self.fixIEPosition(); + + self.draw(); + + }, 10); + } + }, + + close: function() + { + if (Firebug.context.persistedState.isOpen) + { + if (this.isInitialized) + { + //dispatch(Firebug.modules, "shutdown", []); + this.shutdown(); + } + + Firebug.context.persistedState.isOpen = false; + + if (Env.isChromeExtension) + localStorage.setItem("Firebug", "1,0"); + + var node = this.node; + + if (Firebug.showIconWhenHidden) + { + node.style.visibility = "hidden"; // Avoid flickering + + // TODO: xxxpedro - persist IE fixed? + var main = $("fbChrome", FirebugChrome.chromeMap.frame.document); + main.style.display = "none"; + + ChromeMini.initialize(); + + node.style.visibility = "visible"; + } + else + node.style.display = "none"; + } + }, + + deactivate: function() + { + // if it is running as a Chrome extension, dispatch a message to the extension signaling + // that Firebug should be deactivated for the current tab + if (Env.isChromeExtension) + { + localStorage.removeItem("Firebug"); + Firebug.GoogleChrome.dispatch("FB_deactivate"); + + // xxxpedro problem here regarding Chrome extension. We can't deactivate the whole + // app, otherwise it won't be able to be reactivated without reloading the page. + // but we need to stop listening global keys, otherwise the key activation won't work. + Firebug.chrome.close(); + } + else + { + Firebug.shutdown(); + } + }, + + fixIEPosition: function() + { + // fix IE problem with offset when not in fullscreen mode + var doc = this.document; + var offset = isIE ? doc.body.clientTop || doc.documentElement.clientTop: 0; + + var size = Firebug.browser.getWindowSize(); + var scroll = Firebug.browser.getWindowScrollPosition(); + var maxHeight = size.height; + var height = this.node.offsetHeight; + + var bodyStyle = doc.body.currentStyle; + + this.node.style.top = maxHeight - height + scroll.top + "px"; + + if ((this.type == "frame" || this.type == "div") && + (bodyStyle.marginLeft || bodyStyle.marginRight)) + { + this.node.style.width = size.width + "px"; + } + + if (fbVSplitterStyle) + fbVSplitterStyle.right = Firebug.context.persistedState.sidePanelWidth + "px"; + + this.draw(); + } + +}); + + +// ************************************************************************************************ +// ChromeMini + +/** + * @namespace + * @extends FBL.Controller + */ +var ChromeMini = extend(Controller, +/**@extend ns-chrome-ChromeMini*/ +{ + create: function(chrome) + { + append(this, chrome); + this.type = "mini"; + }, + + initialize: function() + { + Controller.initialize.apply(this); + + var doc = FirebugChrome.chromeMap.frame.document; + + var mini = $("fbMiniChrome", doc); + mini.style.display = "block"; + + var miniIcon = $("fbMiniIcon", doc); + var width = miniIcon.offsetWidth + 10; + miniIcon.title = "Open " + Firebug.version; + + var errors = $("fbMiniErrors", doc); + if (errors.offsetWidth) + width += errors.offsetWidth + 10; + + var node = this.node; + node.style.height = "27px"; + node.style.width = width + "px"; + node.style.left = ""; + node.style.right = 0; + + if (this.node.nodeName.toLowerCase() == "iframe") + { + node.setAttribute("allowTransparency", "true"); + this.document.body.style.backgroundColor = "transparent"; + } + else + node.style.background = "transparent"; + + if (noFixedPosition) + this.fixIEPosition(); + + this.addController( + [$("fbMiniIcon", doc), "click", onMiniIconClick] + ); + + if (noFixedPosition) + { + this.addController( + [Firebug.browser.window, "scroll", this.fixIEPosition] + ); + } + + this.isInitialized = true; + }, + + shutdown: function() + { + var node = this.node; + node.style.height = Firebug.context.persistedState.height + "px"; + node.style.width = "100%"; + node.style.left = 0; + node.style.right = ""; + + if (this.node.nodeName.toLowerCase() == "iframe") + { + node.setAttribute("allowTransparency", "false"); + this.document.body.style.backgroundColor = "#fff"; + } + else + node.style.background = "#fff"; + + if (noFixedPosition) + this.fixIEPosition(); + + var doc = FirebugChrome.chromeMap.frame.document; + + var mini = $("fbMiniChrome", doc); + mini.style.display = "none"; + + Controller.shutdown.apply(this); + + this.isInitialized = false; + }, + + draw: function() + { + + }, + + fixIEPosition: ChromeFrameBase.fixIEPosition + +}); + + +// ************************************************************************************************ +// ChromePopupBase + +/** + * @namespace + * @extends ns-chrome-ChromeBase + */ +var ChromePopupBase = extend(ChromeBase, +/**@extend ns-chrome-ChromePopupBase*/ +{ + + initialize: function() + { + setClass(this.document.body, "FirebugPopup"); + + ChromeBase.initialize.call(this); + + this.addController( + [Firebug.chrome.window, "resize", this.resize], + [Firebug.chrome.window, "unload", this.destroy] + //[Firebug.chrome.window, "beforeunload", this.destroy] + ); + + if (Env.Options.enablePersistent) + { + this.persist = bind(this.persist, this); + addEvent(Firebug.browser.window, "unload", this.persist); + } + else + this.addController( + [Firebug.browser.window, "unload", this.close] + ); + + fbVSplitter.onmousedown = onVSplitterMouseDown; + }, + + destroy: function() + { + var chromeWin = Firebug.chrome.window; + var left = chromeWin.screenX || chromeWin.screenLeft; + var top = chromeWin.screenY || chromeWin.screenTop; + var size = Firebug.chrome.getWindowSize(); + + Firebug.context.persistedState.popupTop = top; + Firebug.context.persistedState.popupLeft = left; + Firebug.context.persistedState.popupWidth = size.width; + Firebug.context.persistedState.popupHeight = size.height; + + if (Firebug.saveCookies) + Firebug.savePrefs(); + + // TODO: xxxpedro sync detach reattach attach + var frame = FirebugChrome.chromeMap.frame; + + if(frame) + { + dispatch(frame.panelMap, "detach", [this, frame]); + + frame.reattach(this, frame); + } + + if (Env.Options.enablePersistent) + { + removeEvent(Firebug.browser.window, "unload", this.persist); + } + + ChromeBase.destroy.apply(this); + + FirebugChrome.chromeMap.popup = null; + + this.node.close(); + }, + + persist: function() + { + persistTimeStart = new Date().getTime(); + + removeEvent(Firebug.browser.window, "unload", this.persist); + + Firebug.Inspector.destroy(); + Firebug.browser.window.FirebugOldBrowser = true; + + var persistTimeStart = new Date().getTime(); + + var waitMainWindow = function() + { + var doc, head; + + try + { + if (window.opener && !window.opener.FirebugOldBrowser && (doc = window.opener.document)/* && + doc.documentElement && (head = doc.documentElement.firstChild)*/) + { + + try + { + // exposes the FBL to the global namespace when in debug mode + if (Env.isDebugMode) + { + window.FBL = FBL; + } + + window.Firebug = Firebug; + window.opener.Firebug = Firebug; + + Env.browser = window.opener; + Firebug.browser = Firebug.context = new Context(Env.browser); + Firebug.loadPrefs(); + + registerConsole(); + + // the delay time should be calculated right after registering the + // console, once right after the console registration, call log messages + // will be properly handled + var persistDelay = new Date().getTime() - persistTimeStart; + + var chrome = Firebug.chrome; + addEvent(Firebug.browser.window, "unload", chrome.persist); + + FBL.cacheDocument(); + Firebug.Inspector.create(); + + Firebug.Console.logFormatted( + ["Firebug could not capture console calls during " + + persistDelay + "ms"], + Firebug.context, + "info" + ); + + setTimeout(function(){ + var htmlPanel = chrome.getPanel("HTML"); + htmlPanel.createUI(); + },50); + + } + catch(pE) + { + alert("persist error: " + (pE.message || pE)); + } + + } + else + { + window.setTimeout(waitMainWindow, 0); + } + + } catch (E) { + window.close(); + } + }; + + waitMainWindow(); + }, + + close: function() + { + this.destroy(); + } + +}); + + +//************************************************************************************************ +// UI helpers + +var changeCommandLineVisibility = function changeCommandLineVisibility(visibility) +{ + var last = Firebug.chrome.commandLineVisible; + var visible = Firebug.chrome.commandLineVisible = + typeof visibility == "boolean" ? visibility : !Firebug.chrome.commandLineVisible; + + if (visible != last) + { + if (visible) + { + fbBottom.className = ""; + + if (Firebug.CommandLine) + Firebug.CommandLine.activate(); + } + else + { + if (Firebug.CommandLine) + Firebug.CommandLine.deactivate(); + + fbBottom.className = "hide"; + } + } +}; + +var changeSidePanelVisibility = function changeSidePanelVisibility(visibility) +{ + var last = Firebug.chrome.sidePanelVisible; + Firebug.chrome.sidePanelVisible = + typeof visibility == "boolean" ? visibility : !Firebug.chrome.sidePanelVisible; + + if (Firebug.chrome.sidePanelVisible != last) + { + fbPanelBox2.className = Firebug.chrome.sidePanelVisible ? "" : "hide"; + fbPanelBar2Box.className = Firebug.chrome.sidePanelVisible ? "" : "hide"; + } +}; + + +// ************************************************************************************************ +// F12 Handler + +var onGlobalKeyDown = function onGlobalKeyDown(event) +{ + var keyCode = event.keyCode; + var shiftKey = event.shiftKey; + var ctrlKey = event.ctrlKey; + + if (keyCode == 123 /* F12 */ && (!isFirefox && !shiftKey || shiftKey && isFirefox)) + { + Firebug.chrome.toggle(false, ctrlKey); + cancelEvent(event, true); + + // TODO: xxxpedro replace with a better solution. we're doing this + // to allow reactivating with the F12 key after being deactivated + if (Env.isChromeExtension) + { + Firebug.GoogleChrome.dispatch("FB_enableIcon"); + } + } + else if (keyCode == 67 /* C */ && ctrlKey && shiftKey) + { + Firebug.Inspector.toggleInspect(); + cancelEvent(event, true); + } + else if (keyCode == 76 /* L */ && ctrlKey && shiftKey) + { + Firebug.chrome.focusCommandLine(); + cancelEvent(event, true); + } +}; + +var onMiniIconClick = function onMiniIconClick(event) +{ + Firebug.chrome.toggle(false, event.ctrlKey); + cancelEvent(event, true); +}; + + +// ************************************************************************************************ +// Horizontal Splitter Handling + +var onHSplitterMouseDown = function onHSplitterMouseDown(event) +{ + addGlobalEvent("mousemove", onHSplitterMouseMove); + addGlobalEvent("mouseup", onHSplitterMouseUp); + + if (isIE) + addEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp); + + fbHSplitter.className = "fbOnMovingHSplitter"; + + return false; +}; + +var onHSplitterMouseMove = function onHSplitterMouseMove(event) +{ + cancelEvent(event, true); + + var clientY = event.clientY; + var win = isIE + ? event.srcElement.ownerDocument.parentWindow + : event.target.defaultView || event.target.ownerDocument && event.target.ownerDocument.defaultView; + + if (!win) + return; + + if (win != win.parent) + { + var frameElement = win.frameElement; + if (frameElement) + { + var framePos = Firebug.browser.getElementPosition(frameElement).top; + clientY += framePos; + + if (frameElement.style.position != "fixed") + clientY -= Firebug.browser.getWindowScrollPosition().top; + } + } + + if (isOpera && isQuiksMode && win.frameElement.id == "FirebugUI") + { + clientY = Firebug.browser.getWindowSize().height - win.frameElement.offsetHeight + clientY; + } + + /* + console.log( + typeof win.FBL != "undefined" ? "no-Chrome" : "Chrome", + //win.frameElement.id, + event.target, + clientY + );/**/ + + onHSplitterMouseMoveBuffer = clientY; // buffer + + if (new Date().getTime() - lastHSplitterMouseMove > chromeRedrawSkipRate) // frame skipping + { + lastHSplitterMouseMove = new Date().getTime(); + handleHSplitterMouseMove(); + } + else + if (!onHSplitterMouseMoveTimer) + onHSplitterMouseMoveTimer = setTimeout(handleHSplitterMouseMove, chromeRedrawSkipRate); + + // improving the resizing performance by canceling the mouse event. + // canceling events will prevent the page to receive such events, which would imply + // in more processing being expended. + cancelEvent(event, true); + return false; +}; + +var handleHSplitterMouseMove = function() +{ + if (onHSplitterMouseMoveTimer) + { + clearTimeout(onHSplitterMouseMoveTimer); + onHSplitterMouseMoveTimer = null; + } + + var clientY = onHSplitterMouseMoveBuffer; + + var windowSize = Firebug.browser.getWindowSize(); + var scrollSize = Firebug.browser.getWindowScrollSize(); + + // compute chrome fixed size (top bar and command line) + var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0; + var fixedHeight = topHeight + commandLineHeight; + var chromeNode = Firebug.chrome.node; + + var scrollbarSize = !isIE && (scrollSize.width > windowSize.width) ? 17 : 0; + + //var height = !isOpera ? chromeNode.offsetTop + chromeNode.clientHeight : windowSize.height; + var height = windowSize.height; + + // compute the min and max size of the chrome + var chromeHeight = Math.max(height - clientY + 5 - scrollbarSize, fixedHeight); + chromeHeight = Math.min(chromeHeight, windowSize.height - scrollbarSize); + + Firebug.context.persistedState.height = chromeHeight; + chromeNode.style.height = chromeHeight + "px"; + + if (noFixedPosition) + Firebug.chrome.fixIEPosition(); + + Firebug.chrome.draw(); +}; + +var onHSplitterMouseUp = function onHSplitterMouseUp(event) +{ + removeGlobalEvent("mousemove", onHSplitterMouseMove); + removeGlobalEvent("mouseup", onHSplitterMouseUp); + + if (isIE) + removeEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp); + + fbHSplitter.className = ""; + + Firebug.chrome.draw(); + + // avoid text selection in IE when returning to the document + // after the mouse leaves the document during the resizing + return false; +}; + + +// ************************************************************************************************ +// Vertical Splitter Handling + +var onVSplitterMouseDown = function onVSplitterMouseDown(event) +{ + addGlobalEvent("mousemove", onVSplitterMouseMove); + addGlobalEvent("mouseup", onVSplitterMouseUp); + + return false; +}; + +var onVSplitterMouseMove = function onVSplitterMouseMove(event) +{ + if (new Date().getTime() - lastVSplitterMouseMove > chromeRedrawSkipRate) // frame skipping + { + var target = event.target || event.srcElement; + if (target && target.ownerDocument) // avoid error when cursor reaches out of the chrome + { + var clientX = event.clientX; + var win = document.all + ? event.srcElement.ownerDocument.parentWindow + : event.target.ownerDocument.defaultView; + + if (win != win.parent) + clientX += win.frameElement ? win.frameElement.offsetLeft : 0; + + var size = Firebug.chrome.getSize(); + var x = Math.max(size.width - clientX + 3, 6); + + Firebug.context.persistedState.sidePanelWidth = x; + Firebug.chrome.draw(); + } + + lastVSplitterMouseMove = new Date().getTime(); + } + + cancelEvent(event, true); + return false; +}; + +var onVSplitterMouseUp = function onVSplitterMouseUp(event) +{ + removeGlobalEvent("mousemove", onVSplitterMouseMove); + removeGlobalEvent("mouseup", onVSplitterMouseUp); + + Firebug.chrome.draw(); +}; + + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite = +{ +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Cache = +{ + ID: "firebug-" + new Date().getTime() +}; + +// ************************************************************************************************ + +/** + * TODO: if a cached element is cloned, the expando property will be cloned too in IE + * which will result in a bug. Firebug Lite will think the new cloned node is the old + * one. + * + * TODO: Investigate a possibility of cache validation, to be customized by each + * kind of cache. For ElementCache it should validate if the element still is + * inserted at the DOM. + */ +var cacheUID = 0; +var createCache = function() +{ + var map = {}; + var data = {}; + + var CID = Firebug.Lite.Cache.ID; + + // better detection + var supportsDeleteExpando = !document.all; + + var cacheFunction = function(element) + { + return cacheAPI.set(element); + }; + + var cacheAPI = + { + get: function(key) + { + return map.hasOwnProperty(key) ? + map[key] : + null; + }, + + set: function(element) + { + var id = getValidatedKey(element); + + if (!id) + { + id = ++cacheUID; + element[CID] = id; + } + + if (!map.hasOwnProperty(id)) + { + map[id] = element; + data[id] = {}; + } + + return id; + }, + + unset: function(element) + { + var id = getValidatedKey(element); + + if (!id) return; + + if (supportsDeleteExpando) + { + delete element[CID]; + } + else if (element.removeAttribute) + { + element.removeAttribute(CID); + } + + delete map[id]; + delete data[id]; + + }, + + key: function(element) + { + return getValidatedKey(element); + }, + + has: function(element) + { + var id = getValidatedKey(element); + return id && map.hasOwnProperty(id); + }, + + each: function(callback) + { + for (var key in map) + { + if (map.hasOwnProperty(key)) + { + callback(key, map[key]); + } + } + }, + + data: function(element, name, value) + { + // set data + if (value) + { + if (!name) return null; + + var id = cacheAPI.set(element); + + return data[id][name] = value; + } + // get data + else + { + var id = cacheAPI.key(element); + + return data.hasOwnProperty(id) && data[id].hasOwnProperty(name) ? + data[id][name] : + null; + } + }, + + clear: function() + { + for (var id in map) + { + var element = map[id]; + cacheAPI.unset(element); + } + } + }; + + var getValidatedKey = function(element) + { + var id = element[CID]; + + // If a cached element is cloned in IE, the expando property CID will be also + // cloned (differently than other browsers) resulting in a bug: Firebug Lite + // will think the new cloned node is the old one. To prevent this problem we're + // checking if the cached element matches the given element. + if ( + !supportsDeleteExpando && // the problem happens when supportsDeleteExpando is false + id && // the element has the expando property + map.hasOwnProperty(id) && // there is a cached element with the same id + map[id] != element // but it is a different element than the current one + ) + { + // remove the problematic property + element.removeAttribute(CID); + + id = null; + } + + return id; + }; + + FBL.append(cacheFunction, cacheAPI); + + return cacheFunction; +}; + +// ************************************************************************************************ + +// TODO: xxxpedro : check if we need really this on FBL scope +Firebug.Lite.Cache.StyleSheet = createCache(); +Firebug.Lite.Cache.Element = createCache(); + +// TODO: xxxpedro +Firebug.Lite.Cache.Event = createCache(); + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +var sourceMap = {}; + +// ************************************************************************************************ +Firebug.Lite.Proxy = +{ + // jsonp callbacks + _callbacks: {}, + + /** + * Load a resource, either locally (directly) or externally (via proxy) using + * synchronous XHR calls. Loading external resources requires the proxy plugin to + * be installed and configured (see /plugin/proxy/proxy.php). + */ + load: function(url) + { + var resourceDomain = getDomain(url); + var isLocalResource = + // empty domain means local URL + !resourceDomain || + // same domain means local too + resourceDomain == Firebug.context.window.location.host; // TODO: xxxpedro context + + return isLocalResource ? fetchResource(url) : fetchProxyResource(url); + }, + + /** + * Load a resource using JSONP technique. + */ + loadJSONP: function(url, callback) + { + var script = createGlobalElement("script"), + doc = Firebug.context.document, + + uid = "" + new Date().getTime(), + callbackName = "callback=Firebug.Lite.Proxy._callbacks." + uid, + + jsonpURL = url.indexOf("?") != -1 ? + url + "&" + callbackName : + url + "?" + callbackName; + + Firebug.Lite.Proxy._callbacks[uid] = function(data) + { + if (callback) + callback(data); + + script.parentNode.removeChild(script); + delete Firebug.Lite.Proxy._callbacks[uid]; + }; + + script.src = jsonpURL; + + if (doc.documentElement) + doc.documentElement.appendChild(script); + }, + + /** + * Load a resource using YQL (not reliable). + */ + YQL: function(url, callback) + { + var yql = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" + + encodeURIComponent(url) + "%22&format=xml"; + + this.loadJSONP(yql, function(data) + { + var source = data.results[0]; + + // clean up YQL bogus elements + var match = /\s+

([\s\S]+)<\/p>\s+<\/body>$/.exec(source); + if (match) + source = match[1]; + + console.log(source); + }); + } +}; + +// ************************************************************************************************ + +Firebug.Lite.Proxy.fetchResourceDisabledMessage = + "/* Firebug Lite resource fetching is disabled.\n" + + "To enabled it set the Firebug Lite option \"disableResourceFetching\" to \"false\".\n" + + "More info at http://getfirebug.com/firebuglite#Options */"; + +var fetchResource = function(url) +{ + if (Firebug.disableResourceFetching) + { + var source = sourceMap[url] = Firebug.Lite.Proxy.fetchResourceDisabledMessage; + return source; + } + + if (sourceMap.hasOwnProperty(url)) + return sourceMap[url]; + + // Getting the native XHR object so our calls won't be logged in the Console Panel + var xhr = FBL.getNativeXHRObject(); + xhr.open("get", url, false); + xhr.send(); + + var source = sourceMap[url] = xhr.responseText; + return source; +}; + +var fetchProxyResource = function(url) +{ + if (sourceMap.hasOwnProperty(url)) + return sourceMap[url]; + + var proxyURL = Env.Location.baseDir + "plugin/proxy/proxy.php?url=" + encodeURIComponent(url); + var response = fetchResource(proxyURL); + + try + { + var data = eval("(" + response + ")"); + } + catch(E) + { + return "ERROR: Firebug Lite Proxy plugin returned an invalid response."; + } + + var source = data ? data.contents : ""; + return source; +}; + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Style = +{ +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +Firebug.Lite.Script = function(window) +{ + this.fileName = null; + this.isValid = null; + this.baseLineNumber = null; + this.lineExtent = null; + this.tag = null; + + this.functionName = null; + this.functionSource = null; +}; + +Firebug.Lite.Script.prototype = +{ + isLineExecutable: function(){}, + pcToLine: function(){}, + lineToPc: function(){}, + + toString: function() + { + return "Firebug.Lite.Script"; + } +}; + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + + +Firebug.Lite.Browser = function(window) +{ + this.contentWindow = window; + this.contentDocument = window.document; + this.currentURI = + { + spec: window.location.href + }; +}; + +Firebug.Lite.Browser.prototype = +{ + toString: function() + { + return "Firebug.Lite.Browser"; + } +}; + + +// ************************************************************************************************ +}}); + + +/* See license.txt for terms of usage */ + +/* + http://www.JSON.org/json2.js + 2010-03-20 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, strict: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +// ************************************************************************************************ + +var JSON = window.JSON || {}; + +// ************************************************************************************************ + +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } + +// ************************************************************************************************ +// registration + +FBL.JSON = JSON; + +// ************************************************************************************************ +}()); + +/* See license.txt for terms of usage */ + +(function(){ +// ************************************************************************************************ + +/* Copyright (c) 2010-2011 Marcus Westin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +var store = (function(){ + var api = {}, + win = window, + doc = win.document, + localStorageName = 'localStorage', + globalStorageName = 'globalStorage', + namespace = '__firebug__storejs__', + storage + + api.disabled = false + api.set = function(key, value) {} + api.get = function(key) {} + api.remove = function(key) {} + api.clear = function() {} + api.transact = function(key, transactionFn) { + var val = api.get(key) + if (typeof val == 'undefined') { val = {} } + transactionFn(val) + api.set(key, val) + } + + api.serialize = function(value) { + return JSON.stringify(value) + } + api.deserialize = function(value) { + if (typeof value != 'string') { return undefined } + return JSON.parse(value) + } + + // Functions to encapsulate questionable FireFox 3.6.13 behavior + // when about.config::dom.storage.enabled === false + // See https://github.com/marcuswestin/store.js/issues#issue/13 + function isLocalStorageNameSupported() { + try { return (localStorageName in win && win[localStorageName]) } + catch(err) { return false } + } + + function isGlobalStorageNameSupported() { + try { return (globalStorageName in win && win[globalStorageName] && win[globalStorageName][win.location.hostname]) } + catch(err) { return false } + } + + if (isLocalStorageNameSupported()) { + storage = win[localStorageName] + api.set = function(key, val) { storage.setItem(key, api.serialize(val)) } + api.get = function(key) { return api.deserialize(storage.getItem(key)) } + api.remove = function(key) { storage.removeItem(key) } + api.clear = function() { storage.clear() } + + } else if (isGlobalStorageNameSupported()) { + storage = win[globalStorageName][win.location.hostname] + api.set = function(key, val) { storage[key] = api.serialize(val) } + api.get = function(key) { return api.deserialize(storage[key] && storage[key].value) } + api.remove = function(key) { delete storage[key] } + api.clear = function() { for (var key in storage ) { delete storage[key] } } + + } else if (doc.documentElement.addBehavior) { + var storage = doc.createElement('div') + function withIEStorage(storeFunction) { + return function() { + var args = Array.prototype.slice.call(arguments, 0) + args.unshift(storage) + // See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx + // and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx + // TODO: xxxpedro doc.body is not always available so we must use doc.documentElement. + // We need to make sure this change won't affect the behavior of this library. + doc.documentElement.appendChild(storage) + storage.addBehavior('#default#userData') + storage.load(localStorageName) + var result = storeFunction.apply(api, args) + doc.documentElement.removeChild(storage) + return result + } + } + api.set = withIEStorage(function(storage, key, val) { + storage.setAttribute(key, api.serialize(val)) + storage.save(localStorageName) + }) + api.get = withIEStorage(function(storage, key) { + return api.deserialize(storage.getAttribute(key)) + }) + api.remove = withIEStorage(function(storage, key) { + storage.removeAttribute(key) + storage.save(localStorageName) + }) + api.clear = withIEStorage(function(storage) { + var attributes = storage.XMLDocument.documentElement.attributes + storage.load(localStorageName) + for (var i=0, attr; attr = attributes[i]; i++) { + storage.removeAttribute(attr.name) + } + storage.save(localStorageName) + }) + } + + try { + api.set(namespace, namespace) + if (api.get(namespace) != namespace) { api.disabled = true } + api.remove(namespace) + } catch(e) { + api.disabled = true + } + + return api +})(); + +if (typeof module != 'undefined') { module.exports = store } + + +// ************************************************************************************************ +// registration + +FBL.Store = store; + +// ************************************************************************************************ +})(); + +/* See license.txt for terms of usage */ + +FBL.ns( /**@scope s_selector*/ function() { with (FBL) { +// ************************************************************************************************ + +/* + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +/** + * @name Firebug.Selector + * @namespace + */ + +/** + * @exports Sizzle as Firebug.Selector + */ +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +/**#@+ @ignore */ +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + if ( div.getElementsByClassName("e").length === 0 ) + return; + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +Firebug.Selector = Sizzle; + +/**#@-*/ + +// ************************************************************************************************ +}}); + +/* See license.txt for terms of usage */ + +FBL.ns(function() { with (FBL) { +// ************************************************************************************************ + +// ************************************************************************************************ +// Inspector Module + +var ElementCache = Firebug.Lite.Cache.Element; + +var inspectorTS, inspectorTimer, isInspecting; + +Firebug.Inspector = +{ + create: function() + { + offlineFragment = Env.browser.document.createDocumentFragment(); + + createBoxModelInspector(); + createOutlineInspector(); + }, + + destroy: function() + { + destroyBoxModelInspector(); + destroyOutlineInspector(); + + offlineFragment = null; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Inspect functions + + toggleInspect: function() + { + if (isInspecting) + { + this.stopInspecting(); + } + else + { + Firebug.chrome.inspectButton.changeState("pressed"); + this.startInspecting(); + } + }, + + startInspecting: function() + { + isInspecting = true; + + Firebug.chrome.selectPanel("HTML"); + + createInspectorFrame(); + + var size = Firebug.browser.getWindowScrollSize(); + + fbInspectFrame.style.width = size.width + "px"; + fbInspectFrame.style.height = size.height + "px"; + + //addEvent(Firebug.browser.document.documentElement, "mousemove", Firebug.Inspector.onInspectingBody); + + addEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting); + addEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick); + }, + + stopInspecting: function() + { + isInspecting = false; + + if (outlineVisible) this.hideOutline(); + removeEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting); + removeEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick); + + destroyInspectorFrame(); + + Firebug.chrome.inspectButton.restore(); + + if (Firebug.chrome.type == "popup") + Firebug.chrome.node.focus(); + }, + + onInspectingClick: function(e) + { + fbInspectFrame.style.display = "none"; + var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY); + fbInspectFrame.style.display = "block"; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + //Firebug.Console.log(targ); + Firebug.Inspector.stopInspecting(); + }, + + onInspecting: function(e) + { + if (new Date().getTime() - lastInspecting > 30) + { + fbInspectFrame.style.display = "none"; + var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY); + fbInspectFrame.style.display = "block"; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + if (targ.nodeName.toLowerCase() == "body") return; + + //Firebug.Console.log(e.clientX, e.clientY, targ); + Firebug.Inspector.drawOutline(targ); + + if (ElementCache(targ)) + { + var target = ""+ElementCache.key(targ); + var lazySelect = function() + { + inspectorTS = new Date().getTime(); + + if (Firebug.HTML) + Firebug.HTML.selectTreeNode(""+ElementCache.key(targ)); + }; + + if (inspectorTimer) + { + clearTimeout(inspectorTimer); + inspectorTimer = null; + } + + if (new Date().getTime() - inspectorTS > 200) + setTimeout(lazySelect, 0); + else + inspectorTimer = setTimeout(lazySelect, 300); + } + + lastInspecting = new Date().getTime(); + } + }, + + // TODO: xxxpedro remove this? + onInspectingBody: function(e) + { + if (new Date().getTime() - lastInspecting > 30) + { + var targ = e.target; + + // Avoid inspecting the outline, and the FirebugUI + var id = targ.id; + if (id && /^fbOutline\w$/.test(id)) return; + if (id == "FirebugUI") return; + + // Avoid looking at text nodes in Opera + while (targ.nodeType != 1) targ = targ.parentNode; + + if (targ.nodeName.toLowerCase() == "body") return; + + //Firebug.Console.log(e.clientX, e.clientY, targ); + Firebug.Inspector.drawOutline(targ); + + if (ElementCache.has(targ)) + FBL.Firebug.HTML.selectTreeNode(""+ElementCache.key(targ)); + + lastInspecting = new Date().getTime(); + } + }, + + /** + * + * llttttttrr + * llttttttrr + * ll rr + * ll rr + * llbbbbbbrr + * llbbbbbbrr + */ + drawOutline: function(el) + { + var border = 2; + var scrollbarSize = 17; + + var windowSize = Firebug.browser.getWindowSize(); + var scrollSize = Firebug.browser.getWindowScrollSize(); + var scrollPosition = Firebug.browser.getWindowScrollPosition(); + + var box = Firebug.browser.getElementBox(el); + + var top = box.top; + var left = box.left; + var height = box.height; + var width = box.width; + + var freeHorizontalSpace = scrollPosition.left + windowSize.width - left - width - + (!isIE && scrollSize.height > windowSize.height ? // is *vertical* scrollbar visible + scrollbarSize : 0); + + var freeVerticalSpace = scrollPosition.top + windowSize.height - top - height - + (!isIE && scrollSize.width > windowSize.width ? // is *horizontal* scrollbar visible + scrollbarSize : 0); + + var numVerticalBorders = freeVerticalSpace > 0 ? 2 : 1; + + var o = outlineElements; + var style; + + style = o.fbOutlineT.style; + style.top = top-border + "px"; + style.left = left + "px"; + style.height = border + "px"; // TODO: on initialize() + style.width = width + "px"; + + style = o.fbOutlineL.style; + style.top = top-border + "px"; + style.left = left-border + "px"; + style.height = height+ numVerticalBorders*border + "px"; + style.width = border + "px"; // TODO: on initialize() + + style = o.fbOutlineB.style; + if (freeVerticalSpace > 0) + { + style.top = top+height + "px"; + style.left = left + "px"; + style.width = width + "px"; + //style.height = border + "px"; // TODO: on initialize() or worst case? + } + else + { + style.top = -2*border + "px"; + style.left = -2*border + "px"; + style.width = border + "px"; + //style.height = border + "px"; + } + + style = o.fbOutlineR.style; + if (freeHorizontalSpace > 0) + { + style.top = top-border + "px"; + style.left = left+width + "px"; + style.height = height + numVerticalBorders*border + "px"; + style.width = (freeHorizontalSpace < border ? freeHorizontalSpace : border) + "px"; + } + else + { + style.top = -2*border + "px"; + style.left = -2*border + "px"; + style.height = border + "px"; + style.width = border + "px"; + } + + if (!outlineVisible) this.showOutline(); + }, + + hideOutline: function() + { + if (!outlineVisible) return; + + for (var name in outline) + offlineFragment.appendChild(outlineElements[name]); + + outlineVisible = false; + }, + + showOutline: function() + { + if (outlineVisible) return; + + if (boxModelVisible) this.hideBoxModel(); + + for (var name in outline) + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(outlineElements[name]); + + outlineVisible = true; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + // Box Model + + drawBoxModel: function(el) + { + // avoid error when the element is not attached a document + if (!el || !el.parentNode) + return; + + var box = Firebug.browser.getElementBox(el); + + var windowSize = Firebug.browser.getWindowSize(); + var scrollPosition = Firebug.browser.getWindowScrollPosition(); + + // element may be occluded by the chrome, when in frame mode + var offsetHeight = Firebug.chrome.type == "frame" ? Firebug.context.persistedState.height : 0; + + // if element box is not inside the viewport, don't draw the box model + if (box.top > scrollPosition.top + windowSize.height - offsetHeight || + box.left > scrollPosition.left + windowSize.width || + scrollPosition.top > box.top + box.height || + scrollPosition.left > box.left + box.width ) + return; + + var top = box.top; + var left = box.left; + var height = box.height; + var width = box.width; + + var margin = Firebug.browser.getMeasurementBox(el, "margin"); + var padding = Firebug.browser.getMeasurementBox(el, "padding"); + var border = Firebug.browser.getMeasurementBox(el, "border"); + + boxModelStyle.top = top - margin.top + "px"; + boxModelStyle.left = left - margin.left + "px"; + boxModelStyle.height = height + margin.top + margin.bottom + "px"; + boxModelStyle.width = width + margin.left + margin.right + "px"; + + boxBorderStyle.top = margin.top + "px"; + boxBorderStyle.left = margin.left + "px"; + boxBorderStyle.height = height + "px"; + boxBorderStyle.width = width + "px"; + + boxPaddingStyle.top = margin.top + border.top + "px"; + boxPaddingStyle.left = margin.left + border.left + "px"; + boxPaddingStyle.height = height - border.top - border.bottom + "px"; + boxPaddingStyle.width = width - border.left - border.right + "px"; + + boxContentStyle.top = margin.top + border.top + padding.top + "px"; + boxContentStyle.left = margin.left + border.left + padding.left + "px"; + boxContentStyle.height = height - border.top - padding.top - padding.bottom - border.bottom + "px"; + boxContentStyle.width = width - border.left - padding.left - padding.right - border.right + "px"; + + if (!boxModelVisible) this.showBoxModel(); + }, + + hideBoxModel: function() + { + if (!boxModelVisible) return; + + offlineFragment.appendChild(boxModel); + boxModelVisible = false; + }, + + showBoxModel: function() + { + if (boxModelVisible) return; + + if (outlineVisible) this.hideOutline(); + + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(boxModel); + boxModelVisible = true; + } + +}; + +// ************************************************************************************************ +// Inspector Internals + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Shared variables + + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +// Internal variables + +var offlineFragment = null; + +var boxModelVisible = false; + +var boxModel, boxModelStyle, + boxMargin, boxMarginStyle, + boxBorder, boxBorderStyle, + boxPadding, boxPaddingStyle, + boxContent, boxContentStyle; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;"; +var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;"; + +var inspectStyle = resetStyle + "z-index: 2147483500;"; +var inspectFrameStyle = resetStyle + "z-index: 2147483550; top:0; left:0; background:url(" + + Env.Location.skinDir + "pixel_transparent.gif);"; + +//if (Env.Options.enableTrace) inspectFrameStyle = resetStyle + "z-index: 2147483550; top: 0; left: 0; background: #ff0; opacity: 0.05; _filter: alpha(opacity=5);"; + +var inspectModelOpacity = isIE ? "filter:alpha(opacity=80);" : "opacity:0.8;"; +var inspectModelStyle = inspectStyle + inspectModelOpacity; +var inspectMarginStyle = inspectStyle + "background: #EDFF64; height:100%; width:100%;"; +var inspectBorderStyle = inspectStyle + "background: #666;"; +var inspectPaddingStyle = inspectStyle + "background: SlateBlue;"; +var inspectContentStyle = inspectStyle + "background: SkyBlue;"; + + +var outlineStyle = { + fbHorizontalLine: "background: #3875D7;height: 2px;", + fbVerticalLine: "background: #3875D7;width: 2px;" +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var lastInspecting = 0; +var fbInspectFrame = null; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var outlineVisible = false; +var outlineElements = {}; +var outline = { + "fbOutlineT": "fbHorizontalLine", + "fbOutlineL": "fbVerticalLine", + "fbOutlineB": "fbHorizontalLine", + "fbOutlineR": "fbVerticalLine" +}; + + +var getInspectingTarget = function() +{ + +}; + +// ************************************************************************************************ +// Section + +var createInspectorFrame = function createInspectorFrame() +{ + fbInspectFrame = createGlobalElement("div"); + fbInspectFrame.id = "fbInspectFrame"; + fbInspectFrame.firebugIgnore = true; + fbInspectFrame.style.cssText = inspectFrameStyle; + Firebug.browser.document.getElementsByTagName("body")[0].appendChild(fbInspectFrame); +}; + +var destroyInspectorFrame = function destroyInspectorFrame() +{ + if (fbInspectFrame) + { + Firebug.browser.document.getElementsByTagName("body")[0].removeChild(fbInspectFrame); + fbInspectFrame = null; + } +}; + +var createOutlineInspector = function createOutlineInspector() +{ + for (var name in outline) + { + var el = outlineElements[name] = createGlobalElement("div"); + el.id = name; + el.firebugIgnore = true; + el.style.cssText = inspectStyle + outlineStyle[outline[name]]; + offlineFragment.appendChild(el); + } +}; + +var destroyOutlineInspector = function destroyOutlineInspector() +{ + for (var name in outline) + { + var el = outlineElements[name]; + el.parentNode.removeChild(el); + } +}; + +var createBoxModelInspector = function createBoxModelInspector() +{ + boxModel = createGlobalElement("div"); + boxModel.id = "fbBoxModel"; + boxModel.firebugIgnore = true; + boxModelStyle = boxModel.style; + boxModelStyle.cssText = inspectModelStyle; + + boxMargin = createGlobalElement("div"); + boxMargin.id = "fbBoxMargin"; + boxMarginStyle = boxMargin.style; + boxMarginStyle.cssText = inspectMarginStyle; + boxModel.appendChild(boxMargin); + + boxBorder = createGlobalElement("div"); + boxBorder.id = "fbBoxBorder"; + boxBorderStyle = boxBorder.style; + boxBorderStyle.cssText = inspectBorderStyle; + boxModel.appendChild(boxBorder); + + boxPadding = createGlobalElement("div"); + boxPadding.id = "fbBoxPadding"; + boxPaddingStyle = boxPadding.style; + boxPaddingStyle.cssText = inspectPaddingStyle; + boxModel.appendChild(boxPadding); + + boxContent = createGlobalElement("div"); + boxContent.id = "fbBoxContent"; + boxContentStyle = boxContent.style; + boxContentStyle.cssText = inspectContentStyle; + boxModel.appendChild(boxContent); + + offlineFragment.appendChild(boxModel); +}; + +var destroyBoxModelInspector = function destroyBoxModelInspector() +{ + boxModel.parentNode.removeChild(boxModel); +}; + +// ************************************************************************************************ +// Section + + + + +// ************************************************************************************************ +}}); + +// Problems in IE +// FIXED - eval return +// FIXED - addEventListener problem in IE +// FIXED doc.createRange? +// +// class reserved word +// test all honza examples in IE6 and IE7 + + +/* See license.txt for terms of usage */ + +( /** @scope s_domplate */ function() { + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +FBL.DomplateTag = function DomplateTag(tagName) +{ + this.tagName = tagName; +}; + +/** + * @class + * @extends FBL.DomplateTag + */ +FBL.DomplateEmbed = function DomplateEmbed() +{ +}; + +/** + * @class + * @extends FBL.DomplateTag + */ +FBL.DomplateLoop = function DomplateLoop() +{ +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var DomplateTag = FBL.DomplateTag; +var DomplateEmbed = FBL.DomplateEmbed; +var DomplateLoop = FBL.DomplateLoop; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +var womb = null; + +FBL.domplate = function() +{ + var lastSubject; + for (var i = 0; i < arguments.length; ++i) + lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i]; + + for (var name in lastSubject) + { + var val = lastSubject[name]; + if (isTag(val)) + val.tag.subject = lastSubject; + } + + return lastSubject; +}; + +var domplate = FBL.domplate; + +FBL.domplate.context = function(context, fn) +{ + var lastContext = domplate.lastContext; + domplate.topContext = context; + fn.apply(context); + domplate.topContext = lastContext; +}; + +FBL.TAG = function() +{ + var embed = new DomplateEmbed(); + return embed.merge(arguments); +}; + +FBL.FOR = function() +{ + var loop = new DomplateLoop(); + return loop.merge(arguments); +}; + +FBL.DomplateTag.prototype = +{ + merge: function(args, oldTag) + { + if (oldTag) + this.tagName = oldTag.tagName; + + this.context = oldTag ? oldTag.context : null; + this.subject = oldTag ? oldTag.subject : null; + this.attrs = oldTag ? copyObject(oldTag.attrs) : {}; + this.classes = oldTag ? copyObject(oldTag.classes) : {}; + this.props = oldTag ? copyObject(oldTag.props) : null; + this.listeners = oldTag ? copyArray(oldTag.listeners) : null; + this.children = oldTag ? copyArray(oldTag.children) : []; + this.vars = oldTag ? copyArray(oldTag.vars) : []; + + var attrs = args.length ? args[0] : null; + var hasAttrs = typeof(attrs) == "object" && !isTag(attrs); + + this.children = []; + + if (domplate.topContext) + this.context = domplate.topContext; + + if (args.length) + parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children); + + if (hasAttrs) + this.parseAttrs(attrs); + + return creator(this, DomplateTag); + }, + + parseAttrs: function(args) + { + for (var name in args) + { + var val = parseValue(args[name]); + readPartNames(val, this.vars); + + if (name.indexOf("on") == 0) + { + var eventName = name.substr(2); + if (!this.listeners) + this.listeners = []; + this.listeners.push(eventName, val); + } + else if (name.indexOf("_") == 0) + { + var propName = name.substr(1); + if (!this.props) + this.props = {}; + this.props[propName] = val; + } + else if (name.indexOf("$") == 0) + { + var className = name.substr(1); + if (!this.classes) + this.classes = {}; + this.classes[className] = val; + } + else + { + if (name == "class" && this.attrs.hasOwnProperty(name) ) + this.attrs[name] += " " + val; + else + this.attrs[name] = val; + } + } + }, + + compile: function() + { + if (this.renderMarkup) + return; + + this.compileMarkup(); + this.compileDOM(); + + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderMarkup: ", this.renderMarkup); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderDOM:", this.renderDOM); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate domArgs:", this.domArgs); + }, + + compileMarkup: function() + { + this.markupArgs = []; + var topBlock = [], topOuts = [], blocks = [], info = {args: this.markupArgs, argIndex: 0}; + + this.generateMarkup(topBlock, topOuts, blocks, info); + this.addCode(topBlock, topOuts, blocks); + + var fnBlock = ['r=(function (__code__, __context__, __in__, __out__']; + for (var i = 0; i < info.argIndex; ++i) + fnBlock.push(', s', i); + fnBlock.push(') {'); + + if (this.subject) + fnBlock.push('with (this) {'); + if (this.context) + fnBlock.push('with (__context__) {'); + fnBlock.push('with (__in__) {'); + + fnBlock.push.apply(fnBlock, blocks); + + if (this.subject) + fnBlock.push('}'); + if (this.context) + fnBlock.push('}'); + + fnBlock.push('}})'); + + function __link__(tag, code, outputs, args) + { + if (!tag || !tag.tag) + return; + + tag.tag.compile(); + + var tagOutputs = []; + var markupArgs = [code, tag.tag.context, args, tagOutputs]; + markupArgs.push.apply(markupArgs, tag.tag.markupArgs); + tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs); + + outputs.push(tag); + outputs.push(tagOutputs); + } + + function __escape__(value) + { + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); + } + + function __loop__(iter, outputs, fn) + { + var iterOuts = []; + outputs.push(iterOuts); + + if (iter instanceof Array) + iter = new ArrayIterator(iter); + + try + { + while (1) + { + var value = iter.next(); + var itemOuts = [0,0]; + iterOuts.push(itemOuts); + fn.apply(this, [value, itemOuts]); + } + } + catch (exc) + { + if (exc != StopIteration) + throw exc; + } + } + + var js = fnBlock.join(""); + var r = null; + eval(js); + this.renderMarkup = r; + }, + + getVarNames: function(args) + { + if (this.vars) + args.push.apply(args, this.vars); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + child.tag.getVarNames(args); + else if (child instanceof Parts) + { + for (var i = 0; i < child.parts.length; ++i) + { + if (child.parts[i] instanceof Variable) + { + var name = child.parts[i].name; + var names = name.split("."); + args.push(names[0]); + } + } + } + } + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + topBlock.push(',"<', this.tagName, '"'); + + for (var name in this.attrs) + { + if (name != "class") + { + var val = this.attrs[name]; + topBlock.push(', " ', name, '=\\""'); + addParts(val, ',', topBlock, info, true); + topBlock.push(', "\\""'); + } + } + + if (this.listeners) + { + for (var i = 0; i < this.listeners.length; i += 2) + readPartNames(this.listeners[i+1], topOuts); + } + + if (this.props) + { + for (var name in this.props) + readPartNames(this.props[name], topOuts); + } + + if ( this.attrs.hasOwnProperty("class") || this.classes) + { + topBlock.push(', " class=\\""'); + if (this.attrs.hasOwnProperty("class")) + addParts(this.attrs["class"], ',', topBlock, info, true); + topBlock.push(', " "'); + for (var name in this.classes) + { + topBlock.push(', ('); + addParts(this.classes[name], '', topBlock, info); + topBlock.push(' ? "', name, '" + " " : "")'); + } + topBlock.push(', "\\""'); + } + topBlock.push(',">"'); + + this.generateChildMarkup(topBlock, topOuts, blocks, info); + topBlock.push(',""'); + }, + + generateChildMarkup: function(topBlock, topOuts, blocks, info) + { + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + child.tag.generateMarkup(topBlock, topOuts, blocks, info); + else + addParts(child, ',', topBlock, info, true); + } + }, + + addCode: function(topBlock, topOuts, blocks) + { + if (topBlock.length) + blocks.push('__code__.push(""', topBlock.join(""), ');'); + if (topOuts.length) + blocks.push('__out__.push(', topOuts.join(","), ');'); + topBlock.splice(0, topBlock.length); + topOuts.splice(0, topOuts.length); + }, + + addLocals: function(blocks) + { + var varNames = []; + this.getVarNames(varNames); + + var map = {}; + for (var i = 0; i < varNames.length; ++i) + { + var name = varNames[i]; + if ( map.hasOwnProperty(name) ) + continue; + + map[name] = 1; + var names = name.split("."); + blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';'); + } + }, + + compileDOM: function() + { + var path = []; + var blocks = []; + this.domArgs = []; + path.embedIndex = 0; + path.loopIndex = 0; + path.staticIndex = 0; + path.renderIndex = 0; + var nodeCount = this.generateDOM(path, blocks, this.domArgs); + + var fnBlock = ['r=(function (root, context, o']; + + for (var i = 0; i < path.staticIndex; ++i) + fnBlock.push(', ', 's'+i); + + for (var i = 0; i < path.renderIndex; ++i) + fnBlock.push(', ', 'd'+i); + + fnBlock.push(') {'); + for (var i = 0; i < path.loopIndex; ++i) + fnBlock.push('var l', i, ' = 0;'); + for (var i = 0; i < path.embedIndex; ++i) + fnBlock.push('var e', i, ' = 0;'); + + if (this.subject) + fnBlock.push('with (this) {'); + if (this.context) + fnBlock.push('with (context) {'); + + fnBlock.push(blocks.join("")); + + if (this.subject) + fnBlock.push('}'); + if (this.context) + fnBlock.push('}'); + + fnBlock.push('return ', nodeCount, ';'); + fnBlock.push('})'); + + function __bind__(object, fn) + { + return function(event) { return fn.apply(object, [event]); }; + } + + function __link__(node, tag, args) + { + if (!tag || !tag.tag) + return; + + tag.tag.compile(); + + var domArgs = [node, tag.tag.context, 0]; + domArgs.push.apply(domArgs, tag.tag.domArgs); + domArgs.push.apply(domArgs, args); + //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate__link__ domArgs:", domArgs); + return tag.tag.renderDOM.apply(tag.tag.subject, domArgs); + } + + var self = this; + function __loop__(iter, fn) + { + var nodeCount = 0; + for (var i = 0; i < iter.length; ++i) + { + iter[i][0] = i; + iter[i][1] = nodeCount; + nodeCount += fn.apply(this, iter[i]); + //if (FBTrace.DBG_DOM) FBTrace.sysout("nodeCount", nodeCount); + } + return nodeCount; + } + + function __path__(parent, offset) + { + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate __path__ offset: "+ offset+"\n"); + var root = parent; + + for (var i = 2; i < arguments.length; ++i) + { + var index = arguments[i]; + if (i == 3) + index += offset; + + if (index == -1) + parent = parent.parentNode; + else + parent = parent.childNodes[index]; + } + + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate: "+arguments[2]+", root: "+ root+", parent: "+ parent+"\n"); + return parent; + } + + var js = fnBlock.join(""); + //if (FBTrace.DBG_DOM) FBTrace.sysout(js.replace(/(\;|\{)/g, "$1\n")); + var r = null; + eval(js); + this.renderDOM = r; + }, + + generateDOM: function(path, blocks, args) + { + if (this.listeners || this.props) + this.generateNodePath(path, blocks); + + if (this.listeners) + { + for (var i = 0; i < this.listeners.length; i += 2) + { + var val = this.listeners[i+1]; + var arg = generateArg(val, path, args); + //blocks.push('node.addEventListener("', this.listeners[i], '", __bind__(this, ', arg, '), false);'); + blocks.push('addEvent(node, "', this.listeners[i], '", __bind__(this, ', arg, '), false);'); + } + } + + if (this.props) + { + for (var name in this.props) + { + var val = this.props[name]; + var arg = generateArg(val, path, args); + blocks.push('node.', name, ' = ', arg, ';'); + } + } + + this.generateChildDOM(path, blocks, args); + return 1; + }, + + generateNodePath: function(path, blocks) + { + blocks.push("var node = __path__(root, o"); + for (var i = 0; i < path.length; ++i) + blocks.push(",", path[i]); + blocks.push(");"); + }, + + generateChildDOM: function(path, blocks, args) + { + path.push(0); + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + if (isTag(child)) + path[path.length-1] += '+' + child.tag.generateDOM(path, blocks, args); + else + path[path.length-1] += '+1'; + } + path.pop(); + } +}; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +FBL.DomplateEmbed.prototype = copyObject(FBL.DomplateTag.prototype, +/** @lends FBL.DomplateEmbed.prototype */ +{ + merge: function(args, oldTag) + { + this.value = oldTag ? oldTag.value : parseValue(args[0]); + this.attrs = oldTag ? oldTag.attrs : {}; + this.vars = oldTag ? copyArray(oldTag.vars) : []; + + var attrs = args[1]; + for (var name in attrs) + { + var val = parseValue(attrs[name]); + this.attrs[name] = val; + readPartNames(val, this.vars); + } + + return creator(this, DomplateEmbed); + }, + + getVarNames: function(names) + { + if (this.value instanceof Parts) + names.push(this.value.parts[0].name); + + if (this.vars) + names.push.apply(names, this.vars); + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + this.addCode(topBlock, topOuts, blocks); + + blocks.push('__link__('); + addParts(this.value, '', blocks, info); + blocks.push(', __code__, __out__, {'); + + var lastName = null; + for (var name in this.attrs) + { + if (lastName) + blocks.push(','); + lastName = name; + + var val = this.attrs[name]; + blocks.push('"', name, '":'); + addParts(val, '', blocks, info); + } + + blocks.push('});'); + //this.generateChildMarkup(topBlock, topOuts, blocks, info); + }, + + generateDOM: function(path, blocks, args) + { + var embedName = 'e'+path.embedIndex++; + + this.generateNodePath(path, blocks); + + var valueName = 'd' + path.renderIndex++; + var argsName = 'd' + path.renderIndex++; + blocks.push(embedName + ' = __link__(node, ', valueName, ', ', argsName, ');'); + + return embedName; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +FBL.DomplateLoop.prototype = copyObject(FBL.DomplateTag.prototype, +/** @lends FBL.DomplateLoop.prototype */ +{ + merge: function(args, oldTag) + { + this.varName = oldTag ? oldTag.varName : args[0]; + this.iter = oldTag ? oldTag.iter : parseValue(args[1]); + this.vars = []; + + this.children = oldTag ? copyArray(oldTag.children) : []; + + var offset = Math.min(args.length, 2); + parseChildren(args, offset, this.vars, this.children); + + return creator(this, DomplateLoop); + }, + + getVarNames: function(names) + { + if (this.iter instanceof Parts) + names.push(this.iter.parts[0].name); + + DomplateTag.prototype.getVarNames.apply(this, [names]); + }, + + generateMarkup: function(topBlock, topOuts, blocks, info) + { + this.addCode(topBlock, topOuts, blocks); + + var iterName; + if (this.iter instanceof Parts) + { + var part = this.iter.parts[0]; + iterName = part.name; + + if (part.format) + { + for (var i = 0; i < part.format.length; ++i) + iterName = part.format[i] + "(" + iterName + ")"; + } + } + else + iterName = this.iter; + + blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {'); + this.generateChildMarkup(topBlock, topOuts, blocks, info); + this.addCode(topBlock, topOuts, blocks); + blocks.push('}]);'); + }, + + generateDOM: function(path, blocks, args) + { + var iterName = 'd'+path.renderIndex++; + var counterName = 'i'+path.loopIndex; + var loopName = 'l'+path.loopIndex++; + + if (!path.length) + path.push(-1, 0); + + var preIndex = path.renderIndex; + path.renderIndex = 0; + + var nodeCount = 0; + + var subBlocks = []; + var basePath = path[path.length-1]; + for (var i = 0; i < this.children.length; ++i) + { + path[path.length-1] = basePath+'+'+loopName+'+'+nodeCount; + + var child = this.children[i]; + if (isTag(child)) + nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args); + else + nodeCount += '+1'; + } + + path[path.length-1] = basePath+'+'+loopName; + + blocks.push(loopName,' = __loop__.apply(this, [', iterName, ', function(', counterName,',',loopName); + for (var i = 0; i < path.renderIndex; ++i) + blocks.push(',d'+i); + blocks.push(') {'); + blocks.push(subBlocks.join("")); + blocks.push('return ', nodeCount, ';'); + blocks.push('}]);'); + + path.renderIndex = preIndex; + + return loopName; + } +}); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +function Variable(name, format) +{ + this.name = name; + this.format = format; +} + +/** @class */ +function Parts(parts) +{ + this.parts = parts; +} + +// ************************************************************************************************ + +function parseParts(str) +{ + var re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g; + var index = 0; + var parts = []; + + var m; + while (m = re.exec(str)) + { + var pre = str.substr(index, (re.lastIndex-m[0].length)-index); + if (pre) + parts.push(pre); + + var expr = m[1].split("|"); + parts.push(new Variable(expr[0], expr.slice(1))); + index = re.lastIndex; + } + + if (!index) + return str; + + var post = str.substr(index); + if (post) + parts.push(post); + + return new Parts(parts); +} + +function parseValue(val) +{ + return typeof(val) == 'string' ? parseParts(val) : val; +} + +function parseChildren(args, offset, vars, children) +{ + for (var i = offset; i < args.length; ++i) + { + var val = parseValue(args[i]); + children.push(val); + readPartNames(val, vars); + } +} + +function readPartNames(val, vars) +{ + if (val instanceof Parts) + { + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + vars.push(part.name); + } + } +} + +function generateArg(val, path, args) +{ + if (val instanceof Parts) + { + var vals = []; + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + { + var varName = 'd'+path.renderIndex++; + if (part.format) + { + for (var j = 0; j < part.format.length; ++j) + varName = part.format[j] + '(' + varName + ')'; + } + + vals.push(varName); + } + else + vals.push('"'+part.replace(/"/g, '\\"')+'"'); + } + + return vals.join('+'); + } + else + { + args.push(val); + return 's' + path.staticIndex++; + } +} + +function addParts(val, delim, block, info, escapeIt) +{ + var vals = []; + if (val instanceof Parts) + { + for (var i = 0; i < val.parts.length; ++i) + { + var part = val.parts[i]; + if (part instanceof Variable) + { + var partName = part.name; + if (part.format) + { + for (var j = 0; j < part.format.length; ++j) + partName = part.format[j] + "(" + partName + ")"; + } + + if (escapeIt) + vals.push("__escape__(" + partName + ")"); + else + vals.push(partName); + } + else + vals.push('"'+ part + '"'); + } + } + else if (isTag(val)) + { + info.args.push(val); + vals.push('s'+info.argIndex++); + } + else + vals.push('"'+ val + '"'); + + var parts = vals.join(delim); + if (parts) + block.push(delim, parts); +} + +function isTag(obj) +{ + return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag; +} + +function creator(tag, cons) +{ + var fn = new Function( + "var tag = arguments.callee.tag;" + + "var cons = arguments.callee.cons;" + + "var newTag = new cons();" + + "return newTag.merge(arguments, tag);"); + + fn.tag = tag; + fn.cons = cons; + extend(fn, Renderer); + + return fn; +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +function copyArray(oldArray) +{ + var ary = []; + if (oldArray) + for (var i = 0; i < oldArray.length; ++i) + ary.push(oldArray[i]); + return ary; +} + +function copyObject(l, r) +{ + var m = {}; + extend(m, l); + extend(m, r); + return m; +} + +function extend(l, r) +{ + for (var n in r) + l[n] = r[n]; +} + +function addEvent(object, name, handler) +{ + if (document.all) + object.attachEvent("on"+name, handler); + else + object.addEventListener(name, handler, false); +} + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/** @class */ +function ArrayIterator(array) +{ + var index = -1; + + this.next = function() + { + if (++index >= array.length) + throw StopIteration; + + return array[index]; + }; +} + +/** @class */ +function StopIteration() {} + +FBL.$break = function() +{ + throw StopIteration; +}; + +// ************************************************************************************************ + +/** @namespace */ +var Renderer = +{ + renderHTML: function(args, outputs, self) + { + var code = []; + var markupArgs = [code, this.tag.context, args, outputs]; + markupArgs.push.apply(markupArgs, this.tag.markupArgs); + this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs); + return code.join(""); + }, + + insertRows: function(args, before, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var doc = before.ownerDocument; + var div = doc.createElement("div"); + div.innerHTML = ""+html+"
"; + + var tbody = div.firstChild.firstChild; + var parent = before.tagName == "TR" ? before.parentNode : before; + var after = before.tagName == "TR" ? before.nextSibling : null; + + var firstRow = tbody.firstChild, lastRow; + while (tbody.firstChild) + { + lastRow = tbody.firstChild; + if (after) + parent.insertBefore(lastRow, after); + else + parent.appendChild(lastRow); + } + + var offset = 0; + if (before.tagName == "TR") + { + var node = firstRow.parentNode.firstChild; + for (; node && node != firstRow; node = node.nextSibling) + ++offset; + } + + var domArgs = [firstRow, this.tag.context, offset]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + return [firstRow, lastRow]; + }, + + insertBefore: function(args, before, self) + { + return this.insertNode(args, before.ownerDocument, before, false, self); + }, + + insertAfter: function(args, after, self) + { + return this.insertNode(args, after.ownerDocument, after, true, self); + }, + + insertNode: function(args, doc, element, isAfter, self) + { + if (!args) + args = {}; + + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + //if (FBTrace.DBG_DOM) + // FBTrace.sysout("domplate.insertNode html: "+html+"\n"); + + var doc = element.ownerDocument; + if (!womb || womb.ownerDocument != doc) + womb = doc.createElement("div"); + + womb.innerHTML = html; + + var root = womb.firstChild; + if (isAfter) + { + while (womb.firstChild) + if (element.nextSibling) + element.parentNode.insertBefore(womb.firstChild, element.nextSibling); + else + element.parentNode.appendChild(womb.firstChild); + } + else + { + while (womb.lastChild) + element.parentNode.insertBefore(womb.lastChild, element); + } + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + //if (FBTrace.DBG_DOM) + // FBTrace.sysout("domplate.insertNode domArgs:", domArgs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + }, + /**/ + + /* + insertAfter: function(args, before, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var doc = before.ownerDocument; + if (!womb || womb.ownerDocument != doc) + womb = doc.createElement("div"); + + womb.innerHTML = html; + + var root = womb.firstChild; + while (womb.firstChild) + if (before.nextSibling) + before.parentNode.insertBefore(womb.firstChild, before.nextSibling); + else + before.parentNode.appendChild(womb.firstChild); + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + this.tag.renderDOM.apply(self ? self : (this.tag.subject ? this.tag.subject : null), + domArgs); + + return root; + }, + /**/ + + replace: function(args, parent, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + + var root; + if (parent.nodeType == 1) + { + parent.innerHTML = html; + root = parent.firstChild; + } + else + { + if (!parent || parent.nodeType != 9) + parent = document; + + if (!womb || womb.ownerDocument != parent) + womb = parent.createElement("div"); + womb.innerHTML = html; + + root = womb.firstChild; + //womb.removeChild(root); + } + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + }, + + append: function(args, parent, self) + { + this.tag.compile(); + + var outputs = []; + var html = this.renderHTML(args, outputs, self); + //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate.append html: "+html+"\n"); + + if (!womb || womb.ownerDocument != parent.ownerDocument) + womb = parent.ownerDocument.createElement("div"); + womb.innerHTML = html; + + // TODO: xxxpedro domplate port to Firebug + var root = womb.firstChild; + while (womb.firstChild) + parent.appendChild(womb.firstChild); + + // clearing element reference to avoid reference error in IE8 when switching contexts + womb = null; + + var domArgs = [root, this.tag.context, 0]; + domArgs.push.apply(domArgs, this.tag.domArgs); + domArgs.push.apply(domArgs, outputs); + + //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate append domArgs:", domArgs); + this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs); + + return root; + } +}; + +// ************************************************************************************************ + +function defineTags() +{ + for (var i = 0; i < arguments.length; ++i) + { + var tagName = arguments[i]; + var fn = new Function("var newTag = new arguments.callee.DomplateTag('"+tagName+"'); return newTag.merge(arguments);"); + fn.DomplateTag = DomplateTag; + + var fnName = tagName.toUpperCase(); + FBL[fnName] = fn; + } +} + +defineTags( + "a", "button", "br", "canvas", "code", "col", "colgroup", "div", "fieldset", "form", "h1", "h2", "h3", "hr", + "img", "input", "label", "legend", "li", "ol", "optgroup", "option", "p", "pre", "select", + "span", "strong", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul", "iframe" +); + +})(); + + +/* See license.txt for terms of usage */ + +var FirebugReps = FBL.ns(function() { with (FBL) { + + +// ************************************************************************************************ +// Common Tags + +var OBJECTBOX = this.OBJECTBOX = + SPAN({"class": "objectBox objectBox-$className"}); + +var OBJECTBLOCK = this.OBJECTBLOCK = + DIV({"class": "objectBox objectBox-$className"}); + +var OBJECTLINK = this.OBJECTLINK = isIE6 ? // IE6 object link representation + A({ + "class": "objectLink objectLink-$className a11yFocus", + href: "javascript:void(0)", + // workaround to show XPath (a better approach would use the tooltip on mouseover, + // so the XPath information would be calculated dynamically, but we need to create + // a tooltip class/wrapper around Menu or InfoTip) + title: "$object|FBL.getElementXPath", + _repObject: "$object" + }) + : // Other browsers + A({ + "class": "objectLink objectLink-$className a11yFocus", + // workaround to show XPath (a better approach would use the tooltip on mouseover, + // so the XPath information would be calculated dynamically, but we need to create + // a tooltip class/wrapper around Menu or InfoTip) + title: "$object|FBL.getElementXPath", + _repObject: "$object" + }); + + +// ************************************************************************************************ + +this.Undefined = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("undefined"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "undefined", + + supportsObject: function(object, type) + { + return type == "undefined"; + } +}); + +// ************************************************************************************************ + +this.Null = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("null"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "null", + + supportsObject: function(object, type) + { + return object == null; + } +}); + +// ************************************************************************************************ + +this.Nada = domplate(Firebug.Rep, +{ + tag: SPAN(""), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "nada" +}); + +// ************************************************************************************************ + +this.Number = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("$object"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "number", + + supportsObject: function(object, type) + { + return type == "boolean" || type == "number"; + } +}); + +// ************************************************************************************************ + +this.String = domplate(Firebug.Rep, +{ + tag: OBJECTBOX(""$object""), + + shortTag: OBJECTBOX(""$object|cropString""), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "string", + + supportsObject: function(object, type) + { + return type == "string"; + } +}); + +// ************************************************************************************************ + +this.Text = domplate(Firebug.Rep, +{ + tag: OBJECTBOX("$object"), + + shortTag: OBJECTBOX("$object|cropString"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "text" +}); + +// ************************************************************************************************ + +this.Caption = domplate(Firebug.Rep, +{ + tag: SPAN({"class": "caption"}, "$object") +}); + +// ************************************************************************************************ + +this.Warning = domplate(Firebug.Rep, +{ + tag: DIV({"class": "warning focusRow", role : 'listitem'}, "$object|STR") +}); + +// ************************************************************************************************ + +this.Func = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK("$object|summarizeFunction"), + + summarizeFunction: function(fn) + { + var fnRegex = /function ([^(]+\([^)]*\)) \{/; + var fnText = safeToString(fn); + + var m = fnRegex.exec(fnText); + return m ? m[1] : "function()"; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + copySource: function(fn) + { + copyToClipboard(safeToString(fn)); + }, + + monitor: function(fn, script, monitored) + { + if (monitored) + Firebug.Debugger.unmonitorScript(fn, script, "monitor"); + else + Firebug.Debugger.monitorScript(fn, script, "monitor"); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "function", + + supportsObject: function(object, type) + { + return isFunction(object); + }, + + inspectObject: function(fn, context) + { + var sourceLink = findSourceForFunction(fn, context); + if (sourceLink) + Firebug.chrome.select(sourceLink); + if (FBTrace.DBG_FUNCTION_NAME) + FBTrace.sysout("reps.function.inspectObject selected sourceLink is ", sourceLink); + }, + + getTooltip: function(fn, context) + { + var script = findScriptForFunctionInContext(context, fn); + if (script) + return $STRF("Line", [normalizeURL(script.fileName), script.baseLineNumber]); + else + if (fn.toString) + return fn.toString(); + }, + + getTitle: function(fn, context) + { + var name = fn.name ? fn.name : "function"; + return name + "()"; + }, + + getContextMenuItems: function(fn, target, context, script) + { + if (!script) + script = findScriptForFunctionInContext(context, fn); + if (!script) + return; + + var scriptInfo = getSourceFileAndLineByScript(context, script); + var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false; + + var name = script ? getFunctionName(script, context) : fn.name; + return [ + {label: "CopySource", command: bindFixed(this.copySource, this, fn) }, + "-", + {label: $STRF("ShowCallsInConsole", [name]), nol10n: true, + type: "checkbox", checked: monitored, + command: bindFixed(this.monitor, this, fn, script, monitored) } + ]; + } +}); + +// ************************************************************************************************ +/* +this.jsdScript = domplate(Firebug.Rep, +{ + copySource: function(script) + { + var fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.copySource(fn); + }, + + monitor: function(fn, script, monitored) + { + fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.monitor(fn, script, monitored); + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "jsdScript", + inspectable: false, + + supportsObject: function(object, type) + { + return object instanceof jsdIScript; + }, + + inspectObject: function(script, context) + { + var sourceLink = getSourceLinkForScript(script, context); + if (sourceLink) + Firebug.chrome.select(sourceLink); + }, + + getRealObject: function(script, context) + { + return script; + }, + + getTooltip: function(script) + { + return $STRF("jsdIScript", [script.tag]); + }, + + getTitle: function(script, context) + { + var fn = script.functionObject.getWrappedValue(); + return FirebugReps.Func.getTitle(fn, context); + }, + + getContextMenuItems: function(script, target, context) + { + var fn = script.functionObject.getWrappedValue(); + + var scriptInfo = getSourceFileAndLineByScript(context, script); + var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false; + + var name = getFunctionName(script, context); + + return [ + {label: "CopySource", command: bindFixed(this.copySource, this, script) }, + "-", + {label: $STRF("ShowCallsInConsole", [name]), nol10n: true, + type: "checkbox", checked: monitored, + command: bindFixed(this.monitor, this, fn, script, monitored) } + ]; + } +}); +/**/ +//************************************************************************************************ + +this.Obj = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + SPAN({"class": "objectTitle"}, "$object|getTitle "), + + SPAN({"class": "objectProps"}, + SPAN({"class": "objectLeftBrace", role: "presentation"}, "{"), + FOR("prop", "$object|propIterator", + SPAN({"class": "objectPropName", role: "presentation"}, "$prop.name"), + SPAN({"class": "objectEqual", role: "presentation"}, "$prop.equal"), + TAG("$prop.tag", {object: "$prop.object"}), + SPAN({"class": "objectComma", role: "presentation"}, "$prop.delim") + ), + SPAN({"class": "objectRightBrace"}, "}") + ) + ), + + propNumberTag: + SPAN({"class": "objectProp-number"}, "$object"), + + propStringTag: + SPAN({"class": "objectProp-string"}, ""$object""), + + propObjectTag: + SPAN({"class": "objectProp-object"}, "$object"), + + propIterator: function (object) + { + ///Firebug.ObjectShortIteratorMax; + var maxLength = 55; // default max length for long representation + + if (!object) + return []; + + var props = []; + var length = 0; + + var numProperties = 0; + var numPropertiesShown = 0; + var maxLengthReached = false; + + var lib = this; + + var propRepsMap = + { + "boolean": this.propNumberTag, + "number": this.propNumberTag, + "string": this.propStringTag, + "object": this.propObjectTag + }; + + try + { + var title = Firebug.Rep.getTitle(object); + length += title.length; + + for (var name in object) + { + var value; + try + { + value = object[name]; + } + catch (exc) + { + continue; + } + + var type = typeof(value); + if (type == "boolean" || + type == "number" || + (type == "string" && value) || + (type == "object" && value && value.toString)) + { + var tag = propRepsMap[type]; + + var value = (type == "object") ? + Firebug.getRep(value).getTitle(value) : + value + ""; + + length += name.length + value.length + 4; + + if (length <= maxLength) + { + props.push({ + tag: tag, + name: name, + object: value, + equal: "=", + delim: ", " + }); + + numPropertiesShown++; + } + else + maxLengthReached = true; + + } + + numProperties++; + + if (maxLengthReached && numProperties > numPropertiesShown) + break; + } + + if (numProperties > numPropertiesShown) + { + props.push({ + object: "...", //xxxHonza localization + tag: FirebugReps.Caption.tag, + name: "", + equal:"", + delim:"" + }); + } + else if (props.length > 0) + { + props[props.length-1].delim = ''; + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + return props; + }, + + fb_1_6_propIterator: function (object, max) + { + max = max || 3; + if (!object) + return []; + + var props = []; + var len = 0, count = 0; + + try + { + for (var name in object) + { + var value; + try + { + value = object[name]; + } + catch (exc) + { + continue; + } + + var t = typeof(value); + if (t == "boolean" || t == "number" || (t == "string" && value) + || (t == "object" && value && value.toString)) + { + var rep = Firebug.getRep(value); + var tag = rep.shortTag || rep.tag; + if (t == "object") + { + value = rep.getTitle(value); + tag = rep.titleTag; + } + count++; + if (count <= max) + props.push({tag: tag, name: name, object: value, equal: "=", delim: ", "}); + else + break; + } + } + if (count > max) + { + props[Math.max(1,max-1)] = { + object: "more...", //xxxHonza localization + tag: FirebugReps.Caption.tag, + name: "", + equal:"", + delim:"" + }; + } + else if (props.length > 0) + { + props[props.length-1].delim = ''; + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + return props; + }, + + /* + propIterator: function (object) + { + if (!object) + return []; + + var props = []; + var len = 0; + + try + { + for (var name in object) + { + var val; + try + { + val = object[name]; + } + catch (exc) + { + continue; + } + + var t = typeof val; + if (t == "boolean" || t == "number" || (t == "string" && val) + || (t == "object" && !isFunction(val) && val && val.toString)) + { + var title = (t == "object") + ? Firebug.getRep(val).getTitle(val) + : val+""; + + len += name.length + title.length + 1; + if (len < 50) + props.push({name: name, value: title}); + else + break; + } + } + } + catch (exc) + { + // Sometimes we get exceptions when trying to read from certain objects, like + // StorageList, but don't let that gum up the works + // XXXjjb also History.previous fails because object is a web-page object which does not have + // permission to read the history + } + + return props; + }, + /**/ + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "object", + + supportsObject: function(object, type) + { + return true; + } +}); + + +// ************************************************************************************************ + +this.Arr = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({_repObject: "$object"}, + SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["), + FOR("item", "$object|arrayIterator", + TAG("$item.tag", {object: "$item.object"}), + SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim") + ), + SPAN({"class": "arrayRightBracket", role : "presentation"}, "]") + ), + + shortTag: + OBJECTBOX({_repObject: "$object"}, + SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["), + FOR("item", "$object|shortArrayIterator", + TAG("$item.tag", {object: "$item.object"}), + SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim") + ), + // TODO: xxxpedro - confirm this on Firebug + //FOR("prop", "$object|shortPropIterator", + // " $prop.name=", + // SPAN({"class": "objectPropValue"}, "$prop.value|cropString") + //), + SPAN({"class": "arrayRightBracket"}, "]") + ), + + arrayIterator: function(array) + { + var items = []; + for (var i = 0; i < array.length; ++i) + { + var value = array[i]; + var rep = Firebug.getRep(value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + var delim = (i == array.length-1 ? "" : ", "); + + items.push({object: value, tag: tag, delim: delim}); + } + + return items; + }, + + shortArrayIterator: function(array) + { + var items = []; + for (var i = 0; i < array.length && i < 3; ++i) + { + var value = array[i]; + var rep = Firebug.getRep(value); + var tag = rep.shortTag ? rep.shortTag : rep.tag; + var delim = (i == array.length-1 ? "" : ", "); + + items.push({object: value, tag: tag, delim: delim}); + } + + if (array.length > 3) + items.push({object: (array.length-3) + " more...", tag: FirebugReps.Caption.tag, delim: ""}); + + return items; + }, + + shortPropIterator: this.Obj.propIterator, + + getItemIndex: function(child) + { + var arrayIndex = 0; + for (child = child.previousSibling; child; child = child.previousSibling) + { + if (child.repObject) + ++arrayIndex; + } + return arrayIndex; + }, + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "array", + + supportsObject: function(object) + { + return this.isArray(object); + }, + + // http://code.google.com/p/fbug/issues/detail?id=874 + // BEGIN Yahoo BSD Source (modified here) YAHOO.lang.isArray, YUI 2.2.2 June 2007 + isArray: function(obj) { + try { + if (!obj) + return false; + else if (isIE && !isFunction(obj) && typeof obj == "object" && isFinite(obj.length) && obj.nodeType != 8) + return true; + else if (isFinite(obj.length) && isFunction(obj.splice)) + return true; + else if (isFinite(obj.length) && isFunction(obj.callee)) // arguments + return true; + else if (instanceOf(obj, "HTMLCollection")) + return true; + else if (instanceOf(obj, "NodeList")) + return true; + else + return false; + } + catch(exc) + { + if (FBTrace.DBG_ERRORS) + { + FBTrace.sysout("isArray FAILS:", exc); /* Something weird: without the try/catch, OOM, with no exception?? */ + FBTrace.sysout("isArray Fails on obj", obj); + } + } + + return false; + }, + // END Yahoo BSD SOURCE See license below. + + getTitle: function(object, context) + { + return "[" + object.length + "]"; + } +}); + +// ************************************************************************************************ + +this.Property = domplate(Firebug.Rep, +{ + supportsObject: function(object) + { + return object instanceof Property; + }, + + getRealObject: function(prop, context) + { + return prop.object[prop.name]; + }, + + getTitle: function(prop, context) + { + return prop.name; + } +}); + +// ************************************************************************************************ + +this.NetFile = domplate(this.Obj, +{ + supportsObject: function(object) + { + return object instanceof Firebug.NetFile; + }, + + browseObject: function(file, context) + { + openNewTab(file.href); + return true; + }, + + getRealObject: function(file, context) + { + return null; + } +}); + +// ************************************************************************************************ + +this.Except = domplate(Firebug.Rep, +{ + tag: + OBJECTBOX({_repObject: "$object"}, "$object.message"), + + // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + + className: "exception", + + supportsObject: function(object) + { + return object instanceof ErrorCopy; + } +}); + + +// ************************************************************************************************ + +this.Element = domplate(Firebug.Rep, +{ + tag: + OBJECTLINK( + "<", + SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), + FOR("attr", "$object|attrIterator", + " $attr.nodeName="", SPAN({"class": "nodeValue"}, "$attr.nodeValue"), """ + ), + ">" + ), + + shortTag: + OBJECTLINK( + SPAN({"class": "$object|getVisible"}, + SPAN({"class": "selectorTag"}, "$object|getSelectorTag"), + SPAN({"class": "selectorId"}, "$object|getSelectorId"), + SPAN({"class": "selectorClass"}, "$object|getSelectorClass"), + SPAN({"class": "selectorValue"}, "$object|getValue") + ) + ), + + getVisible: function(elt) + { + return isVisible(elt) ? "" : "selectorHidden"; + }, + + getSelectorTag: function(elt) + { + return elt.nodeName.toLowerCase(); + }, + + getSelectorId: function(elt) + { + return elt.id ? "#" + elt.id : ""; + }, + + getSelectorClass: function(elt) + { + return elt.className ? "." + elt.className.split(" ")[0] : ""; + }, + + getValue: function(elt) + { + // TODO: xxxpedro + return ""; + var value; + if (elt instanceof HTMLImageElement) + value = getFileName(elt.src); + else if (elt instanceof HTMLAnchorElement) + value = getFileName(elt.href); + else if (elt instanceof HTMLInputElement) + value = elt.value; + else if (elt instanceof HTMLFormElement) + value = getFileName(elt.action); + else if (elt instanceof HTMLScriptElement) + value = getFileName(elt.src); + + return value ? " " + cropString(value, 20) : ""; + }, + + attrIterator: function(elt) + { + var attrs = []; + var idAttr, classAttr; + if (elt.attributes) + { + for (var i = 0; i < elt.attributes.length; ++i) + { + var attr = elt.attributes[i]; + + // we must check if the attribute is specified otherwise IE will show them + if (!attr.specified || attr.nodeName && attr.nodeName.indexOf("firebug-") != -1) + continue; + else if (attr.nodeName == "id") + idAttr = attr; + else if (attr.nodeName == "class") + classAttr = attr; + else if (attr.nodeName == "style") + attrs.push({ + nodeName: attr.nodeName, + nodeValue: attr.nodeValue || + // IE won't recognize the attr.nodeValue of