From 81ed773baa8c2ddb3059d5a42bf45813bc05c396 Mon Sep 17 00:00:00 2001 From: JC Brand Date: Thu, 14 Jun 2018 10:22:16 +0200 Subject: [PATCH] Updated css and fix some niggles in the Makefile --- Makefile | 24 +- css/converse.css | 271 +- dist/converse-no-dependencies.js | 8167 +++++++++++++++++++++++++++--- dist/converse.js | 230 +- 4 files changed, 7877 insertions(+), 815 deletions(-) diff --git a/Makefile b/Makefile index 929d6a342..cdff95931 100644 --- a/Makefile +++ b/Makefile @@ -8,16 +8,16 @@ CHROMIUM ?= ./node_modules/.bin/run-headless-chromium CLEANCSS ?= ./node_modules/clean-css-cli/bin/cleancss --skip-rebase ESLINT ?= ./node_modules/.bin/eslint HTTPSERVE ?= ./node_modules/.bin/http-server -HTTPSERVE_PORT ?= 8000 -INKSCAPE ?= inkscape +HTTPSERVE_PORT ?= 8000 +INKSCAPE ?= inkscape JSDOC ?= ./node_modules/.bin/jsdoc -OXIPNG ?= oxipng +OXIPNG ?= oxipng PAPER = PO2JSON ?= ./node_modules/.bin/po2json -RJS ?= ./node_modules/.bin/r.js +RJS ?= ./node_modules/.bin/r.js WEBPACK ?= ./node_modules/.bin/npx SASS ?= ./.bundle/bin/sass -SED ?= sed +SED ?= sed SPHINXBUILD ?= ./bin/sphinx-build SPHINXOPTS = UGLIFYJS ?= node_modules/.bin/uglifyjs @@ -122,7 +122,7 @@ stamp-bundler: Gemfile .PHONY: clean clean: - rm -rf node_modules .bundle stamp-npm + rm -rf node_modules .bundle stamp-npm stamp-bundler rm dist/*.min.js rm css/website.min.css rm css/converse.min.css @@ -179,12 +179,12 @@ logo/conversejs-filled%.png:: logo/conversejs-filled.svg $(OXIPNG) $@ BUILDS = dist/converse.js \ - dist/converse.min.js \ - dist/converse-headless.js \ - dist/converse-headless.min.js \ - dist/converse-no-dependencies.min.js \ - dist/converse-no-dependencies.js \ - dist/converse-no-dependencies-es2015.js + dist/converse.min.js \ + dist/converse-headless.js \ + dist/converse-headless.min.js \ + dist/converse-no-dependencies.min.js \ + dist/converse-no-dependencies.js \ + dist/converse-no-dependencies-es2015.js dist/converse.js: src webpack.config.js stamp-npm ./node_modules/.bin/npx webpack --mode=development diff --git a/css/converse.css b/css/converse.css index 4b9fbf9a9..a762d5123 100644 --- a/css/converse.css +++ b/css/converse.css @@ -2371,7 +2371,7 @@ -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } @-ms-viewport { width: device-width; } -#conversejs article, #conversejs aside, #conversejs dialog, #conversejs figcaption, #conversejs figure, #conversejs footer, #conversejs header, #conversejs hgroup, #conversejs main, #conversejs nav, #conversejs section { +#conversejs article, #conversejs aside, #conversejs figcaption, #conversejs figure, #conversejs footer, #conversejs header, #conversejs hgroup, #conversejs main, #conversejs nav, #conversejs section { display: block; } #conversejs body { margin: 0; @@ -2458,7 +2458,7 @@ #conversejs code, #conversejs kbd, #conversejs samp { - font-family: monospace, monospace; + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 1em; } #conversejs pre { margin-top: 0; @@ -2484,7 +2484,7 @@ text-align: inherit; } #conversejs label { display: inline-block; - margin-bottom: .5rem; } + margin-bottom: 0.5rem; } #conversejs button { border-radius: 0; } #conversejs button:focus { @@ -3208,6 +3208,9 @@ border: 1px solid #ced4da; border-radius: 0.25rem; transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media screen and (prefers-reduced-motion: reduce) { + #conversejs .form-control { + transition: none; } } #conversejs .form-control::-ms-expand { background-color: transparent; border: 0; } @@ -3255,6 +3258,7 @@ padding-bottom: 0.375rem; margin-bottom: 0; line-height: 1.5; + color: #212529; background-color: transparent; border: solid transparent; border-width: 1px 0; } @@ -3371,6 +3375,10 @@ #conversejs .custom-select.is-valid ~ .valid-feedback, #conversejs .custom-select.is-valid ~ .valid-tooltip { display: block; } +.was-validated #conversejs .form-control-file:valid ~ .valid-feedback, +.was-validated #conversejs .form-control-file:valid ~ .valid-tooltip, #conversejs .form-control-file.is-valid ~ .valid-feedback, +#conversejs .form-control-file.is-valid ~ .valid-tooltip { + display: block; } .was-validated #conversejs .form-check-input:valid ~ .form-check-label, #conversejs .form-check-input.is-valid ~ .form-check-label { color: #3AA569; } .was-validated #conversejs .form-check-input:valid ~ .valid-feedback, @@ -3435,6 +3443,10 @@ #conversejs .custom-select.is-invalid ~ .invalid-feedback, #conversejs .custom-select.is-invalid ~ .invalid-tooltip { display: block; } +.was-validated #conversejs .form-control-file:invalid ~ .invalid-feedback, +.was-validated #conversejs .form-control-file:invalid ~ .invalid-tooltip, #conversejs .form-control-file.is-invalid ~ .invalid-feedback, +#conversejs .form-control-file.is-invalid ~ .invalid-tooltip { + display: block; } .was-validated #conversejs .form-check-input:invalid ~ .form-check-label, #conversejs .form-check-input.is-invalid ~ .form-check-label { color: #E77051; } .was-validated #conversejs .form-check-input:invalid ~ .invalid-feedback, @@ -3487,7 +3499,8 @@ vertical-align: middle; } #conversejs .form-inline .form-control-plaintext { display: inline-block; } - #conversejs .form-inline .input-group { + #conversejs .form-inline .input-group, + #conversejs .form-inline .custom-select { width: auto; } #conversejs .form-inline .form-check { display: flex; @@ -3518,6 +3531,9 @@ line-height: 1.5; border-radius: 0.25rem; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } + @media screen and (prefers-reduced-motion: reduce) { + #conversejs .btn { + transition: none; } } #conversejs .btn:hover, #conversejs .btn:focus { text-decoration: none; } #conversejs .btn:focus, #conversejs .btn.focus { @@ -3866,7 +3882,8 @@ border-color: transparent; box-shadow: none; } #conversejs .btn-link:disabled, #conversejs .btn-link.disabled { - color: #6c757d; } + color: #6c757d; + pointer-events: none; } #conversejs .btn-lg, #conversejs .btn-group-lg > .btn { padding: 0.5rem 1rem; font-size: 1.25rem; @@ -3887,25 +3904,26 @@ #conversejs input[type="button"].btn-block { width: 100%; } #conversejs .fade { - opacity: 0; transition: opacity 0.15s linear; } - #conversejs .fade.show { - opacity: 1; } -#conversejs .collapse { + @media screen and (prefers-reduced-motion: reduce) { + #conversejs .fade { + transition: none; } } + #conversejs .fade:not(.show) { + opacity: 0; } +#conversejs .collapse:not(.show) { display: none; } - #conversejs .collapse.show { - display: block; } -#conversejs tr.collapse.show { - display: table-row; } -#conversejs tbody.collapse.show { - display: table-row-group; } #conversejs .collapsing { position: relative; height: 0; overflow: hidden; transition: height 0.35s ease; } + @media screen and (prefers-reduced-motion: reduce) { + #conversejs .collapsing { + transition: none; } } #conversejs .dropup, -#conversejs .dropdown { +#conversejs .dropright, +#conversejs .dropdown, +#conversejs .dropleft { position: relative; } #conversejs .dropdown-toggle::after { display: inline-block; @@ -3938,7 +3956,12 @@ background-clip: padding-box; border: 1px solid rgba(0, 0, 0, 0.15); border-radius: 0.25rem; } +#conversejs .dropdown-menu-right { + right: 0; + left: auto; } #conversejs .dropup .dropdown-menu { + top: auto; + bottom: 100%; margin-top: 0; margin-bottom: 0.125rem; } #conversejs .dropup .dropdown-toggle::after { @@ -3955,6 +3978,9 @@ #conversejs .dropup .dropdown-toggle:empty::after { margin-left: 0; } #conversejs .dropright .dropdown-menu { + top: 0; + right: auto; + left: 100%; margin-top: 0; margin-left: 0.125rem; } #conversejs .dropright .dropdown-toggle::after { @@ -3965,6 +3991,7 @@ vertical-align: 0.255em; content: ""; border-top: 0.3em solid transparent; + border-right: 0; border-bottom: 0.3em solid transparent; border-left: 0.3em solid; } #conversejs .dropright .dropdown-toggle:empty::after { @@ -3972,6 +3999,9 @@ #conversejs .dropright .dropdown-toggle::after { vertical-align: 0; } #conversejs .dropleft .dropdown-menu { + top: 0; + right: 100%; + left: auto; margin-top: 0; margin-right: 0.125rem; } #conversejs .dropleft .dropdown-toggle::after { @@ -3997,6 +4027,9 @@ margin-left: 0; } #conversejs .dropleft .dropdown-toggle::before { vertical-align: 0; } +#conversejs .dropdown-menu[x-placement^="top"], #conversejs .dropdown-menu[x-placement^="right"], #conversejs .dropdown-menu[x-placement^="bottom"], #conversejs .dropdown-menu[x-placement^="left"] { + right: auto; + bottom: auto; } #conversejs .dropdown-divider { height: 0; margin: 0.5rem 0; @@ -4033,6 +4066,10 @@ font-size: 0.875rem; color: #6c757d; white-space: nowrap; } +#conversejs .dropdown-item-text { + display: block; + padding: 0.25rem 1.5rem; + color: #212529; } #conversejs .btn-group, #conversejs .btn-group-vertical { position: relative; @@ -4078,8 +4115,10 @@ #conversejs .dropdown-toggle-split { padding-right: 0.5625rem; padding-left: 0.5625rem; } - #conversejs .dropdown-toggle-split::after { + #conversejs .dropdown-toggle-split::after, .dropup #conversejs .dropdown-toggle-split::after, .dropright #conversejs .dropdown-toggle-split::after { margin-left: 0; } + .dropleft #conversejs .dropdown-toggle-split::before { + margin-right: 0; } #conversejs .btn-sm + .dropdown-toggle-split, #conversejs .btn-group-sm > .btn + .dropdown-toggle-split { padding-right: 0.375rem; padding-left: 0.375rem; } @@ -4155,10 +4194,10 @@ #conversejs .input-group > .custom-file { display: flex; align-items: center; } - #conversejs .input-group > .custom-file:not(:last-child) .custom-file-label, #conversejs .input-group > .custom-file:not(:last-child) .custom-file-label::before { + #conversejs .input-group > .custom-file:not(:last-child) .custom-file-label, #conversejs .input-group > .custom-file:not(:last-child) .custom-file-label::after { border-top-right-radius: 0; border-bottom-right-radius: 0; } - #conversejs .input-group > .custom-file:not(:first-child) .custom-file-label, #conversejs .input-group > .custom-file:not(:first-child) .custom-file-label::before { + #conversejs .input-group > .custom-file:not(:first-child) .custom-file-label { border-top-left-radius: 0; border-bottom-left-radius: 0; } #conversejs .input-group-prepend, @@ -4239,11 +4278,12 @@ #conversejs .custom-control-input:disabled ~ .custom-control-label::before { background-color: #e9ecef; } #conversejs .custom-control-label { + position: relative; margin-bottom: 0; } #conversejs .custom-control-label::before { position: absolute; top: 0.25rem; - left: 0; + left: -1.5rem; display: block; width: 1rem; height: 1rem; @@ -4254,7 +4294,7 @@ #conversejs .custom-control-label::after { position: absolute; top: 0.25rem; - left: 0; + left: -1.5rem; display: block; width: 1rem; height: 1rem; @@ -4336,10 +4376,10 @@ height: calc(2.25rem + 2px); margin: 0; opacity: 0; } - #conversejs .custom-file-input:focus ~ .custom-file-control { + #conversejs .custom-file-input:focus ~ .custom-file-label { border-color: #7db3cd; box-shadow: 0 0 0 0.2rem rgba(56, 117, 146, 0.25); } - #conversejs .custom-file-input:focus ~ .custom-file-control::before { + #conversejs .custom-file-input:focus ~ .custom-file-label::after { border-color: #7db3cd; } #conversejs .custom-file-input:lang(en) ~ .custom-file-label::after { content: "Browse"; } @@ -4363,7 +4403,7 @@ bottom: 0; z-index: 3; display: block; - height: calc(calc(2.25rem + 2px) - 1px * 2); + height: 2.25rem; padding: 0.375rem 0.75rem; line-height: 1.5; color: #495057; @@ -4371,6 +4411,83 @@ background-color: #e9ecef; border-left: 1px solid #ced4da; border-radius: 0 0.25rem 0.25rem 0; } +#conversejs .custom-range { + width: 100%; + padding-left: 0; + background-color: transparent; + appearance: none; } + #conversejs .custom-range:focus { + outline: none; } + #conversejs .custom-range::-moz-focus-outer { + border: 0; } + #conversejs .custom-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + background-color: #387592; + border: 0; + border-radius: 1rem; + appearance: none; } + #conversejs .custom-range::-webkit-slider-thumb:focus { + outline: none; + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(56, 117, 146, 0.25); } + #conversejs .custom-range::-webkit-slider-thumb:active { + background-color: #a1c9db; } + #conversejs .custom-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; } + #conversejs .custom-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #387592; + border: 0; + border-radius: 1rem; + appearance: none; } + #conversejs .custom-range::-moz-range-thumb:focus { + outline: none; + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(56, 117, 146, 0.25); } + #conversejs .custom-range::-moz-range-thumb:active { + background-color: #a1c9db; } + #conversejs .custom-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; } + #conversejs .custom-range::-ms-thumb { + width: 1rem; + height: 1rem; + background-color: #387592; + border: 0; + border-radius: 1rem; + appearance: none; } + #conversejs .custom-range::-ms-thumb:focus { + outline: none; + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(56, 117, 146, 0.25); } + #conversejs .custom-range::-ms-thumb:active { + background-color: #a1c9db; } + #conversejs .custom-range::-ms-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: transparent; + border-color: transparent; + border-width: 0.5rem; } + #conversejs .custom-range::-ms-fill-lower { + background-color: #dee2e6; + border-radius: 1rem; } + #conversejs .custom-range::-ms-fill-upper { + margin-right: 15px; + background-color: #dee2e6; + border-radius: 1rem; } #conversejs .card { position: relative; display: flex; @@ -4516,10 +4633,24 @@ @media (min-width: 576px) { #conversejs .card-columns { column-count: 3; - column-gap: 1.25rem; } + column-gap: 1.25rem; + orphans: 1; + widows: 1; } #conversejs .card-columns .card { display: inline-block; width: 100%; } } +#conversejs .accordion .card:not(:first-of-type):not(:last-of-type) { + border-bottom: 0; + border-radius: 0; } +#conversejs .accordion .card:not(:first-of-type) .card-header:first-child { + border-radius: 0; } +#conversejs .accordion .card:first-of-type { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } +#conversejs .accordion .card:last-of-type { + border-top-left-radius: 0; + border-top-right-radius: 0; } #conversejs .breadcrumb { display: flex; flex-wrap: wrap; @@ -4528,12 +4659,13 @@ list-style: none; background-color: #e9ecef; border-radius: 0.25rem; } -#conversejs .breadcrumb-item + .breadcrumb-item::before { - display: inline-block; - padding-right: 0.5rem; - padding-left: 0.5rem; - color: #6c757d; - content: "/"; } +#conversejs .breadcrumb-item + .breadcrumb-item { + padding-left: 0.5rem; } + #conversejs .breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + color: #6c757d; + content: "/"; } #conversejs .breadcrumb-item + .breadcrumb-item:hover::before { text-decoration: underline; } #conversejs .breadcrumb-item + .breadcrumb-item:hover::before { @@ -4873,6 +5005,9 @@ .modal.fade #conversejs .modal-dialog { transition: transform 0.3s ease-out; transform: translate(0, -25%); } + @media screen and (prefers-reduced-motion: reduce) { + .modal.fade #conversejs .modal-dialog { + transition: none; } } .modal.show #conversejs .modal-dialog { transform: translate(0, 0); } #conversejs .modal-dialog-centered { @@ -5428,6 +5563,16 @@ flex-wrap: nowrap !important; } #conversejs .flex-wrap-reverse { flex-wrap: wrap-reverse !important; } +#conversejs .flex-fill { + flex: 1 1 auto !important; } +#conversejs .flex-grow-0 { + flex-grow: 0 !important; } +#conversejs .flex-grow-1 { + flex-grow: 1 !important; } +#conversejs .flex-shrink-0 { + flex-shrink: 0 !important; } +#conversejs .flex-shrink-1 { + flex-shrink: 1 !important; } #conversejs .justify-content-start { justify-content: flex-start !important; } #conversejs .justify-content-end { @@ -5487,6 +5632,16 @@ flex-wrap: nowrap !important; } #conversejs .flex-sm-wrap-reverse { flex-wrap: wrap-reverse !important; } + #conversejs .flex-sm-fill { + flex: 1 1 auto !important; } + #conversejs .flex-sm-grow-0 { + flex-grow: 0 !important; } + #conversejs .flex-sm-grow-1 { + flex-grow: 1 !important; } + #conversejs .flex-sm-shrink-0 { + flex-shrink: 0 !important; } + #conversejs .flex-sm-shrink-1 { + flex-shrink: 1 !important; } #conversejs .justify-content-sm-start { justify-content: flex-start !important; } #conversejs .justify-content-sm-end { @@ -5546,6 +5701,16 @@ flex-wrap: nowrap !important; } #conversejs .flex-md-wrap-reverse { flex-wrap: wrap-reverse !important; } + #conversejs .flex-md-fill { + flex: 1 1 auto !important; } + #conversejs .flex-md-grow-0 { + flex-grow: 0 !important; } + #conversejs .flex-md-grow-1 { + flex-grow: 1 !important; } + #conversejs .flex-md-shrink-0 { + flex-shrink: 0 !important; } + #conversejs .flex-md-shrink-1 { + flex-shrink: 1 !important; } #conversejs .justify-content-md-start { justify-content: flex-start !important; } #conversejs .justify-content-md-end { @@ -5605,6 +5770,16 @@ flex-wrap: nowrap !important; } #conversejs .flex-lg-wrap-reverse { flex-wrap: wrap-reverse !important; } + #conversejs .flex-lg-fill { + flex: 1 1 auto !important; } + #conversejs .flex-lg-grow-0 { + flex-grow: 0 !important; } + #conversejs .flex-lg-grow-1 { + flex-grow: 1 !important; } + #conversejs .flex-lg-shrink-0 { + flex-shrink: 0 !important; } + #conversejs .flex-lg-shrink-1 { + flex-shrink: 1 !important; } #conversejs .justify-content-lg-start { justify-content: flex-start !important; } #conversejs .justify-content-lg-end { @@ -5664,6 +5839,16 @@ flex-wrap: nowrap !important; } #conversejs .flex-xl-wrap-reverse { flex-wrap: wrap-reverse !important; } + #conversejs .flex-xl-fill { + flex: 1 1 auto !important; } + #conversejs .flex-xl-grow-0 { + flex-grow: 0 !important; } + #conversejs .flex-xl-grow-1 { + flex-grow: 1 !important; } + #conversejs .flex-xl-shrink-0 { + flex-shrink: 0 !important; } + #conversejs .flex-xl-shrink-1 { + flex-shrink: 1 !important; } #conversejs .justify-content-xl-start { justify-content: flex-start !important; } #conversejs .justify-content-xl-end { @@ -5777,7 +5962,6 @@ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; - clip-path: inset(50%); border: 0; } #conversejs .sr-only-focusable:active, #conversejs .sr-only-focusable:focus { position: static; @@ -5785,8 +5969,15 @@ height: auto; overflow: visible; clip: auto; - white-space: normal; - clip-path: none; } + white-space: normal; } +#conversejs .shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; } +#conversejs .shadow { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; } +#conversejs .shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; } +#conversejs .shadow-none { + box-shadow: none !important; } #conversejs .w-25 { width: 25% !important; } #conversejs .w-50 { @@ -5795,6 +5986,8 @@ width: 75% !important; } #conversejs .w-100 { width: 100% !important; } +#conversejs .w-auto { + width: auto !important; } #conversejs .h-25 { height: 25% !important; } #conversejs .h-50 { @@ -5803,6 +5996,8 @@ height: 75% !important; } #conversejs .h-100 { height: 100% !important; } +#conversejs .h-auto { + height: auto !important; } #conversejs .mw-100 { max-width: 100% !important; } #conversejs .mh-100 { @@ -6721,6 +6916,8 @@ #conversejs .ml-xl-auto, #conversejs .mx-xl-auto { margin-left: auto !important; } } +#conversejs .text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } #conversejs .text-justify { text-align: justify !important; } #conversejs .text-nowrap { @@ -6811,8 +7008,14 @@ color: #343a40 !important; } #conversejs a.text-dark:hover, #conversejs a.text-dark:focus { color: #1d2124 !important; } +#conversejs .text-body { + color: #212529 !important; } #conversejs .text-muted { color: #6c757d !important; } +#conversejs .text-black-50 { + color: rgba(0, 0, 0, 0.5) !important; } +#conversejs .text-white-50 { + color: rgba(255, 255, 255, 0.5) !important; } #conversejs .text-hide { font: 0/0 a; color: transparent; diff --git a/dist/converse-no-dependencies.js b/dist/converse-no-dependencies.js index dfb2b025a..65e5e19aa 100644 --- a/dist/converse-no-dependencies.js +++ b/dist/converse-no-dependencies.js @@ -36,19 +36,34 @@ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); +/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ +/******/ // create a fake namespace object +/******/ // mode & 1: value is a module id, require it +/******/ // mode & 2: merge all properties of value into the ns +/******/ // mode & 4: return value when already ns object +/******/ // mode & 8|1: behave like require +/******/ __webpack_require__.t = function(value, mode) { +/******/ if(mode & 1) value = __webpack_require__(value); +/******/ if(mode & 8) return value; +/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; +/******/ var ns = Object.create(null); +/******/ __webpack_require__.r(ns); +/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); +/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); +/******/ return ns; +/******/ }; +/******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? @@ -253,10 +268,10 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/***/ "./node_modules/backbone.overview/dist/backbone.orderedlistview.js": -/*!*************************************************************************!*\ - !*** ./node_modules/backbone.overview/dist/backbone.orderedlistview.js ***! - \*************************************************************************/ +/***/ "./node_modules/backbone.overview/backbone.orderedlistview.js": +/*!********************************************************************!*\ + !*** ./node_modules/backbone.overview/backbone.orderedlistview.js ***! + \********************************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { @@ -271,101 +286,99 @@ backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_mod * Licensed under the Mozilla Public License (MPL) */ (function (root, factory) { - if (true) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! underscore */ "./src/underscore-shim.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"), __webpack_require__(/*! backbone.overview */ "backbone.overview")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + if (true) { + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! underscore */ "./src/underscore-shim.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js"), __webpack_require__(/*! backbone.overview */ "backbone.overview")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else {} -})(this, function (_, Backbone) { - "use strict"; + } else {} +}(this, function (_, Backbone) { + "use strict"; - Backbone.OrderedListView = Backbone.Overview.extend({ - /* An OrderedListView is a special type of Overview which adds some - * methods and conventions for rendering an ordered list of elements. - */ - // The `listItems` attribute denotes the path (from this View) to the - // list of items. - listItems: 'model', - // The `sortEvent` attribute specifies the event which should cause the - // ordered list to be sorted. - sortEvent: 'change', - // The `listSelector` is the selector used to query for the DOM list - // element which contains the ordered items. - listSelector: '.ordered-items', - // The `itemView` is constructor which should be called to create a - // View for a new item. - ItemView: undefined, - // The `subviewIndex` is the attribute of the list element model which - // acts as the index of the subview in the overview. - // An overview is a "Collection" of views, and they can be retrieved - // via an index. By default this is the 'id' attribute, but it could be - // set to something else. - subviewIndex: 'id', - initialize: function initialize() { - this.sortEventually = _.debounce(this.sortAndPositionAllItems.bind(this), 500); - this.items = _.get(this, this.listItems); - this.items.on('add', this.sortAndPositionAllItems, this); - this.items.on('remove', this.removeView, this); + Backbone.OrderedListView = Backbone.Overview.extend({ + /* An OrderedListView is a special type of Overview which adds some + * methods and conventions for rendering an ordered list of elements. + */ - if (!_.isNil(this.sortEvent)) { - this.items.on(this.sortEvent, this.sortEventually, this); - } - }, - createItemView: function createItemView(item) { - var item_view = this.get(item.get(this.subviewIndex)); + // The `listItems` attribute denotes the path (from this View) to the + // list of items. + listItems: 'model', + // The `sortEvent` attribute specifies the event which should cause the + // ordered list to be sorted. + sortEvent: 'change', + // The `listSelector` is the selector used to query for the DOM list + // element which contains the ordered items. + listSelector: '.ordered-items', + // The `itemView` is constructor which should be called to create a + // View for a new item. + ItemView: undefined, + // The `subviewIndex` is the attribute of the list element model which + // acts as the index of the subview in the overview. + // An overview is a "Collection" of views, and they can be retrieved + // via an index. By default this is the 'id' attribute, but it could be + // set to something else. + subviewIndex: 'id', - if (!item_view) { - item_view = new this.ItemView({ - model: item - }); - this.add(item.get(this.subviewIndex), item_view); - } else { - item_view.model = item; - item_view.initialize(); - } + initialize () { + this.sortEventually = _.debounce( + this.sortAndPositionAllItems.bind(this), 500); - item_view.render(); - return item_view; - }, - removeView: function removeView(item) { - this.remove(item.get(this.subviewIndex)); - }, - sortAndPositionAllItems: function sortAndPositionAllItems() { - var _this = this; + this.items = _.get(this, this.listItems); + this.items.on('add', this.sortAndPositionAllItems, this); + this.items.on('remove', this.removeView, this); + if (!_.isNil(this.sortEvent)) { + this.items.on(this.sortEvent, this.sortEventually, this); + } + }, - if (!this.items.length) { - return; - } + createItemView (item) { + let item_view = this.get(item.get(this.subviewIndex)); + if (!item_view) { + item_view = new this.ItemView({model: item}); + this.add(item.get(this.subviewIndex), item_view); + } else { + item_view.model = item; + item_view.initialize(); + } + item_view.render(); + return item_view; + }, - this.items.sort(); - var list_el = this.el.querySelector(this.listSelector); - var div = document.createElement('div'); - list_el.parentNode.replaceChild(div, list_el); - this.items.each(function (item) { - var view = _this.get(item.get(_this.subviewIndex)); + removeView (item) { + this.remove(item.get(this.subviewIndex)); + }, - if (_.isUndefined(view)) { - view = _this.createItemView(item); + sortAndPositionAllItems () { + if (!this.items.length) { + return; + } + this.items.sort(); + + const list_el = this.el.querySelector(this.listSelector); + const div = document.createElement('div'); + list_el.parentNode.replaceChild(div, list_el); + this.items.each((item) => { + let view = this.get(item.get(this.subviewIndex)); + if (_.isUndefined(view)) { + view = this.createItemView(item) + } + list_el.insertAdjacentElement('beforeend', view.el); + }); + div.parentNode.replaceChild(list_el, div); } + }); - list_el.insertAdjacentElement('beforeend', view.el); - }); - div.parentNode.replaceChild(list_el, div); - } - }); - return Backbone.OrderedListView; -}); + return Backbone.OrderedListView; +})); -//# sourceMappingURL=backbone.orderedlistview.js.map /***/ }), -/***/ "./node_modules/backbone.vdomview/dist/backbone.vdomview.js": -/*!******************************************************************!*\ - !*** ./node_modules/backbone.vdomview/dist/backbone.vdomview.js ***! - \******************************************************************/ +/***/ "./node_modules/backbone.vdomview/backbone.vdomview.js": +/*!*************************************************************!*\ + !*** ./node_modules/backbone.vdomview/backbone.vdomview.js ***! + \*************************************************************/ /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { @@ -373,90 +386,117 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ var backbone = (backbone || {}); backbone.nativeview = __webpack_require__(/*! backbone.nativeview */ "./node_modules/backbone.nativeview/backbone.nativeview.js"); -function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } - /*! * Backbone.VDOMView * * MIT Licensed. Copyright (c) 2017, JC Brand */ (function (root, factory) { - if (true) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(/*! snabbdom */ "./node_modules/snabbdom/dist/snabbdom.js"), __webpack_require__(/*! snabbdom-attributes */ "./node_modules/snabbdom/dist/snabbdom-attributes.js"), __webpack_require__(/*! snabbdom-class */ "./node_modules/snabbdom/dist/snabbdom-class.js"), __webpack_require__(/*! snabbdom-dataset */ "./node_modules/snabbdom/dist/snabbdom-dataset.js"), __webpack_require__(/*! snabbdom-props */ "./node_modules/snabbdom/dist/snabbdom-props.js"), __webpack_require__(/*! snabbdom-style */ "./node_modules/snabbdom/dist/snabbdom-style.js"), __webpack_require__(/*! tovnode */ "./node_modules/snabbdom/dist/tovnode.js"), __webpack_require__(/*! underscore */ "./src/underscore-shim.js"), __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js")], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), + if (true) { + !(__WEBPACK_AMD_DEFINE_ARRAY__ = [ + __webpack_require__(/*! snabbdom */ "./node_modules/snabbdom/dist/snabbdom.js"), + __webpack_require__(/*! snabbdom-attributes */ "./node_modules/snabbdom/dist/snabbdom-attributes.js"), + __webpack_require__(/*! snabbdom-class */ "./node_modules/snabbdom/dist/snabbdom-class.js"), + __webpack_require__(/*! snabbdom-dataset */ "./node_modules/snabbdom/dist/snabbdom-dataset.js"), + __webpack_require__(/*! snabbdom-props */ "./node_modules/snabbdom/dist/snabbdom-props.js"), + __webpack_require__(/*! snabbdom-style */ "./node_modules/snabbdom/dist/snabbdom-style.js"), + __webpack_require__(/*! tovnode */ "./node_modules/snabbdom/dist/tovnode.js"), + __webpack_require__(/*! underscore */ "./src/underscore-shim.js"), + __webpack_require__(/*! backbone */ "./node_modules/backbone/backbone.js") + ], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else {} -})(this, function (snabbdom, snabbdom_attributes, snabbdom_class, snabbdom_dataset, snabbdom_props, snabbdom_style, tovnode, _, Backbone) { - "use strict"; + } else {} +}(this, function ( + snabbdom, + snabbdom_attributes, + snabbdom_class, + snabbdom_dataset, + snabbdom_props, + snabbdom_style, + tovnode, + _, + Backbone) { + "use strict"; - var domParser = new DOMParser(); - var patch = snabbdom.init([snabbdom_attributes.default, snabbdom_class.default, snabbdom_dataset.default, snabbdom_props.default, snabbdom_style.default]); - var View = _.isUndefined(Backbone.NativeView) ? Backbone.View : Backbone.NativeView; + let domParser = new DOMParser(); + const patch = snabbdom.init([ + snabbdom_attributes.default, + snabbdom_class.default, + snabbdom_dataset.default, + snabbdom_props.default, + snabbdom_style.default + ]); - function parseHTMLToDOM(html_str) { - /* Parses a string with HTML and returns a DOM element. - * - * Forked from vdom_parser: - * https://github.com/bitinn/vdom-parser - */ - if (typeof html_str !== 'string') { - throw new Error('Invalid parameter type in parseHTMLToDOM'); + const View = _.isUndefined(Backbone.NativeView) ? Backbone.View : Backbone.NativeView; + + function parseHTMLToDOM (html_str) { + /* Parses a string with HTML and returns a DOM element. + * + * Forked from vdom_parser: + * https://github.com/bitinn/vdom-parser + */ + if (typeof html_str !== 'string') { + throw new Error('Invalid parameter type in parseHTMLToDOM'); + } + if ( !('DOMParser' in window) ) { + throw new Error( + 'DOMParser is not available, '+ + 'so parsing string to DOM node is not possible.'); + } + if (!html_str) { + return document.createTextNode(''); + } + domParser = domParser || new DOMParser(); + const doc = domParser.parseFromString(html_str, 'text/html'); + + // most tags default to body + if (doc.body.firstChild) { + return doc.getElementsByTagName('body')[0].firstChild; + + // some tags, like script and style, default to head + } else if (doc.head.firstChild && (doc.head.firstChild.tagName !== 'TITLE' || doc.title)) { + return doc.head.firstChild; + + // special case for html comment, cdata, doctype + } else if (doc.firstChild && doc.firstChild.tagName !== 'HTML') { + return doc.firstChild; + + // other element, such as whitespace, or html/body/head tag, fallback to empty text node + } else { + return document.createTextNode(''); + } } - if (!('DOMParser' in window)) { - throw new Error('DOMParser is not available, ' + 'so parsing string to DOM node is not possible.'); - } + Backbone.VDOMView = View.extend({ - if (!html_str) { - return document.createTextNode(''); - } + updateEventListeners (old_vnode, new_vnode) { + this.setElement(new_vnode.elm); + }, - domParser = domParser || new DOMParser(); - var doc = domParser.parseFromString(html_str, 'text/html'); // most tags default to body + render () { + if (_.isFunction(this.beforeRender)) { + this.beforeRender(); + } + const new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML())); + new_vnode.data.hook = _.extend({ + create: this.updateEventListeners.bind(this), + update: this.updateEventListeners.bind(this) + }); + const el = this.vnode ? this.vnode.elm : this.el; + if (el.outerHTML !== new_vnode.elm.outerHTML) { + this.vnode = patch(this.vnode || this.el, new_vnode); + } + if (_.isFunction(this.afterRender)) { + this.afterRender(); + } + return this; + } + }); + return Backbone.VDOMView; +})); - if (doc.body.firstChild) { - return doc.getElementsByTagName('body')[0].firstChild; // some tags, like script and style, default to head - } else if (doc.head.firstChild && (doc.head.firstChild.tagName !== 'TITLE' || doc.title)) { - return doc.head.firstChild; // special case for html comment, cdata, doctype - } else if (doc.firstChild && doc.firstChild.tagName !== 'HTML') { - return doc.firstChild; // other element, such as whitespace, or html/body/head tag, fallback to empty text node - } else { - return document.createTextNode(''); - } - } - - Backbone.VDOMView = View.extend({ - updateEventListeners: function updateEventListeners(old_vnode, new_vnode) { - this.setElement(new_vnode.elm); - }, - render: function render() { - if (_.isFunction(this.beforeRender)) { - this.beforeRender(); - } - - var new_vnode = tovnode.toVNode(parseHTMLToDOM(this.toHTML())); - new_vnode.data.hook = _.extend({ - create: this.updateEventListeners.bind(this), - update: this.updateEventListeners.bind(this) - }); - var el = this.vnode ? this.vnode.elm : this.el; - - if (el.outerHTML !== new_vnode.elm.outerHTML) { - this.vnode = patch(this.vnode || this.el, new_vnode); - } - - if (_.isFunction(this.afterRender)) { - this.afterRender(); - } - - return this; - } - }); - return Backbone.VDOMView; -}); - -//# sourceMappingURL=backbone.vdomview.js.map /***/ }), @@ -2393,7 +2433,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat /*! no static exports found */ /***/ (function(module, exports, __webpack_require__) { -/* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Native Javascript for Bootstrap 4 v2.0.22 | © dnp_theme | MIT-License +/* WEBPACK VAR INJECTION */(function(global) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Native Javascript for Bootstrap 4 v2.0.23 | © dnp_theme | MIT-License (function (root, factory) { if (true) { // AMD support: @@ -2476,7 +2516,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat clickEvent = 'click', hoverEvent = 'hover', keydownEvent = 'keydown', - keyupEvent = 'keyup', + keyupEvent = 'keyup', resizeEvent = 'resize', scrollEvent = 'scroll', // originalEvents @@ -2496,18 +2536,20 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat hasAttribute = 'hasAttribute', createElement = 'createElement', appendChild = 'appendChild', - innerHTML = 'innerHTML', + innerHTML = 'innerHTML', getElementsByTagName = 'getElementsByTagName', preventDefault = 'preventDefault', getBoundingClientRect = 'getBoundingClientRect', querySelectorAll = 'querySelectorAll', getElementsByCLASSNAME = 'getElementsByClassName', + getComputedStyle = 'getComputedStyle', indexOf = 'indexOf', parentNode = 'parentNode', length = 'length', toLowerCase = 'toLowerCase', Transition = 'Transition', + Duration = 'Duration', Webkit = 'Webkit', style = 'style', push = 'push', @@ -2527,15 +2569,16 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat // tooltip / popover mouseHover = ('onmouseleave' in DOC) ? [ 'mouseenter', 'mouseleave'] : [ 'mouseover', 'mouseout' ], tipPositions = /\b(top|bottom|left|right)+/, - + // modal modalOverlay = 0, fixedTop = 'fixed-top', fixedBottom = 'fixed-bottom', - + // transitionEnd since 2.0.4 supportTransitions = Webkit+Transition in HTML[style] || Transition[toLowerCase]() in HTML[style], transitionEndEvent = Webkit+Transition in HTML[style] ? Webkit[toLowerCase]()+Transition+'End' : Transition[toLowerCase]()+'end', + transitionDuration = Webkit+Duration in HTML[style] ? Webkit[toLowerCase]()+Transition+Duration : Transition[toLowerCase]()+Duration, // set new focus element since 2.0.3 setFocus = function(element){ @@ -2589,9 +2632,16 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat off(element, event, handlerWrapper); }); }, + getTransitionDurationFromElement = function(element) { + var duration = globalObject[getComputedStyle](element)[transitionDuration]; + duration = parseFloat(duration); + duration = typeof duration === 'number' && !isNaN(duration) ? duration * 1000 : 0; + return duration + 50; // we take a short offset to make sure we fire on the next frame after animation + }, emulateTransitionEnd = function(element,handler){ // emulateTransitionEnd since 2.0.4 - if (supportTransitions) { one(element, transitionEndEvent, function(e){ handler(e); }); } - else { handler(); } + var called = 0, duration = getTransitionDurationFromElement(element); + supportTransitions && one(element, transitionEndEvent, function(e){ handler(e); called = 1; }); + setTimeout(function() { !called && handler(); }, duration); }, bootstrapCustomEvent = function (eventName, componentName, related) { var OriginalCustomEvent = new CustomEvent( eventName + '.bs.' + componentName); @@ -2614,8 +2664,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat scroll = parent === DOC[body] ? getScroll() : { x: parent[offsetLeft] + parent[scrollLeft], y: parent[offsetTop] + parent[scrollTop] }, linkDimensions = { w: rect[right] - rect[left], h: rect[bottom] - rect[top] }, isPopover = hasClass(element,'popover'), - topPosition, leftPosition, - + topPosition, leftPosition, + arrow = queryElement('.arrow',element), arrowTop, arrowLeft, arrowWidth, arrowHeight, @@ -2634,7 +2684,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat position = position === bottom && bottomExceed ? top : position; position = position === left && leftExceed ? right : position; position = position === right && rightExceed ? left : position; - + // update tooltip/popover class element.className[indexOf](position) === -1 && (element.className = element.className.replace(tipPositions,position)); @@ -2687,7 +2737,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat arrowLeft && (arrow[style][left] = arrowLeft + 'px'); }; - BSN.version = '2.0.22'; + BSN.version = '2.0.23'; /* Native Javascript for Bootstrap 4 | Alert -------------------------------------------*/ @@ -2857,7 +2907,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat // DATA API var intervalAttribute = element[getAttribute](dataInterval), intervalOption = options[interval], - intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute) || 5000, // bootstrap carousel default interval + intervalData = intervalAttribute === 'false' ? 0 : parseInt(intervalAttribute), pauseData = element[getAttribute](dataPause) === hoverEvent || false, keyboardData = element[getAttribute](dataKeyboard) === 'true' || false, @@ -2872,8 +2922,8 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat this[pause] = (options[pause] === hoverEvent || pauseData) ? hoverEvent : false; // false / hover this[interval] = typeof intervalOption === 'number' ? intervalOption - : intervalData === 0 ? 0 - : intervalData; + : intervalOption === false || intervalData === 0 || intervalData === false ? 0 + : 5000; // bootstrap carousel default interval // bind, event targets var self = this, index = element.index = 0, timer = element.timer = 0, @@ -2992,10 +3042,10 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat addClass(slides[next],carouselItem +'-'+ slideDirection); addClass(slides[activeItem],carouselItem +'-'+ slideDirection); - one(slides[activeItem], transitionEndEvent, function(e) { - var timeout = e[target] !== slides[activeItem] ? e.elapsedTime*1000 : 0; + one(slides[next], transitionEndEvent, function(e) { + var timeout = e[target] !== slides[next] ? e.elapsedTime*1000+100 : 20; - setTimeout(function(){ + isSliding && setTimeout(function(){ isSliding = false; addClass(slides[next],active); @@ -3010,7 +3060,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat if ( !DOC.hidden && self[interval] && !hasClass(element,paused) ) { self.cycle(); } - },timeout+100); + }, timeout); }); } else { @@ -3075,23 +3125,24 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat // event targets and constants var accordion = null, collapse = null, self = this, - isAnimating = false, // when true it will prevent click handlers accordionData = element[getAttribute]('data-parent'), + activeCollapse, activeElement, // component strings component = 'collapse', collapsed = 'collapsed', + isAnimating = 'isAnimating', // private methods openAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, showEvent, component); - isAnimating = true; + collapseElement[isAnimating] = true; addClass(collapseElement,collapsing); removeClass(collapseElement,component); collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; emulateTransitionEnd(collapseElement, function() { - isAnimating = false; + collapseElement[isAnimating] = false; collapseElement[setAttribute](ariaExpanded,'true'); toggle[setAttribute](ariaExpanded,'true'); removeClass(collapseElement,collapsing); @@ -3103,7 +3154,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat }, closeAction = function(collapseElement,toggle) { bootstrapCustomEvent.call(collapseElement, hideEvent, component); - isAnimating = true; + collapseElement[isAnimating] = true; collapseElement[style][height] = collapseElement[scrollHeight] + 'px'; // set height first removeClass(collapseElement,component); removeClass(collapseElement,showClass); @@ -3112,7 +3163,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat collapseElement[style][height] = '0px'; emulateTransitionEnd(collapseElement, function() { - isAnimating = false; + collapseElement[isAnimating] = false; collapseElement[setAttribute](ariaExpanded,'false'); toggle[setAttribute](ariaExpanded,'false'); removeClass(collapseElement,collapsing); @@ -3131,29 +3182,29 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat // public methods this.toggle = function(e) { e[preventDefault](); - if (isAnimating) return; if (!hasClass(collapse,showClass)) { self.show(); } else { self.hide(); } }; this.hide = function() { + if ( collapse[isAnimating] ) return; closeAction(collapse,element); addClass(element,collapsed); }; this.show = function() { if ( accordion ) { - var activeCollapse = queryElement('.'+component+'.'+showClass,accordion), - toggle = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) - || queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ), - correspondingCollapse = toggle && (toggle[getAttribute](dataTarget) || toggle.href); - if ( activeCollapse && toggle && activeCollapse !== collapse ) { - closeAction(activeCollapse,toggle); - if ( correspondingCollapse.split('#')[1] !== collapse.id ) { addClass(toggle,collapsed); } - else { removeClass(toggle,collapsed); } - } + activeCollapse = queryElement('.'+component+'.'+showClass,accordion); + activeElement = activeCollapse && (queryElement('['+dataToggle+'="'+component+'"]['+dataTarget+'="#'+activeCollapse.id+'"]',accordion) + || queryElement('['+dataToggle+'="'+component+'"][href="#'+activeCollapse.id+'"]',accordion) ); } - openAction(collapse,element); - removeClass(element,collapsed); + if ( !collapse[isAnimating] || activeCollapse && !activeCollapse[isAnimating] ) { + if ( activeElement && activeCollapse !== collapse ) { + closeAction(activeCollapse,activeElement); + addClass(activeElement,collapsed); + } + openAction(collapse,element); + removeClass(element,collapsed); + } }; // init @@ -3161,6 +3212,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat on(element, clickEvent, self.toggle); } collapse = getTarget(); + collapse[isAnimating] = false; // when true it will prevent click handlers accordion = queryElement(options.parent) || accordionData && getClosest(element, accordionData); element[stringCollapse] = self; }; @@ -3318,6 +3370,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat var btnCheck = element[getAttribute](dataTarget)||element[getAttribute]('href'), checkModal = queryElement( btnCheck ), modal = hasClass(element,'modal') ? element : checkModal, + overlayDelay, // strings component = 'modal', @@ -3351,13 +3404,13 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat return globalObject[innerWidth] || (htmlRect[right] - Math.abs(htmlRect[left])); }, setScrollbar = function () { - var bodyStyle = globalObject.getComputedStyle(DOC[body]), + var bodyStyle = globalObject[getComputedStyle](DOC[body]), bodyPad = parseInt((bodyStyle[paddingRight]), 10), itemPad; if (bodyIsOverflowing) { DOC[body][style][paddingRight] = (bodyPad + scrollbarWidth) + 'px'; if (fixedItems[length]){ for (var i = 0; i < fixedItems[length]; i++) { - itemPad = globalObject.getComputedStyle(fixedItems[i])[paddingRight]; + itemPad = globalObject[getComputedStyle](fixedItems[i])[paddingRight]; fixedItems[i][style][paddingRight] = ( parseInt(itemPad) + scrollbarWidth) + 'px'; } } @@ -3499,6 +3552,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat if ( overlay && modalOverlay && !hasClass(overlay,showClass)) { overlay[offsetWidth]; // force reflow to enable trasition + overlayDelay = getTransitionDurationFromElement(overlay); addClass(overlay, showClass); } @@ -3518,18 +3572,19 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat keydownHandlerToggle(); hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerShow) : triggerShow(); - }, supportTransitions ? 150 : 0); + }, supportTransitions && overlay ? overlayDelay : 0); }; this.hide = function() { bootstrapCustomEvent.call(modal, hideEvent, component); overlay = queryElement('.'+modalBackdropString); + overlayDelay = overlay && getTransitionDurationFromElement(overlay); removeClass(modal,showClass); modal[setAttribute](ariaHidden, true); - (function(){ + setTimeout(function(){ hasClass(modal,'fade') ? emulateTransitionEnd(modal, triggerHide) : triggerHide(); - }()); + }, supportTransitions && overlay ? overlayDelay : 0); }; this.setContent = function( content ) { queryElement('.'+component+'-content',modal)[innerHTML] = content; @@ -3885,7 +3940,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat tabsContentContainer[style][height] = nextHeight + 'px'; // height animation tabsContentContainer[offsetWidth]; emulateTransitionEnd(tabsContentContainer, triggerEnd); - },1); + },50); } } else { tabs[isAnimating] = false; @@ -3912,7 +3967,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat tabsContentContainer[style][height] = containerHeight + 'px'; // height animation tabsContentContainer[offsetHeight]; activeContent[style][float] = ''; - nextContent[style][float] = ''; + nextContent[style][float] = ''; } if ( hasClass(nextContent, 'fade') ) { @@ -4317,6 +4372,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat ns.emojiVersion = '3.1'; // you can [optionally] modify this to load alternate emoji versions. see readme for backwards compatibility and version options ns.emojiSize = '32'; ns.greedyMatch = false; // set to true for greedy unicode matching + ns.blacklistChars = ''; ns.imagePathPNG = 'https://cdn.jsdelivr.net/emojione/assets/' + ns.emojiVersion + '/png/'; ns.defaultPathPNG = ns.imagePathPNG; ns.imageTitleTag = true; // set to false to remove title attribute from img tag @@ -4330,7 +4386,7 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat ns.regAscii = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|((\\s|^)"+ns.asciiRegexp+"(?=\\s|$|[!,.?]))", "gi"); ns.regAsciiRisky = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(()"+ns.asciiRegexp+"())", "gi"); - ns.regUnicode = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(?:\uD83C\uDFF3)\uFE0F?\u200D?(?:\uD83C\uDF08)|(?:\uD83D\uDC41)\uFE0F?\u200D?(?:\uD83D\uDDE8)\uFE0F?|[#-9]\uFE0F?\u20E3|(?:(?:\uD83C\uDFF4)(?:\uDB40[\uDC60-\uDCFF]){1,6})|(?:\uD83C[\uDDE0-\uDDFF]){2}|(?:(?:\uD83D[\uDC68\uDC69]))\uFE0F?(?:\uD83C[\uDFFA-\uDFFF])?\u200D?(?:[\u2695\u2696\u2708]|\uD83C[\uDF3E-\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83D[\uDC68\uDC69]|\uD83E[\uDDD0-\uDDDF])(?:\uD83C[\uDFFA-\uDFFF])?\u200D?[\u2640\u2642\u2695\u2696\u2708]?\uFE0F?|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])[\u200D\uFE0F]{0,2}){1,3}(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])\uFE0F?){2,4}|(?:\uD83D[\uDC68\uDC69\uDC6E\uDC71-\uDC87\uDD75\uDE45-\uDE4E]|\uD83E[\uDD26\uDD37]|\uD83C[\uDFC3-\uDFCC]|\uD83E[\uDD38-\uDD3E]|\uD83D[\uDEA3-\uDEB6]|\u26f9|\uD83D\uDC6F)\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])?\u200D?[\u2640\u2642]?\uFE0F?|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85-\uDFCC]|\uD83D[\uDC42-\uDCAA\uDD74-\uDD96\uDE45-\uDE4F\uDEA3-\uDECC]|\uD83E[\uDD18-\uDD3E])\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u2194-\u2199\u21a9-\u21aa]\uFE0F?|[\u0023\u002a]|[\u3030\u303d]\uFE0F?|(?:\ud83c[\udd70-\udd71]|\ud83c\udd8e|\ud83c[\udd91-\udd9a])\uFE0F?|\u24c2\uFE0F?|[\u3297\u3299]\uFE0F?|(?:\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51])\uFE0F?|[\u203c\u2049]\uFE0F?|[\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe]\uFE0F?|[\u00a9\u00ae]\uFE0F?|[\u2122\u2139]\uFE0F?|\ud83c\udc04\uFE0F?|[\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55]\uFE0F?|[\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa]\uFE0F?|\ud83c\udccf|[\u2934\u2935]\uFE0F?)|[\u2700-\u27bf]\uFE0F?|[\ud800-\udbff][\udc00-\udfff]\uFE0F?|[\u2600-\u26FF]\uFE0F?|[\u0030-\u0039]\uFE0F", "g"); + ns.regUnicode = new RegExp("]*>.*?<\/object>|]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(?:\uD83C\uDFF3)\uFE0F?\u200D?(?:\uD83C\uDF08)|(?:\uD83D\uDC41)\uFE0F?\u200D?(?:\uD83D\uDDE8)\uFE0F?|[#-9]\uFE0F?\u20E3|(?:(?:\uD83C\uDFF4)(?:\uDB40[\uDC60-\uDCFF]){1,6})|(?:\uD83C[\uDDE0-\uDDFF]){2}|(?:(?:\uD83D[\uDC68\uDC69]))\uFE0F?(?:\uD83C[\uDFFA-\uDFFF])?\u200D?(?:[\u2695\u2696\u2708]|\uD83C[\uDF3E-\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83D[\uDC68\uDC69]|\uD83E[\uDDD0-\uDDDF])(?:\uD83C[\uDFFA-\uDFFF])?\u200D?[\u2640\u2642\u2695\u2696\u2708]?\uFE0F?|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])[\u200D\uFE0F]{0,2})|(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])|(?:(?:\u2764|\uD83D[\uDC66-\uDC69\uDC8B])\uFE0F?)|(?:\uD83D[\uDC68\uDC69\uDC6E\uDC71-\uDC87\uDD75\uDE45-\uDE4E]|\uD83E[\uDD26\uDD37]|\uD83C[\uDFC3-\uDFCC]|\uD83E[\uDD38-\uDD3E]|\uD83D[\uDEA3-\uDEB6]|\u26f9|\uD83D\uDC6F)\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])?\u200D?[\u2640\u2642]?\uFE0F?|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85-\uDFCC]|\uD83D[\uDC42-\uDCAA\uDD74-\uDD96\uDE45-\uDE4F\uDEA3-\uDECC]|\uD83E[\uDD18-\uDD3E])\uFE0F?(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u2194-\u2199\u21a9-\u21aa]\uFE0F?|[\u0023\u002a]|[\u3030\u303d]\uFE0F?|(?:\ud83c[\udd70-\udd71]|\ud83c\udd8e|\ud83c[\udd91-\udd9a])\uFE0F?|\u24c2\uFE0F?|[\u3297\u3299]\uFE0F?|(?:\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51])\uFE0F?|[\u203c\u2049]\uFE0F?|[\u25aa-\u25ab\u25b6\u25c0\u25fb-\u25fe]\uFE0F?|[\u00a9\u00ae]\uFE0F?|[\u2122\u2139]\uFE0F?|\ud83c\udc04\uFE0F?|[\u2b05-\u2b07\u2b1b-\u2b1c\u2b50\u2b55]\uFE0F?|[\u231a-\u231b\u2328\u23cf\u23e9-\u23f3\u23f8-\u23fa]\uFE0F?|\ud83c\udccf|[\u2934\u2935]\uFE0F?)|[\u2700-\u27bf]\uFE0F?|[\ud800-\udbff][\udc00-\udfff]\uFE0F?|[\u2600-\u26FF]\uFE0F?|[\u0030-\u0039]\uFE0F", "g"); ns.toImage = function(str) { str = ns.unicodeToImage(str); @@ -4491,16 +4547,17 @@ function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterat var replaceWith,unicode,short,fname,alt,category,title,size,ePath; var mappedUnicode = ns.mapUnicodeToShort(); var eList = ns.emojioneList; + var bList = ns.blacklistChars.split(','); str = str.replace(ns.regUnicode, function(unicodeChar) { if( (typeof unicodeChar === 'undefined') || (unicodeChar === '') ) { return unicodeChar; } - else if ( unicodeChar in ns.jsEscapeMap ) + else if ( unicodeChar in ns.jsEscapeMap && bList.indexOf(unicodeChar) === -1 ) { fname = ns.jsEscapeMap[unicodeChar]; } - else if ( ns.greedyMatch && unicodeChar in ns.jsEscapeMapGreedy ) + else if ( ns.greedyMatch && unicodeChar in ns.jsEscapeMapGreedy && bList.indexOf(unicodeChar) === -1 ) { fname = ns.jsEscapeMapGreedy[unicodeChar]; } @@ -4915,6 +4972,5955 @@ if(true) module.exports = this.emojione; /***/ }), +/***/ "./node_modules/hellojs/dist/hello.all.js": +/*!************************************************!*\ + !*** ./node_modules/hellojs/dist/hello.all.js ***! + \************************************************/ +/*! no static exports found */ +/***/ (function(module, exports, __webpack_require__) { + +/* WEBPACK VAR INJECTION */(function(process, setImmediate) {var __WEBPACK_AMD_DEFINE_RESULT__;/*! hellojs v1.16.1 | (c) 2012-2017 Andrew Dodson | MIT https://adodson.com/hello.js/LICENSE */ +// ES5 Object.create +if (!Object.create) { + + // Shim, Object create + // A shim for Object.create(), it adds a prototype to a new object + Object.create = (function() { + + function F() {} + + return function(o) { + + if (arguments.length != 1) { + throw new Error('Object.create implementation only accepts one parameter.'); + } + + F.prototype = o; + return new F(); + }; + + })(); + +} + +// ES5 Object.keys +if (!Object.keys) { + Object.keys = function(o, k, r) { + r = []; + for (k in o) { + if (r.hasOwnProperty.call(o, k)) + r.push(k); + } + + return r; + }; +} + +// ES5 [].indexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(s) { + + for (var j = 0; j < this.length; j++) { + if (this[j] === s) { + return j; + } + } + + return -1; + }; +} + +// ES5 [].forEach +if (!Array.prototype.forEach) { + Array.prototype.forEach = function(fun/*, thisArg*/) { + + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== 'function') { + throw new TypeError(); + } + + var thisArg = arguments.length >= 2 ? arguments[1] : void 0; + for (var i = 0; i < len; i++) { + if (i in t) { + fun.call(thisArg, t[i], i, t); + } + } + + return this; + }; +} + +// ES5 [].filter +if (!Array.prototype.filter) { + Array.prototype.filter = function(fun, thisArg) { + + var a = []; + this.forEach(function(val, i, t) { + if (fun.call(thisArg || void 0, val, i, t)) { + a.push(val); + } + }); + + return a; + }; +} + +// Production steps of ECMA-262, Edition 5, 15.4.4.19 +// Reference: http://es5.github.io/#x15.4.4.19 +if (!Array.prototype.map) { + + Array.prototype.map = function(fun, thisArg) { + + var a = []; + this.forEach(function(val, i, t) { + a.push(fun.call(thisArg || void 0, val, i, t)); + }); + + return a; + }; +} + +// ES5 isArray +if (!Array.isArray) { + + // Function Array.isArray + Array.isArray = function(o) { + return Object.prototype.toString.call(o) === '[object Array]'; + }; + +} + +// Test for location.assign +if (typeof window === 'object' && typeof window.location === 'object' && !window.location.assign) { + + window.location.assign = function(url) { + window.location = url; + }; + +} + +// Test for Function.bind +if (!Function.prototype.bind) { + + // MDN + // Polyfill IE8, does not support native Function.bind + Function.prototype.bind = function(b) { + + if (typeof this !== 'function') { + throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + + function C() {} + + var a = [].slice; + var f = a.call(arguments, 1); + var _this = this; + var D = function() { + return _this.apply(this instanceof C ? this : b || window, f.concat(a.call(arguments))); + }; + + C.prototype = this.prototype; + D.prototype = new C(); + + return D; + }; + +} + +/** + * @hello.js + * + * HelloJS is a client side Javascript SDK for making OAuth2 logins and subsequent REST calls. + * + * @author Andrew Dodson + * @website https://adodson.com/hello.js/ + * + * @copyright Andrew Dodson, 2012 - 2015 + * @license MIT: You are free to use and modify this code for any use, on the condition that this copyright notice remains. + */ + +var hello = function(name) { + return hello.use(name); +}; + +hello.utils = { + + // Extend the first object with the properties and methods of the second + extend: function(r /*, a[, b[, ...]] */) { + + // Get the arguments as an array but ommit the initial item + Array.prototype.slice.call(arguments, 1).forEach(function(a) { + if (Array.isArray(r) && Array.isArray(a)) { + Array.prototype.push.apply(r, a); + } + else if (r && (r instanceof Object || typeof r === 'object') && a && (a instanceof Object || typeof a === 'object') && r !== a) { + for (var x in a) { + r[x] = hello.utils.extend(r[x], a[x]); + } + } + else { + + if (Array.isArray(a)) { + // Clone it + a = a.slice(0); + } + + r = a; + } + }); + + return r; + } +}; + +// Core library +hello.utils.extend(hello, { + + settings: { + + // OAuth2 authentication defaults + redirect_uri: window.location.href.split('#')[0], + response_type: 'token', + display: 'popup', + state: '', + + // OAuth1 shim + // The path to the OAuth1 server for signing user requests + // Want to recreate your own? Checkout https://github.com/MrSwitch/node-oauth-shim + oauth_proxy: 'https://auth-server.herokuapp.com/proxy', + + // API timeout in milliseconds + timeout: 20000, + + // Popup Options + popup: { + resizable: 1, + scrollbars: 1, + width: 500, + height: 550 + }, + + // Default scope + // Many services require atleast a profile scope, + // HelloJS automatially includes the value of provider.scope_map.basic + // If that's not required it can be removed via hello.settings.scope.length = 0; + scope: ['basic'], + + // Scope Maps + // This is the default module scope, these are the defaults which each service is mapped too. + // By including them here it prevents the scope from being applied accidentally + scope_map: { + basic: '' + }, + + // Default service / network + default_service: null, + + // Force authentication + // When hello.login is fired. + // (null): ignore current session expiry and continue with login + // (true): ignore current session expiry and continue with login, ask for user to reauthenticate + // (false): if the current session looks good for the request scopes return the current session. + force: null, + + // Page URL + // When 'display=page' this property defines where the users page should end up after redirect_uri + // Ths could be problematic if the redirect_uri is indeed the final place, + // Typically this circumvents the problem of the redirect_url being a dumb relay page. + page_uri: window.location.href + }, + + // Service configuration objects + services: {}, + + // Use + // Define a new instance of the HelloJS library with a default service + use: function(service) { + + // Create self, which inherits from its parent + var self = Object.create(this); + + // Inherit the prototype from its parent + self.settings = Object.create(this.settings); + + // Define the default service + if (service) { + self.settings.default_service = service; + } + + // Create an instance of Events + self.utils.Event.call(self); + + return self; + }, + + // Initialize + // Define the client_ids for the endpoint services + // @param object o, contains a key value pair, service => clientId + // @param object opts, contains a key value pair of options used for defining the authentication defaults + // @param number timeout, timeout in seconds + init: function(services, options) { + + var utils = this.utils; + + if (!services) { + return this.services; + } + + // Define provider credentials + // Reformat the ID field + for (var x in services) {if (services.hasOwnProperty(x)) { + if (typeof (services[x]) !== 'object') { + services[x] = {id: services[x]}; + } + }} + + // Merge services if there already exists some + utils.extend(this.services, services); + + // Update the default settings with this one. + if (options) { + utils.extend(this.settings, options); + + // Do this immediatly incase the browser changes the current path. + if ('redirect_uri' in options) { + this.settings.redirect_uri = utils.url(options.redirect_uri).href; + } + } + + return this; + }, + + // Login + // Using the endpoint + // @param network stringify name to connect to + // @param options object (optional) {display mode, is either none|popup(default)|page, scope: email,birthday,publish, .. } + // @param callback function (optional) fired on signin + login: function() { + + // Create an object which inherits its parent as the prototype and constructs a new event chain. + var _this = this; + var utils = _this.utils; + var error = utils.error; + var promise = utils.Promise(); + + // Get parameters + var p = utils.args({network: 's', options: 'o', callback: 'f'}, arguments); + + // Local vars + var url; + + // Get all the custom options and store to be appended to the querystring + var qs = utils.diffKey(p.options, _this.settings); + + // Merge/override options with app defaults + var opts = p.options = utils.merge(_this.settings, p.options || {}); + + // Merge/override options with app defaults + opts.popup = utils.merge(_this.settings.popup, p.options.popup || {}); + + // Network + p.network = p.network || _this.settings.default_service; + + // Bind callback to both reject and fulfill states + promise.proxy.then(p.callback, p.callback); + + // Trigger an event on the global listener + function emit(s, value) { + hello.emit(s, value); + } + + promise.proxy.then(emit.bind(this, 'auth.login auth'), emit.bind(this, 'auth.failed auth')); + + // Is our service valid? + if (typeof (p.network) !== 'string' || !(p.network in _this.services)) { + // Trigger the default login. + // Ahh we dont have one. + return promise.reject(error('invalid_network', 'The provided network was not recognized')); + } + + var provider = _this.services[p.network]; + + // Create a global listener to capture events triggered out of scope + var callbackId = utils.globalEvent(function(str) { + + // The responseHandler returns a string, lets save this locally + var obj; + + if (str) { + obj = JSON.parse(str); + } + else { + obj = error('cancelled', 'The authentication was not completed'); + } + + // Handle these response using the local + // Trigger on the parent + if (!obj.error) { + + // Save on the parent window the new credentials + // This fixes an IE10 bug i think... atleast it does for me. + utils.store(obj.network, obj); + + // Fulfill a successful login + promise.fulfill({ + network: obj.network, + authResponse: obj + }); + } + else { + // Reject a successful login + promise.reject(obj); + } + }); + + var redirectUri = utils.url(opts.redirect_uri).href; + + // May be a space-delimited list of multiple, complementary types + var responseType = provider.oauth.response_type || opts.response_type; + + // Fallback to token if the module hasn't defined a grant url + if (/\bcode\b/.test(responseType) && !provider.oauth.grant) { + responseType = responseType.replace(/\bcode\b/, 'token'); + } + + // Query string parameters, we may pass our own arguments to form the querystring + p.qs = utils.merge(qs, { + client_id: encodeURIComponent(provider.id), + response_type: encodeURIComponent(responseType), + redirect_uri: encodeURIComponent(redirectUri), + state: { + client_id: provider.id, + network: p.network, + display: opts.display, + callback: callbackId, + state: opts.state, + redirect_uri: redirectUri + } + }); + + // Get current session for merging scopes, and for quick auth response + var session = utils.store(p.network); + + // Scopes (authentication permisions) + // Ensure this is a string - IE has a problem moving Arrays between windows + // Append the setup scope + var SCOPE_SPLIT = /[,\s]+/; + + // Include default scope settings (cloned). + var scope = _this.settings.scope ? [_this.settings.scope.toString()] : []; + + // Extend the providers scope list with the default + var scopeMap = utils.merge(_this.settings.scope_map, provider.scope || {}); + + // Add user defined scopes... + if (opts.scope) { + scope.push(opts.scope.toString()); + } + + // Append scopes from a previous session. + // This helps keep app credentials constant, + // Avoiding having to keep tabs on what scopes are authorized + if (session && 'scope' in session && session.scope instanceof String) { + scope.push(session.scope); + } + + // Join and Split again + scope = scope.join(',').split(SCOPE_SPLIT); + + // Format remove duplicates and empty values + scope = utils.unique(scope).filter(filterEmpty); + + // Save the the scopes to the state with the names that they were requested with. + p.qs.state.scope = scope.join(','); + + // Map scopes to the providers naming convention + scope = scope.map(function(item) { + // Does this have a mapping? + return (item in scopeMap) ? scopeMap[item] : item; + }); + + // Stringify and Arrayify so that double mapped scopes are given the chance to be formatted + scope = scope.join(',').split(SCOPE_SPLIT); + + // Again... + // Format remove duplicates and empty values + scope = utils.unique(scope).filter(filterEmpty); + + // Join with the expected scope delimiter into a string + p.qs.scope = scope.join(provider.scope_delim || ','); + + // Is the user already signed in with the appropriate scopes, valid access_token? + if (opts.force === false) { + + if (session && 'access_token' in session && session.access_token && 'expires' in session && session.expires > ((new Date()).getTime() / 1e3)) { + // What is different about the scopes in the session vs the scopes in the new login? + var diff = utils.diff((session.scope || '').split(SCOPE_SPLIT), (p.qs.state.scope || '').split(SCOPE_SPLIT)); + if (diff.length === 0) { + + // OK trigger the callback + promise.fulfill({ + unchanged: true, + network: p.network, + authResponse: session + }); + + // Nothing has changed + return promise; + } + } + } + + // Page URL + if (opts.display === 'page' && opts.page_uri) { + // Add a page location, place to endup after session has authenticated + p.qs.state.page_uri = utils.url(opts.page_uri).href; + } + + // Bespoke + // Override login querystrings from auth_options + if ('login' in provider && typeof (provider.login) === 'function') { + // Format the paramaters according to the providers formatting function + provider.login(p); + } + + // Add OAuth to state + // Where the service is going to take advantage of the oauth_proxy + if (!/\btoken\b/.test(responseType) || + parseInt(provider.oauth.version, 10) < 2 || + (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token)) { + + // Add the oauth endpoints + p.qs.state.oauth = provider.oauth; + + // Add the proxy url + p.qs.state.oauth_proxy = opts.oauth_proxy; + + } + + // Convert state to a string + p.qs.state = encodeURIComponent(JSON.stringify(p.qs.state)); + + // URL + if (parseInt(provider.oauth.version, 10) === 1) { + + // Turn the request to the OAuth Proxy for 3-legged auth + url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction); + } + + // Refresh token + else if (opts.display === 'none' && provider.oauth.grant && session && session.refresh_token) { + + // Add the refresh_token to the request + p.qs.refresh_token = session.refresh_token; + + // Define the request path + url = utils.qs(opts.oauth_proxy, p.qs, encodeFunction); + } + else { + url = utils.qs(provider.oauth.auth, p.qs, encodeFunction); + } + + // Broadcast this event as an auth:init + emit('auth.init', p); + + // Execute + // Trigger how we want self displayed + if (opts.display === 'none') { + // Sign-in in the background, iframe + utils.iframe(url, redirectUri); + } + + // Triggering popup? + else if (opts.display === 'popup') { + + var popup = utils.popup(url, redirectUri, opts.popup); + + var timer = setInterval(function() { + if (!popup || popup.closed) { + clearInterval(timer); + if (!promise.state) { + + var response = error('cancelled', 'Login has been cancelled'); + + if (!popup) { + response = error('blocked', 'Popup was blocked'); + } + + response.network = p.network; + + promise.reject(response); + } + } + }, 100); + } + + else { + window.location = url; + } + + return promise.proxy; + + function encodeFunction(s) {return s;} + + function filterEmpty(s) {return !!s;} + }, + + // Remove any data associated with a given service + // @param string name of the service + // @param function callback + logout: function() { + + var _this = this; + var utils = _this.utils; + var error = utils.error; + + // Create a new promise + var promise = utils.Promise(); + + var p = utils.args({name:'s', options: 'o', callback: 'f'}, arguments); + + p.options = p.options || {}; + + // Add callback to events + promise.proxy.then(p.callback, p.callback); + + // Trigger an event on the global listener + function emit(s, value) { + hello.emit(s, value); + } + + promise.proxy.then(emit.bind(this, 'auth.logout auth'), emit.bind(this, 'error')); + + // Network + p.name = p.name || this.settings.default_service; + p.authResponse = utils.store(p.name); + + if (p.name && !(p.name in _this.services)) { + + promise.reject(error('invalid_network', 'The network was unrecognized')); + + } + else if (p.name && p.authResponse) { + + // Define the callback + var callback = function(opts) { + + // Remove from the store + utils.store(p.name, null); + + // Emit events by default + promise.fulfill(hello.utils.merge({network:p.name}, opts || {})); + }; + + // Run an async operation to remove the users session + var _opts = {}; + if (p.options.force) { + var logout = _this.services[p.name].logout; + if (logout) { + // Convert logout to URL string, + // If no string is returned, then this function will handle the logout async style + if (typeof (logout) === 'function') { + logout = logout(callback, p); + } + + // If logout is a string then assume URL and open in iframe. + if (typeof (logout) === 'string') { + utils.iframe(logout); + _opts.force = null; + _opts.message = 'Logout success on providers site was indeterminate'; + } + else if (logout === undefined) { + // The callback function will handle the response. + return promise.proxy; + } + } + } + + // Remove local credentials + callback(_opts); + } + else { + promise.reject(error('invalid_session', 'There was no session to remove')); + } + + return promise.proxy; + }, + + // Returns all the sessions that are subscribed too + // @param string optional, name of the service to get information about. + getAuthResponse: function(service) { + + // If the service doesn't exist + service = service || this.settings.default_service; + + if (!service || !(service in this.services)) { + return null; + } + + return this.utils.store(service) || null; + }, + + // Events: placeholder for the events + events: {} +}); + +// Core utilities +hello.utils.extend(hello.utils, { + + // Error + error: function(code, message) { + return { + error: { + code: code, + message: message + } + }; + }, + + // Append the querystring to a url + // @param string url + // @param object parameters + qs: function(url, params, formatFunction) { + + if (params) { + + // Set default formatting function + formatFunction = formatFunction || encodeURIComponent; + + // Override the items in the URL which already exist + for (var x in params) { + var str = '([\\?\\&])' + x + '=[^\\&]*'; + var reg = new RegExp(str); + if (url.match(reg)) { + url = url.replace(reg, '$1' + x + '=' + formatFunction(params[x])); + delete params[x]; + } + } + } + + if (!this.isEmpty(params)) { + return url + (url.indexOf('?') > -1 ? '&' : '?') + this.param(params, formatFunction); + } + + return url; + }, + + // Param + // Explode/encode the parameters of an URL string/object + // @param string s, string to decode + param: function(s, formatFunction) { + var b; + var a = {}; + var m; + + if (typeof (s) === 'string') { + + formatFunction = formatFunction || decodeURIComponent; + + m = s.replace(/^[\#\?]/, '').match(/([^=\/\&]+)=([^\&]+)/g); + if (m) { + for (var i = 0; i < m.length; i++) { + b = m[i].match(/([^=]+)=(.*)/); + a[b[1]] = formatFunction(b[2]); + } + } + + return a; + } + else { + + formatFunction = formatFunction || encodeURIComponent; + + var o = s; + + a = []; + + for (var x in o) {if (o.hasOwnProperty(x)) { + if (o.hasOwnProperty(x)) { + a.push([x, o[x] === '?' ? '?' : formatFunction(o[x])].join('=')); + } + }} + + return a.join('&'); + } + }, + + // Local storage facade + store: (function() { + + var a = ['localStorage', 'sessionStorage']; + var i = -1; + var prefix = 'test'; + + // Set LocalStorage + var localStorage; + + while (a[++i]) { + try { + // In Chrome with cookies blocked, calling localStorage throws an error + localStorage = window[a[i]]; + localStorage.setItem(prefix + i, i); + localStorage.removeItem(prefix + i); + break; + } + catch (e) { + localStorage = null; + } + } + + if (!localStorage) { + + var cache = null; + + localStorage = { + getItem: function(prop) { + prop = prop + '='; + var m = document.cookie.split(';'); + for (var i = 0; i < m.length; i++) { + var _m = m[i].replace(/(^\s+|\s+$)/, ''); + if (_m && _m.indexOf(prop) === 0) { + return _m.substr(prop.length); + } + } + + return cache; + }, + + setItem: function(prop, value) { + cache = value; + document.cookie = prop + '=' + value; + } + }; + + // Fill the cache up + cache = localStorage.getItem('hello'); + } + + function get() { + var json = {}; + try { + json = JSON.parse(localStorage.getItem('hello')) || {}; + } + catch (e) {} + + return json; + } + + function set(json) { + localStorage.setItem('hello', JSON.stringify(json)); + } + + // Check if the browser support local storage + return function(name, value, days) { + + // Local storage + var json = get(); + + if (name && value === undefined) { + return json[name] || null; + } + else if (name && value === null) { + try { + delete json[name]; + } + catch (e) { + json[name] = null; + } + } + else if (name) { + json[name] = value; + } + else { + return json; + } + + set(json); + + return json || null; + }; + + })(), + + // Create and Append new DOM elements + // @param node string + // @param attr object literal + // @param dom/string + append: function(node, attr, target) { + + var n = typeof (node) === 'string' ? document.createElement(node) : node; + + if (typeof (attr) === 'object') { + if ('tagName' in attr) { + target = attr; + } + else { + for (var x in attr) {if (attr.hasOwnProperty(x)) { + if (typeof (attr[x]) === 'object') { + for (var y in attr[x]) {if (attr[x].hasOwnProperty(y)) { + n[x][y] = attr[x][y]; + }} + } + else if (x === 'html') { + n.innerHTML = attr[x]; + } + + // IE doesn't like us setting methods with setAttribute + else if (!/^on/.test(x)) { + n.setAttribute(x, attr[x]); + } + else { + n[x] = attr[x]; + } + }} + } + } + + if (target === 'body') { + (function self() { + if (document.body) { + document.body.appendChild(n); + } + else { + setTimeout(self, 16); + } + })(); + } + else if (typeof (target) === 'object') { + target.appendChild(n); + } + else if (typeof (target) === 'string') { + document.getElementsByTagName(target)[0].appendChild(n); + } + + return n; + }, + + // An easy way to create a hidden iframe + // @param string src + iframe: function(src) { + this.append('iframe', {src: src, style: {position:'absolute', left: '-1000px', bottom: 0, height: '1px', width: '1px'}}, 'body'); + }, + + // Recursive merge two objects into one, second parameter overides the first + // @param a array + merge: function(/* Args: a, b, c, .. n */) { + var args = Array.prototype.slice.call(arguments); + args.unshift({}); + return this.extend.apply(null, args); + }, + + // Makes it easier to assign parameters, where some are optional + // @param o object + // @param a arguments + args: function(o, args) { + + var p = {}; + var i = 0; + var t = null; + var x = null; + + // 'x' is the first key in the list of object parameters + for (x in o) {if (o.hasOwnProperty(x)) { + break; + }} + + // Passing in hash object of arguments? + // Where the first argument can't be an object + if ((args.length === 1) && (typeof (args[0]) === 'object') && o[x] != 'o!') { + + // Could this object still belong to a property? + // Check the object keys if they match any of the property keys + for (x in args[0]) {if (o.hasOwnProperty(x)) { + // Does this key exist in the property list? + if (x in o) { + // Yes this key does exist so its most likely this function has been invoked with an object parameter + // Return first argument as the hash of all arguments + return args[0]; + } + }} + } + + // Else loop through and account for the missing ones. + for (x in o) {if (o.hasOwnProperty(x)) { + + t = typeof (args[i]); + + if ((typeof (o[x]) === 'function' && o[x].test(args[i])) || (typeof (o[x]) === 'string' && ( + (o[x].indexOf('s') > -1 && t === 'string') || + (o[x].indexOf('o') > -1 && t === 'object') || + (o[x].indexOf('i') > -1 && t === 'number') || + (o[x].indexOf('a') > -1 && t === 'object') || + (o[x].indexOf('f') > -1 && t === 'function') + )) + ) { + p[x] = args[i++]; + } + + else if (typeof (o[x]) === 'string' && o[x].indexOf('!') > -1) { + return false; + } + }} + + return p; + }, + + // Returns a URL instance + url: function(path) { + + // If the path is empty + if (!path) { + return window.location; + } + + // Chrome and FireFox support new URL() to extract URL objects + else if (window.URL && URL instanceof Function && URL.length !== 0) { + return new URL(path, window.location); + } + + // Ugly shim, it works! + else { + var a = document.createElement('a'); + a.href = path; + return a.cloneNode(false); + } + }, + + diff: function(a, b) { + return b.filter(function(item) { + return a.indexOf(item) === -1; + }); + }, + + // Get the different hash of properties unique to `a`, and not in `b` + diffKey: function(a, b) { + if (a || !b) { + var r = {}; + for (var x in a) { + // Does the property not exist? + if (!(x in b)) { + r[x] = a[x]; + } + } + + return r; + } + + return a; + }, + + // Unique + // Remove duplicate and null values from an array + // @param a array + unique: function(a) { + if (!Array.isArray(a)) { return []; } + + return a.filter(function(item, index) { + // Is this the first location of item + return a.indexOf(item) === index; + }); + }, + + isEmpty: function(obj) { + + // Scalar + if (!obj) + return true; + + // Array + if (Array.isArray(obj)) { + return !obj.length; + } + else if (typeof (obj) === 'object') { + // Object + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + return false; + } + } + } + + return true; + }, + + //jscs:disable + + /*! + ** Thenable -- Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable + ** Copyright (c) 2013-2014 Ralf S. Engelschall + ** Licensed under The MIT License + ** Source-Code distributed on + */ + Promise: (function(){ + /* promise states [Promises/A+ 2.1] */ + var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */ + var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */ + var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */ + + /* promise object constructor */ + var api = function (executor) { + /* optionally support non-constructor/plain-function call */ + if (!(this instanceof api)) + return new api(executor); + + /* initialize object */ + this.id = "Thenable/1.0.6"; + this.state = STATE_PENDING; /* initial state */ + this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */ + this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */ + this.onFulfilled = []; /* initial handlers */ + this.onRejected = []; /* initial handlers */ + + /* provide optional information-hiding proxy */ + this.proxy = { + then: this.then.bind(this) + }; + + /* support optional executor function */ + if (typeof executor === "function") + executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); + }; + + /* promise API methods */ + api.prototype = { + /* promise resolving methods */ + fulfill: function (value) { return deliver(this, STATE_FULFILLED, "fulfillValue", value); }, + reject: function (value) { return deliver(this, STATE_REJECTED, "rejectReason", value); }, + + /* "The then Method" [Promises/A+ 1.1, 1.2, 2.2] */ + then: function (onFulfilled, onRejected) { + var curr = this; + var next = new api(); /* [Promises/A+ 2.2.7] */ + curr.onFulfilled.push( + resolver(onFulfilled, next, "fulfill")); /* [Promises/A+ 2.2.2/2.2.6] */ + curr.onRejected.push( + resolver(onRejected, next, "reject" )); /* [Promises/A+ 2.2.3/2.2.6] */ + execute(curr); + return next.proxy; /* [Promises/A+ 2.2.7, 3.3] */ + } + }; + + /* deliver an action */ + var deliver = function (curr, state, name, value) { + if (curr.state === STATE_PENDING) { + curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ + curr[name] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ + execute(curr); + } + return curr; + }; + + /* execute all handlers */ + var execute = function (curr) { + if (curr.state === STATE_FULFILLED) + execute_handlers(curr, "onFulfilled", curr.fulfillValue); + else if (curr.state === STATE_REJECTED) + execute_handlers(curr, "onRejected", curr.rejectReason); + }; + + /* execute particular set of handlers */ + var execute_handlers = function (curr, name, value) { + /* global process: true */ + /* global setImmediate: true */ + /* global setTimeout: true */ + + /* short-circuit processing */ + if (curr[name].length === 0) + return; + + /* iterate over all handlers, exactly once */ + var handlers = curr[name]; + curr[name] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ + var func = function () { + for (var i = 0; i < handlers.length; i++) + handlers[i](value); /* [Promises/A+ 2.2.5] */ + }; + + /* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */ + if (typeof process === "object" && typeof process.nextTick === "function") + process.nextTick(func); + else if (typeof setImmediate === "function") + setImmediate(func); + else + setTimeout(func, 0); + }; + + /* generate a resolver function */ + var resolver = function (cb, next, method) { + return function (value) { + if (typeof cb !== "function") /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ + next[method].call(next, value); /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ + else { + var result; + try { result = cb(value); } /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ + catch (e) { + next.reject(e); /* [Promises/A+ 2.2.7.2] */ + return; + } + resolve(next, result); /* [Promises/A+ 2.2.7.1] */ + } + }; + }; + + /* "Promise Resolution Procedure" */ /* [Promises/A+ 2.3] */ + var resolve = function (promise, x) { + /* sanity check arguments */ /* [Promises/A+ 2.3.1] */ + if (promise === x || promise.proxy === x) { + promise.reject(new TypeError("cannot resolve promise with itself")); + return; + } + + /* surgically check for a "then" method + (mainly to just call the "getter" of "then" only once) */ + var then; + if ((typeof x === "object" && x !== null) || typeof x === "function") { + try { then = x.then; } /* [Promises/A+ 2.3.3.1, 3.5] */ + catch (e) { + promise.reject(e); /* [Promises/A+ 2.3.3.2] */ + return; + } + } + + /* handle own Thenables [Promises/A+ 2.3.2] + and similar "thenables" [Promises/A+ 2.3.3] */ + if (typeof then === "function") { + var resolved = false; + try { + /* call retrieved "then" method */ /* [Promises/A+ 2.3.3.3] */ + then.call(x, + /* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */ + function (y) { + if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ + if (y === x) /* [Promises/A+ 3.6] */ + promise.reject(new TypeError("circular thenable chain")); + else + resolve(promise, y); + }, + + /* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */ + function (r) { + if (resolved) return; resolved = true; /* [Promises/A+ 2.3.3.3.3] */ + promise.reject(r); + } + ); + } + catch (e) { + if (!resolved) /* [Promises/A+ 2.3.3.3.3] */ + promise.reject(e); /* [Promises/A+ 2.3.3.3.4] */ + } + return; + } + + /* handle other values */ + promise.fulfill(x); /* [Promises/A+ 2.3.4, 2.3.3.4] */ + }; + + /* export API */ + return api; + })(), + + //jscs:enable + + // Event + // A contructor superclass for adding event menthods, on, off, emit. + Event: function() { + + var separator = /[\s\,]+/; + + // If this doesn't support getPrototype then we can't get prototype.events of the parent + // So lets get the current instance events, and add those to a parent property + this.parent = { + events: this.events, + findEvents: this.findEvents, + parent: this.parent, + utils: this.utils + }; + + this.events = {}; + + // On, subscribe to events + // @param evt string + // @param callback function + this.on = function(evt, callback) { + + if (callback && typeof (callback) === 'function') { + var a = evt.split(separator); + for (var i = 0; i < a.length; i++) { + + // Has this event already been fired on this instance? + this.events[a[i]] = [callback].concat(this.events[a[i]] || []); + } + } + + return this; + }; + + // Off, unsubscribe to events + // @param evt string + // @param callback function + this.off = function(evt, callback) { + + this.findEvents(evt, function(name, index) { + if (!callback || this.events[name][index] === callback) { + this.events[name][index] = null; + } + }); + + return this; + }; + + // Emit + // Triggers any subscribed events + this.emit = function(evt /*, data, ... */) { + + // Get arguments as an Array, knock off the first one + var args = Array.prototype.slice.call(arguments, 1); + args.push(evt); + + // Handler + var handler = function(name, index) { + + // Replace the last property with the event name + args[args.length - 1] = (name === '*' ? evt : name); + + // Trigger + this.events[name][index].apply(this, args); + }; + + // Find the callbacks which match the condition and call + var _this = this; + while (_this && _this.findEvents) { + + // Find events which match + _this.findEvents(evt + ',*', handler); + _this = _this.parent; + } + + return this; + }; + + // + // Easy functions + this.emitAfter = function() { + var _this = this; + var args = arguments; + setTimeout(function() { + _this.emit.apply(_this, args); + }, 0); + + return this; + }; + + this.findEvents = function(evt, callback) { + + var a = evt.split(separator); + + for (var name in this.events) {if (this.events.hasOwnProperty(name)) { + + if (a.indexOf(name) > -1) { + + for (var i = 0; i < this.events[name].length; i++) { + + // Does the event handler exist? + if (this.events[name][i]) { + // Emit on the local instance of this + callback.call(this, name, i); + } + } + } + }} + }; + + return this; + }, + + // Global Events + // Attach the callback to the window object + // Return its unique reference + globalEvent: function(callback, guid) { + // If the guid has not been supplied then create a new one. + guid = guid || '_hellojs_' + parseInt(Math.random() * 1e12, 10).toString(36); + + // Define the callback function + window[guid] = function() { + // Trigger the callback + try { + if (callback.apply(this, arguments)) { + delete window[guid]; + } + } + catch (e) { + console.error(e); + } + }; + + return guid; + }, + + // Trigger a clientside popup + // This has been augmented to support PhoneGap + popup: function(url, redirectUri, options) { + + var documentElement = document.documentElement; + + // Multi Screen Popup Positioning (http://stackoverflow.com/a/16861050) + // Credit: http://www.xtf.dk/2011/08/center-new-popup-window-even-on.html + // Fixes dual-screen position Most browsers Firefox + + if (options.height) { + var dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; + var height = screen.height || window.innerHeight || documentElement.clientHeight; + options.top = (options.top) ? options.top : parseInt((height - options.height) / 2, 10) + dualScreenTop; + } + + if (options.width) { + var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; + var width = screen.width || window.innerWidth || documentElement.clientWidth; + options.left = (options.left) ? options.left : parseInt((width - options.width) / 2, 10) + dualScreenLeft; + } + + // Convert options into an array + var optionsArray = []; + Object.keys(options).forEach(function(name) { + var value = options[name]; + optionsArray.push(name + (value !== null ? '=' + value : '')); + }); + + // Call the open() function with the initial path + // + // OAuth redirect, fixes URI fragments from being lost in Safari + // (URI Fragments within 302 Location URI are lost over HTTPS) + // Loading the redirect.html before triggering the OAuth Flow seems to fix it. + // + // Firefox decodes URL fragments when calling location.hash. + // - This is bad if the value contains break points which are escaped + // - Hence the url must be encoded twice as it contains breakpoints. + if (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) { + url = redirectUri + '#oauth_redirect=' + encodeURIComponent(encodeURIComponent(url)); + } + + var popup = window.open( + url, + '_blank', + optionsArray.join(',') + ); + + if (popup && popup.focus) { + popup.focus(); + } + + return popup; + }, + + // OAuth and API response handler + responseHandler: function(window, parent) { + + var _this = this; + var p; + var location = window.location; + + // Is this an auth relay message which needs to call the proxy? + p = _this.param(location.search); + + // OAuth2 or OAuth1 server response? + if (p && p.state && (p.code || p.oauth_token)) { + + var state = JSON.parse(p.state); + + // Add this path as the redirect_uri + p.redirect_uri = state.redirect_uri || location.href.replace(/[\?\#].*$/, ''); + + // Redirect to the host + var path = _this.qs(state.oauth_proxy, p); + + location.assign(path); + + return; + } + + // Save session, from redirected authentication + // #access_token has come in? + // + // FACEBOOK is returning auth errors within as a query_string... thats a stickler for consistency. + // SoundCloud is the state in the querystring and the token in the hashtag, so we'll mix the two together + + p = _this.merge(_this.param(location.search || ''), _this.param(location.hash || '')); + + // If p.state + if (p && 'state' in p) { + + // Remove any addition information + // E.g. p.state = 'facebook.page'; + try { + var a = JSON.parse(p.state); + _this.extend(p, a); + } + catch (e) { + var stateDecoded = decodeURIComponent(p.state); + try { + var b = JSON.parse(stateDecoded); + _this.extend(p, b); + } + catch (e) { + console.error('Could not decode state parameter'); + } + } + + // Access_token? + if (('access_token' in p && p.access_token) && p.network) { + + if (!p.expires_in || parseInt(p.expires_in, 10) === 0) { + // If p.expires_in is unset, set to 0 + p.expires_in = 0; + } + + p.expires_in = parseInt(p.expires_in, 10); + p.expires = ((new Date()).getTime() / 1e3) + (p.expires_in || (60 * 60 * 24 * 365)); + + // Lets use the "state" to assign it to one of our networks + authCallback(p, window, parent); + } + + // Error=? + // &error_description=? + // &state=? + else if (('error' in p && p.error) && p.network) { + + p.error = { + code: p.error, + message: p.error_message || p.error_description + }; + + // Let the state handler handle it + authCallback(p, window, parent); + } + + // API call, or a cancelled login + // Result is serialized JSON string + else if (p.callback && p.callback in parent) { + + // Trigger a function in the parent + var res = 'result' in p && p.result ? JSON.parse(p.result) : false; + + // Trigger the callback on the parent + callback(parent, p.callback)(res); + closeWindow(); + } + + // If this page is still open + if (p.page_uri) { + location.assign(p.page_uri); + } + } + + // OAuth redirect, fixes URI fragments from being lost in Safari + // (URI Fragments within 302 Location URI are lost over HTTPS) + // Loading the redirect.html before triggering the OAuth Flow seems to fix it. + else if ('oauth_redirect' in p) { + + location.assign(decodeURIComponent(p.oauth_redirect)); + return; + } + + // Trigger a callback to authenticate + function authCallback(obj, window, parent) { + + var cb = obj.callback; + var network = obj.network; + + // Trigger the callback on the parent + _this.store(network, obj); + + // If this is a page request it has no parent or opener window to handle callbacks + if (('display' in obj) && obj.display === 'page') { + return; + } + + // Remove from session object + if (parent && cb && cb in parent) { + + try { + delete obj.callback; + } + catch (e) {} + + // Update store + _this.store(network, obj); + + // Call the globalEvent function on the parent + // It's safer to pass back a string to the parent, + // Rather than an object/array (better for IE8) + var str = JSON.stringify(obj); + + try { + callback(parent, cb)(str); + } + catch (e) { + // Error thrown whilst executing parent callback + } + } + + closeWindow(); + } + + function callback(parent, callbackID) { + if (callbackID.indexOf('_hellojs_') !== 0) { + return function() { + throw 'Could not execute callback ' + callbackID; + }; + } + + return parent[callbackID]; + } + + function closeWindow() { + + if (window.frameElement) { + // Inside an iframe, remove from parent + parent.document.body.removeChild(window.frameElement); + } + else { + // Close this current window + try { + window.close(); + } + catch (e) {} + + // IOS bug wont let us close a popup if still loading + if (window.addEventListener) { + window.addEventListener('load', function() { + window.close(); + }); + } + } + + } + } +}); + +// Events +// Extend the hello object with its own event instance +hello.utils.Event.call(hello); + +/////////////////////////////////// +// Monitoring session state +// Check for session changes +/////////////////////////////////// + +(function(hello) { + + // Monitor for a change in state and fire + var oldSessions = {}; + + // Hash of expired tokens + var expired = {}; + + // Listen to other triggers to Auth events, use these to update this + hello.on('auth.login, auth.logout', function(auth) { + if (auth && typeof (auth) === 'object' && auth.network) { + oldSessions[auth.network] = hello.utils.store(auth.network) || {}; + } + }); + + (function self() { + + var CURRENT_TIME = ((new Date()).getTime() / 1e3); + var emit = function(eventName) { + hello.emit('auth.' + eventName, { + network: name, + authResponse: session + }); + }; + + // Loop through the services + for (var name in hello.services) {if (hello.services.hasOwnProperty(name)) { + + if (!hello.services[name].id) { + // We haven't attached an ID so dont listen. + continue; + } + + // Get session + var session = hello.utils.store(name) || {}; + var provider = hello.services[name]; + var oldSess = oldSessions[name] || {}; + + // Listen for globalEvents that did not get triggered from the child + if (session && 'callback' in session) { + + // To do remove from session object... + var cb = session.callback; + try { + delete session.callback; + } + catch (e) {} + + // Update store + // Removing the callback + hello.utils.store(name, session); + + // Emit global events + try { + window[cb](session); + } + catch (e) {} + } + + // Refresh token + if (session && ('expires' in session) && session.expires < CURRENT_TIME) { + + // If auto refresh is possible + // Either the browser supports + var refresh = provider.refresh || session.refresh_token; + + // Has the refresh been run recently? + if (refresh && (!(name in expired) || expired[name] < CURRENT_TIME)) { + // Try to resignin + hello.emit('notice', name + ' has expired trying to resignin'); + hello.login(name, {display: 'none', force: false}); + + // Update expired, every 10 minutes + expired[name] = CURRENT_TIME + 600; + } + + // Does this provider not support refresh + else if (!refresh && !(name in expired)) { + // Label the event + emit('expired'); + expired[name] = true; + } + + // If session has expired then we dont want to store its value until it can be established that its been updated + continue; + } + + // Has session changed? + else if (oldSess.access_token === session.access_token && + oldSess.expires === session.expires) { + continue; + } + + // Access_token has been removed + else if (!session.access_token && oldSess.access_token) { + emit('logout'); + } + + // Access_token has been created + else if (session.access_token && !oldSess.access_token) { + emit('login'); + } + + // Access_token has been updated + else if (session.expires !== oldSess.expires) { + emit('update'); + } + + // Updated stored session + oldSessions[name] = session; + + // Remove the expired flags + if (name in expired) { + delete expired[name]; + } + }} + + // Check error events + setTimeout(self, 1000); + })(); + +})(hello); + +// EOF CORE lib +////////////////////////////////// + +///////////////////////////////////////// +// API +// @param path string +// @param query object (optional) +// @param method string (optional) +// @param data object (optional) +// @param timeout integer (optional) +// @param callback function (optional) + +hello.api = function() { + + // Shorthand + var _this = this; + var utils = _this.utils; + var error = utils.error; + + // Construct a new Promise object + var promise = utils.Promise(); + + // Arguments + var p = utils.args({path: 's!', query: 'o', method: 's', data: 'o', timeout: 'i', callback: 'f'}, arguments); + + // Method + p.method = (p.method || 'get').toLowerCase(); + + // Headers + p.headers = p.headers || {}; + + // Query + p.query = p.query || {}; + + // If get, put all parameters into query + if (p.method === 'get' || p.method === 'delete') { + utils.extend(p.query, p.data); + p.data = {}; + } + + var data = p.data = p.data || {}; + + // Completed event callback + promise.then(p.callback, p.callback); + + // Remove the network from path, e.g. facebook:/me/friends + // Results in { network : facebook, path : me/friends } + if (!p.path) { + return promise.reject(error('invalid_path', 'Missing the path parameter from the request')); + } + + p.path = p.path.replace(/^\/+/, ''); + var a = (p.path.split(/[\/\:]/, 2) || [])[0].toLowerCase(); + + if (a in _this.services) { + p.network = a; + var reg = new RegExp('^' + a + ':?\/?'); + p.path = p.path.replace(reg, ''); + } + + // Network & Provider + // Define the network that this request is made for + p.network = _this.settings.default_service = p.network || _this.settings.default_service; + var o = _this.services[p.network]; + + // INVALID + // Is there no service by the given network name? + if (!o) { + return promise.reject(error('invalid_network', 'Could not match the service requested: ' + p.network)); + } + + // PATH + // As long as the path isn't flagged as unavaiable, e.g. path == false + + if (!(!(p.method in o) || !(p.path in o[p.method]) || o[p.method][p.path] !== false)) { + return promise.reject(error('invalid_path', 'The provided path is not available on the selected network')); + } + + // PROXY + // OAuth1 calls always need a proxy + + if (!p.oauth_proxy) { + p.oauth_proxy = _this.settings.oauth_proxy; + } + + if (!('proxy' in p)) { + p.proxy = p.oauth_proxy && o.oauth && parseInt(o.oauth.version, 10) === 1; + } + + // TIMEOUT + // Adopt timeout from global settings by default + + if (!('timeout' in p)) { + p.timeout = _this.settings.timeout; + } + + // Format response + // Whether to run the raw response through post processing. + if (!('formatResponse' in p)) { + p.formatResponse = true; + } + + // Get the current session + // Append the access_token to the query + p.authResponse = _this.getAuthResponse(p.network); + if (p.authResponse && p.authResponse.access_token) { + p.query.access_token = p.authResponse.access_token; + } + + var url = p.path; + var m; + + // Store the query as options + // This is used to populate the request object before the data is augmented by the prewrap handlers. + p.options = utils.clone(p.query); + + // Clone the data object + // Prevent this script overwriting the data of the incoming object. + // Ensure that everytime we run an iteration the callbacks haven't removed some data + p.data = utils.clone(data); + + // URL Mapping + // Is there a map for the given URL? + var actions = o[{'delete': 'del'}[p.method] || p.method] || {}; + + // Extrapolate the QueryString + // Provide a clean path + // Move the querystring into the data + if (p.method === 'get') { + + var query = url.split(/[\?#]/)[1]; + if (query) { + utils.extend(p.query, utils.param(query)); + + // Remove the query part from the URL + url = url.replace(/\?.*?(#|$)/, '$1'); + } + } + + // Is the hash fragment defined + if ((m = url.match(/#(.+)/, ''))) { + url = url.split('#')[0]; + p.path = m[1]; + } + else if (url in actions) { + p.path = url; + url = actions[url]; + } + else if ('default' in actions) { + url = actions['default']; + } + + // Redirect Handler + // This defines for the Form+Iframe+Hash hack where to return the results too. + p.redirect_uri = _this.settings.redirect_uri; + + // Define FormatHandler + // The request can be procesed in a multitude of ways + // Here's the options - depending on the browser and endpoint + p.xhr = o.xhr; + p.jsonp = o.jsonp; + p.form = o.form; + + // Make request + if (typeof (url) === 'function') { + // Does self have its own callback? + url(p, getPath); + } + else { + // Else the URL is a string + getPath(url); + } + + return promise.proxy; + + // If url needs a base + // Wrap everything in + function getPath(url) { + + // Format the string if it needs it + url = url.replace(/\@\{([a-z\_\-]+)(\|.*?)?\}/gi, function(m, key, defaults) { + var val = defaults ? defaults.replace(/^\|/, '') : ''; + if (key in p.query) { + val = p.query[key]; + delete p.query[key]; + } + else if (p.data && key in p.data) { + val = p.data[key]; + delete p.data[key]; + } + else if (!defaults) { + promise.reject(error('missing_attribute', 'The attribute ' + key + ' is missing from the request')); + } + + return val; + }); + + // Add base + if (!url.match(/^https?:\/\//)) { + url = o.base + url; + } + + // Define the request URL + p.url = url; + + // Make the HTTP request with the curated request object + // CALLBACK HANDLER + // @ response object + // @ statusCode integer if available + utils.request(p, function(r, headers) { + + // Is this a raw response? + if (!p.formatResponse) { + // Bad request? error statusCode or otherwise contains an error response vis JSONP? + if (typeof headers === 'object' ? (headers.statusCode >= 400) : (typeof r === 'object' && 'error' in r)) { + promise.reject(r); + } + else { + promise.fulfill(r); + } + + return; + } + + // Should this be an object + if (r === true) { + r = {success:true}; + } + else if (!r) { + r = {}; + } + + // The delete callback needs a better response + if (p.method === 'delete') { + r = (!r || utils.isEmpty(r)) ? {success:true} : r; + } + + // FORMAT RESPONSE? + // Does self request have a corresponding formatter + if (o.wrap && ((p.path in o.wrap) || ('default' in o.wrap))) { + var wrap = (p.path in o.wrap ? p.path : 'default'); + var time = (new Date()).getTime(); + + // FORMAT RESPONSE + var b = o.wrap[wrap](r, headers, p); + + // Has the response been utterly overwritten? + // Typically self augments the existing object.. but for those rare occassions + if (b) { + r = b; + } + } + + // Is there a next_page defined in the response? + if (r && 'paging' in r && r.paging.next) { + + // Add the relative path if it is missing from the paging/next path + if (r.paging.next[0] === '?') { + r.paging.next = p.path + r.paging.next; + } + + // The relative path has been defined, lets markup the handler in the HashFragment + else { + r.paging.next += '#' + p.path; + } + } + + // Dispatch to listeners + // Emit events which pertain to the formatted response + if (!r || 'error' in r) { + promise.reject(r); + } + else { + promise.fulfill(r); + } + }); + } +}; + +// API utilities +hello.utils.extend(hello.utils, { + + // Make an HTTP request + request: function(p, callback) { + + var _this = this; + var error = _this.error; + + // This has to go through a POST request + if (!_this.isEmpty(p.data) && !('FileList' in window) && _this.hasBinary(p.data)) { + + // Disable XHR and JSONP + p.xhr = false; + p.jsonp = false; + } + + // Check if the browser and service support CORS + var cors = this.request_cors(function() { + // If it does then run this... + return ((p.xhr === undefined) || (p.xhr && (typeof (p.xhr) !== 'function' || p.xhr(p, p.query)))); + }); + + if (cors) { + + formatUrl(p, function(url) { + + var x = _this.xhr(p.method, url, p.headers, p.data, callback); + x.onprogress = p.onprogress || null; + + // Windows Phone does not support xhr.upload, see #74 + // Feature detect + if (x.upload && p.onuploadprogress) { + x.upload.onprogress = p.onuploadprogress; + } + + }); + + return; + } + + // Clone the query object + // Each request modifies the query object and needs to be tared after each one. + var _query = p.query; + + p.query = _this.clone(p.query); + + // Assign a new callbackID + p.callbackID = _this.globalEvent(); + + // JSONP + if (p.jsonp !== false) { + + // Clone the query object + p.query.callback = p.callbackID; + + // If the JSONP is a function then run it + if (typeof (p.jsonp) === 'function') { + p.jsonp(p, p.query); + } + + // Lets use JSONP if the method is 'get' + if (p.method === 'get') { + + formatUrl(p, function(url) { + _this.jsonp(url, callback, p.callbackID, p.timeout); + }); + + return; + } + else { + // It's not compatible reset query + p.query = _query; + } + + } + + // Otherwise we're on to the old school, iframe hacks and JSONP + if (p.form !== false) { + + // Add some additional query parameters to the URL + // We're pretty stuffed if the endpoint doesn't like these + p.query.redirect_uri = p.redirect_uri; + p.query.state = JSON.stringify({callback:p.callbackID}); + + var opts; + + if (typeof (p.form) === 'function') { + + // Format the request + opts = p.form(p, p.query); + } + + if (p.method === 'post' && opts !== false) { + + formatUrl(p, function(url) { + _this.post(url, p.data, opts, callback, p.callbackID, p.timeout); + }); + + return; + } + } + + // None of the methods were successful throw an error + callback(error('invalid_request', 'There was no mechanism for handling this request')); + + return; + + // Format URL + // Constructs the request URL, optionally wraps the URL through a call to a proxy server + // Returns the formatted URL + function formatUrl(p, callback) { + + // Are we signing the request? + var sign; + + // OAuth1 + // Remove the token from the query before signing + if (p.authResponse && p.authResponse.oauth && parseInt(p.authResponse.oauth.version, 10) === 1) { + + // OAUTH SIGNING PROXY + sign = p.query.access_token; + + // Remove the access_token + delete p.query.access_token; + + // Enfore use of Proxy + p.proxy = true; + } + + // POST body to querystring + if (p.data && (p.method === 'get' || p.method === 'delete')) { + // Attach the p.data to the querystring. + _this.extend(p.query, p.data); + p.data = null; + } + + // Construct the path + var path = _this.qs(p.url, p.query); + + // Proxy the request through a server + // Used for signing OAuth1 + // And circumventing services without Access-Control Headers + if (p.proxy) { + // Use the proxy as a path + path = _this.qs(p.oauth_proxy, { + path: path, + access_token: sign || '', + + // This will prompt the request to be signed as though it is OAuth1 + then: p.proxy_response_type || (p.method.toLowerCase() === 'get' ? 'redirect' : 'proxy'), + method: p.method.toLowerCase(), + suppress_response_codes: true + }); + } + + callback(path); + } + }, + + // Test whether the browser supports the CORS response + request_cors: function(callback) { + return 'withCredentials' in new XMLHttpRequest() && callback(); + }, + + // Return the type of DOM object + domInstance: function(type, data) { + var test = 'HTML' + (type || '').replace( + /^[a-z]/, + function(m) { + return m.toUpperCase(); + } + + ) + 'Element'; + + if (!data) { + return false; + } + + if (window[test]) { + return data instanceof window[test]; + } + else if (window.Element) { + return data instanceof window.Element && (!type || (data.tagName && data.tagName.toLowerCase() === type)); + } + else { + return (!(data instanceof Object || data instanceof Array || data instanceof String || data instanceof Number) && data.tagName && data.tagName.toLowerCase() === type); + } + }, + + // Create a clone of an object + clone: function(obj) { + // Does not clone DOM elements, nor Binary data, e.g. Blobs, Filelists + if (obj === null || typeof (obj) !== 'object' || obj instanceof Date || 'nodeName' in obj || this.isBinary(obj) || (typeof FormData === 'function' && obj instanceof FormData)) { + return obj; + } + + if (Array.isArray(obj)) { + // Clone each item in the array + return obj.map(this.clone.bind(this)); + } + + // But does clone everything else. + var clone = {}; + for (var x in obj) { + clone[x] = this.clone(obj[x]); + } + + return clone; + }, + + // XHR: uses CORS to make requests + xhr: function(method, url, headers, data, callback) { + + var r = new XMLHttpRequest(); + var error = this.error; + + // Binary? + var binary = false; + if (method === 'blob') { + binary = method; + method = 'GET'; + } + + method = method.toUpperCase(); + + // Xhr.responseType 'json' is not supported in any of the vendors yet. + r.onload = function(e) { + var json = r.response; + try { + json = JSON.parse(r.responseText); + } + catch (_e) { + if (r.status === 401) { + json = error('access_denied', r.statusText); + } + } + + var headers = headersToJSON(r.getAllResponseHeaders()); + headers.statusCode = r.status; + + callback(json || (method === 'GET' ? error('empty_response', 'Could not get resource') : {}), headers); + }; + + r.onerror = function(e) { + var json = r.responseText; + try { + json = JSON.parse(r.responseText); + } + catch (_e) {} + + callback(json || error('access_denied', 'Could not get resource')); + }; + + var x; + + // Should we add the query to the URL? + if (method === 'GET' || method === 'DELETE') { + data = null; + } + else if (data && typeof (data) !== 'string' && !(data instanceof FormData) && !(data instanceof File) && !(data instanceof Blob)) { + // Loop through and add formData + var f = new FormData(); + for (x in data) if (data.hasOwnProperty(x)) { + if (data[x] instanceof HTMLInputElement) { + if ('files' in data[x] && data[x].files.length > 0) { + f.append(x, data[x].files[0]); + } + } + else if (data[x] instanceof Blob) { + f.append(x, data[x], data.name); + } + else { + f.append(x, data[x]); + } + } + + data = f; + } + + // Open the path, async + r.open(method, url, true); + + if (binary) { + if ('responseType' in r) { + r.responseType = binary; + } + else { + r.overrideMimeType('text/plain; charset=x-user-defined'); + } + } + + // Set any bespoke headers + if (headers) { + for (x in headers) { + r.setRequestHeader(x, headers[x]); + } + } + + r.send(data); + + return r; + + // Headers are returned as a string + function headersToJSON(s) { + var r = {}; + var reg = /([a-z\-]+):\s?(.*);?/gi; + var m; + while ((m = reg.exec(s))) { + r[m[1]] = m[2]; + } + + return r; + } + }, + + // JSONP + // Injects a script tag into the DOM to be executed and appends a callback function to the window object + // @param string/function pathFunc either a string of the URL or a callback function pathFunc(querystringhash, continueFunc); + // @param function callback a function to call on completion; + jsonp: function(url, callback, callbackID, timeout) { + + var _this = this; + var error = _this.error; + + // Change the name of the callback + var bool = 0; + var head = document.getElementsByTagName('head')[0]; + var operaFix; + var result = error('server_error', 'server_error'); + var cb = function() { + if (!(bool++)) { + window.setTimeout(function() { + callback(result); + head.removeChild(script); + }, 0); + } + + }; + + // Add callback to the window object + callbackID = _this.globalEvent(function(json) { + result = json; + return true; + + // Mark callback as done + }, callbackID); + + // The URL is a function for some cases and as such + // Determine its value with a callback containing the new parameters of this function. + url = url.replace(new RegExp('=\\?(&|$)'), '=' + callbackID + '$1'); + + // Build script tag + var script = _this.append('script', { + id: callbackID, + name: callbackID, + src: url, + async: true, + onload: cb, + onerror: cb, + onreadystatechange: function() { + if (/loaded|complete/i.test(this.readyState)) { + cb(); + } + } + }); + + // Opera fix error + // Problem: If an error occurs with script loading Opera fails to trigger the script.onerror handler we specified + // + // Fix: + // By setting the request to synchronous we can trigger the error handler when all else fails. + // This action will be ignored if we've already called the callback handler "cb" with a successful onload event + if (window.navigator.userAgent.toLowerCase().indexOf('opera') > -1) { + operaFix = _this.append('script', { + text: 'document.getElementById(\'' + callbackID + '\').onerror();' + }); + script.async = false; + } + + // Add timeout + if (timeout) { + window.setTimeout(function() { + result = error('timeout', 'timeout'); + cb(); + }, timeout); + } + + // TODO: add fix for IE, + // However: unable recreate the bug of firing off the onreadystatechange before the script content has been executed and the value of "result" has been defined. + // Inject script tag into the head element + head.appendChild(script); + + // Append Opera Fix to run after our script + if (operaFix) { + head.appendChild(operaFix); + } + }, + + // Post + // Send information to a remote location using the post mechanism + // @param string uri path + // @param object data, key value data to send + // @param function callback, function to execute in response + post: function(url, data, options, callback, callbackID, timeout) { + + var _this = this; + var error = _this.error; + var doc = document; + + // This hack needs a form + var form = null; + var reenableAfterSubmit = []; + var newform; + var i = 0; + var x = null; + var bool = 0; + var cb = function(r) { + if (!(bool++)) { + callback(r); + } + }; + + // What is the name of the callback to contain + // We'll also use this to name the iframe + _this.globalEvent(cb, callbackID); + + // Build the iframe window + var win; + try { + // IE7 hack, only lets us define the name here, not later. + win = doc.createElement('