<commit_before>;
(function () {
    "use strict";
    if (window.yiiResourceSmartLoadPrivateObj) {
        return; // Prevent reload of current js file (on AJAX request)
    }

    // Private global JS path (only for internal use):
    window.yiiResourceSmartLoadPrivateObj = (function ($) {
        var resourceSmartLoad = {

            /**
             * Is already initialized (for prevent repeated initializing)
             */
            isInitialized: false,

            /**
             * Contain common function, helpers
             */
            fn: {},

            /**
             * Options of extension (will be exported from server side).
             * Null values will be replaced after initialization (on client).
             *
             * Contain following options:
             *

             *
{String} {@link extensionOptions.hashMethod}

             *
{Boolean} {@link extensionOptions.enableLog}

             *
{Boolean} {@link extensionOptions.activateOnAllPages}

             *
{String[]} {@link extensionOptions.alwaysReloadableResources}

             *

             *
             * @type {Object}
             */
            extensionOptions: {
                /**
                 * Current hashing method (for resource names)
                 * @type {String}
                 */
                hashMethod: null,

                /**
                 * Types of resources, that will be tracked by current extension
                 * @type {String[]}
                 */
                resourceTypes: null,

                /**
                 * Enable log of registered/disabled resources
                 * @type {Boolean}
                 */
                enableLog: null,

                /**
                 * Activates "smart" disabling of resources on all pages
                 * @type {Boolean}
                 */
                activateOnAllPages: null,

                /**
                 * List of resources, that always should be loaded on client.
                 * @type {String[]}
                 */
                alwaysReloadableResources: null,

                /**
                 * Public path, that can be used for accessing to current global object.
                 * @type {String}
                 */
                jsGlobalObjPublicPath: null,

                /**
                 * Name of "client" variable, see detail {@link resourceSmartLoad.setClientVar}
                 * @type {String}
                 */
                clientVarName: null
            },

            /**
             * Resources, that already loaded on client
             *
             * @type {Object}
             * @see getResourceByHash
             * @see getResourcesHashList
             * @see addResource
             */
            resources: {},

            /**
             * Returns loaded resource with given hash
             *
             * @param {String} hash    Hash of resource
             * @returns {String|null}  Resource identifier (for js/css files: name/url of file). Returns null if not found
             * @see resources
             */
            getResourceByHash: function (hash) {
                var app = this;

                return app.resources[hash] || null;
            },

            /**
             * Returns list of hashes of all loaded resources
             *
             * @return {String[]} List of hashes
             * @see resources
             */
            getResourcesHashList: function () {
                var app = this,
                    result = [];

                $.each(app.resources, function (hash, resource) {
                    result.push(resource.hash);
                });
                return result;
            },

            /**
             * Adds given resource to list of loaded resources {@link resources}
             *
             * @param {String} hash         Hash of resource
             * @param {String} [resource]   Resource identifier: basename/url of js/css files or part of content
             *                              of inline js/css (supplemental information)
             * @param {String} [comment]    Description for internal using (on debug mode)
             *                              (supplemental information)
             * @see resources
             */
            addResource: function (hash, resource, comment) {
                var app = this,
                    isAlreadyLoaded = Boolean(app.getResourceByHash(hash));

                if (!isAlreadyLoaded) {
                    app.resources[hash] = {
                        resource: resource,
                        hash    : hash,
                        source  : comment
                    };
                }

                app.log('addResource = ', resource, !isAlreadyLoaded ? ' ...added' : ' ...skipped');
            },

            /**
             * Checks global $.ajaxSend handlers. If not found handler of current extension, binds it.
             * This is prevents duplicate binding.
             */
            bindAjaxSendHandlerIfNotDefined: function () {
                var jQ = jQuery, // assign actual global instance (for the case, when jQuery reloaded at AJAX request)
                    app = this,
                    clientVarName = app.extensionOptions.clientVarName,
                    isHandled = false,
                    ajaxSendEvents = jQ._data(document) && jQ._data(document).events
                        ? jQ._data(document).events.ajaxSend
                        : undefined;

                // hook for all ajax-requests, in "client" variable we send hashes of all loaded resources
                function resourceSmartLoadAjaxSendHandler(event, jqXHR, settings) {
                    var varsObj = {};
                    varsObj[clientVarName] = JSON.stringify(app.getResourcesHashList());
                    app.setClientVar(jqXHR, varsObj, settings.url);
                }

                if (ajaxSendEvents) {
                    ajaxSendEvents.map(function (event) {
                        if (event.handler.name === resourceSmartLoadAjaxSendHandler.name) {
                            isHandled = true;
                        }
                        return event.handler;
                    });
                }

                if (!isHandled) {
                    jQ(document).ajaxSend(resourceSmartLoadAjaxSendHandler);
                }
                app.log('Check of global $.ajaxSend handler: ', isHandled
                    ? 'already bind... skipped'
                    : 'not found... bind extension handler');
            },

            /**
             * Initializes extension
             *
             * @param {Object} extensionOptionsInit     Options, that exported from server side,
             *                                          see {@link resourceSmartLoad.extensionOptions}
             * @see resourceSmartLoad.extensionOptions
             */
            initExtension: function (extensionOptionsInit) {
                var app = this;

                // prevent duplicate initialization of extension
                if (!app.isInitialized) {
                    app.extensionOptions = extensionOptionsInit;
                    app.fn.assignPropertyInNamespace(app.extensionOptions.jsGlobalObjPublicPath, this);
                    app.isInitialized = true;
                }

                $(function () {
                    // This code after each AJAX request checks global $.ajaxSend handlers. If on AJAX request
                    // for any reason will be reloaded "jquery.js" (for example, at "activateOnAllPages" = false), all
                    // global handlers of jQuery events will be removed. In this case method restores handler of $.ajaxSend.
                    app.bindAjaxSendHandlerIfNotDefined();
                });
            },

            /**
             * Sets "client" variable for given AJAX request (inserts variable into HTTP headers and cookie).
             * Cookie will be deleted after complete of given request.
             * On server this variable can be accessed through Helper::getClientVar(name).
             *
             * @param {jqXHR}           jqXHR       jQuery.ajax-object of request (returns by function $.ajax,
             *                                      or passed as argument in handlers $.ajax.beforeSend, $.ajaxSend etc.).
             * @param {Object}          vars        Object with variables in format {name:value}.
             *                                      "value" cannot contain non english symbols.
             * @param {String}          [url='/']   URL, which will be sent to ajax-request
             *                                      (for this URL will be assigned a cookie).
             * @see http://api.jquery.com/jQuery.ajax/#jqXHR
             * @see http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
             */
            setClientVar: function (jqXHR, vars, url) {
                var app = this,
                    prefix = 'clientvar';  // Should not contain extraneous characters, otherwise the cookie names/titles can be invalid
                url = url || '/';

                $.each(vars, function (key, value) {
                    jqXHR.setRequestHeader(prefix + key, value);
                    app.fn.setCookie(prefix + key, value, {path: url});
                });
                jqXHR.always(function () {
                    $.each(vars, function (key, value) {
                        app.fn.deleteCookie(prefix + key, url);
                    });
                });
            },

            /**
             * Adds to log given strings/variables
             *
             * @param {...*} *   Variable number of parameters
             */
            log: function () {
                var args,
                    app = this;

                if (app.extensionOptions.enableLog) {
                    args = ['ResourceSmartLoad >>  '];
                    $.each(arguments, function (key, val) { // copy values from "pseudo array" to normal array
                        args.push(val);
                    });
                    console.log.apply(this, args);
                }
            }
        };

        resourceSmartLoad.fn = {
            /**
             * Returns cookie by name.
             *
             * @param {String}  name    Cookie name
             * @returns {String}        Cookie value. If not found, returns undefined
             */
            getCookie: function (name) {
                var safeName = name.replace(/([\.$?*|{}\(\)\[\]\\\/\+\^])/g, '\\$1'),
                    matches = document.cookie.match(new RegExp("(?:^|; )" + safeName + "=([^;]*)"));

                return matches ? decodeURIComponent(matches[1]) : undefined;
            },

            /**
             * Sets cookie.
             *
             * @param {String} name         Cookie name
             * @param {String} value        Cookie value
             * @param {Object} [options={}] Additional options:
             *

             *

             *         expires - Expiry time of cookie. Is interpreted differently, depending on the type of:
             *

             *
{Number} — number of seconds. For example, expires: 3600 - Cookie for an hour.

             *
{Date} — expiration date.

             *
If expires in the past, the cookie will be deleted.

             *
If expires is undefined or 0, the cookie will be set as a session and will disappear
             *             when the browser is closed.

             *

             *

             *
path - Cookie path.

             *
domain - Cookie domain.

             *
secure - If =true, cookie will be sent over a secure connection.

             *

             */
            setCookie: function (name, value, options) {
                options = options || {};

                var d, updatedCookie, propName, propValue,
                    expires = options.expires;

                if (typeof expires === "number" && expires) {
                    d = new Date();
                    d.setTime(d.getTime() + expires * 1000);
                    expires = options.expires = d;
                }
                if (expires && expires.toUTCString) {
                    options.expires = expires.toUTCString();
                }

                value = encodeURIComponent(value);

                updatedCookie = name + "=" + value;

                for (propName in options) {
                    if (options.hasOwnProperty(propName)) {
                        updatedCookie += "; " + propName;
                        propValue = options[propName];
                        if (propValue !== true) {
                            updatedCookie += "=" + propValue;
                        }
                    }
                }

                document.cookie = updatedCookie;
            },

            /**
             * Deletes cookie by name (for given path).
             * If the cookie is set on a certain path, one name is not enough to read/delete.
             *
             * @param {String} name         Cookie name
             * @param {String} [url]        URL for cookie
             */
            deleteCookie: function (name, url) {
                var fn = this,
                    value = {expires: -1};  // "Cookie expired"

                if (url) {
                    $.extend(value, {path: url});
                }
                fn.setCookie(name, "", value);
            },

            /**
             * Assigns given value to given property (into namespace).
             * If given namespaces does not exist, they will be created (only if createNamespaces === true).
             *
             * @param {String}  path                        Property path with namespaces
             * @param {*}       value                       Any value
             * @param {Boolean} [createNamespaces=false]    If =true, nonexistent namespaces will be created,
             *                                              otherwise - throws exception
             * @example:
             *
             *      'yii.moduleName.property'           // property path with namespaces
             *      'window.yii.moduleName.property'    // equivalent path
             *
             */
            assignPropertyInNamespace: function (path, value, createNamespaces) {
                createNamespaces = (createNamespaces === undefined) ? false : createNamespaces;

                var fn = this,
                    namespaces = path.split('.'),
                    targetProp = namespaces.pop(),
                    targetObj = window;

                if (namespaces[0] === 'window') {
                    namespaces.shift();
                }

                namespaces.map(function (namespace) {
                    var exceptPrefix = '[resourceSmartLoad.fn.assignPropertyInNamespace] >> ';
                    if (targetObj[namespace] === undefined) {
                        if (!createNamespaces) {
                            throw exceptPrefix + 'Undefined namespace "' + namespace + '". See param "createNamespaces"';
                        }
                        targetObj[namespace] = {};
                    } else if (!fn.isObject(targetObj[namespace])) {
                        throw exceptPrefix + 'Property "' + namespace + '" is not an object, therefore, cannot be used as namespace.';
                    }
                    targetObj = targetObj[namespace];
                });

                targetObj[targetProp] = value;
            },

            /**
             * Checks whether the given variable is an object.
             *
             * @param {*} mixedVar
             * @return {Boolean}    Returns =true, if given variable is an object
             */
            isObject: function (mixedVar) {
                if (Object.prototype.toString.call(mixedVar) === '[object Array]') {
                    return false;
                }
                return mixedVar !== null && typeof mixedVar === 'object';
            }
        };

        return resourceSmartLoad;
    }(jQuery));
}());<commit_msg>fix: fixed .log() for Chrome<commit_after>;
(function () {
    "use strict";
    if (window.yiiResourceSmartLoadPrivateObj) {
        return; // Prevent reload of current js file (on AJAX request)
    }

    // Private global JS path (only for internal use):
    window.yiiResourceSmartLoadPrivateObj = (function ($) {
        var resourceSmartLoad = {

            /**
             * Is already initialized (for prevent repeated initializing)
             */
            isInitialized: false,

            /**
             * Contain common function, helpers
             */
            fn: {},

            /**
             * Options of extension (will be exported from server side).
             * Null values will be replaced after initialization (on client).
             *
             * Contain following options:
             *

             *
{String} {@link extensionOptions.hashMethod}

             *
{Boolean} {@link extensionOptions.enableLog}

             *
{Boolean} {@link extensionOptions.activateOnAllPages}

             *
{String[]} {@link extensionOptions.alwaysReloadableResources}

             *

             *
             * @type {Object}
             */
            extensionOptions: {
                /**
                 * Current hashing method (for resource names)
                 * @type {String}
                 */
                hashMethod: null,

                /**
                 * Types of resources, that will be tracked by current extension
                 * @type {String[]}
                 */
                resourceTypes: null,

                /**
                 * Enable log of registered/disabled resources
                 * @type {Boolean}
                 */
                enableLog: null,

                /**
                 * Activates "smart" disabling of resources on all pages
                 * @type {Boolean}
                 */
                activateOnAllPages: null,

                /**
                 * List of resources, that always should be loaded on client.
                 * @type {String[]}
                 */
                alwaysReloadableResources: null,

                /**
                 * Public path, that can be used for accessing to current global object.
                 * @type {String}
                 */
                jsGlobalObjPublicPath: null,

                /**
                 * Name of "client" variable, see detail {@link resourceSmartLoad.setClientVar}
                 * @type {String}
                 */
                clientVarName: null
            },

            /**
             * Resources, that already loaded on client
             *
             * @type {Object}
             * @see getResourceByHash
             * @see getResourcesHashList
             * @see addResource
             */
            resources: {},

            /**
             * Returns loaded resource with given hash
             *
             * @param {String} hash    Hash of resource
             * @returns {String|null}  Resource identifier (for js/css files: name/url of file). Returns null if not found
             * @see resources
             */
            getResourceByHash: function (hash) {
                var app = this;

                return app.resources[hash] || null;
            },

            /**
             * Returns list of hashes of all loaded resources
             *
             * @return {String[]} List of hashes
             * @see resources
             */
            getResourcesHashList: function () {
                var app = this,
                    result = [];

                $.each(app.resources, function (hash, resource) {
                    result.push(resource.hash);
                });
                return result;
            },

            /**
             * Adds given resource to list of loaded resources {@link resources}
             *
             * @param {String} hash         Hash of resource
             * @param {String} [resource]   Resource identifier: basename/url of js/css files or part of content
             *                              of inline js/css (supplemental information)
             * @param {String} [comment]    Description for internal using (on debug mode)
             *                              (supplemental information)
             * @see resources
             */
            addResource: function (hash, resource, comment) {
                var app = this,
                    isAlreadyLoaded = Boolean(app.getResourceByHash(hash));

                if (!isAlreadyLoaded) {
                    app.resources[hash] = {
                        resource: resource,
                        hash    : hash,
                        source  : comment
                    };
                }

                app.log('addResource = ', resource, !isAlreadyLoaded ? ' ...added' : ' ...skipped');
            },

            /**
             * Checks global $.ajaxSend handlers. If not found handler of current extension, binds it.
             * This is prevents duplicate binding.
             */
            bindAjaxSendHandlerIfNotDefined: function () {
                var jQ = jQuery, // assign actual global instance (for the case, when jQuery reloaded at AJAX request)
                    app = this,
                    clientVarName = app.extensionOptions.clientVarName,
                    isHandled = false,
                    ajaxSendEvents = jQ._data(document) && jQ._data(document).events
                        ? jQ._data(document).events.ajaxSend
                        : undefined;

                // hook for all ajax-requests, in "client" variable we send hashes of all loaded resources
                function resourceSmartLoadAjaxSendHandler(event, jqXHR, settings) {
                    var varsObj = {};
                    varsObj[clientVarName] = JSON.stringify(app.getResourcesHashList());
                    app.setClientVar(jqXHR, varsObj, settings.url);
                }

                if (ajaxSendEvents) {
                    ajaxSendEvents.map(function (event) {
                        if (event.handler.name === resourceSmartLoadAjaxSendHandler.name) {
                            isHandled = true;
                        }
                        return event.handler;
                    });
                }

                if (!isHandled) {
                    jQ(document).ajaxSend(resourceSmartLoadAjaxSendHandler);
                }
                app.log('Check of global $.ajaxSend handler: ', isHandled
                    ? 'already bind... skipped'
                    : 'not found... bind extension handler');
            },

            /**
             * Initializes extension
             *
             * @param {Object} extensionOptionsInit     Options, that exported from server side,
             *                                          see {@link resourceSmartLoad.extensionOptions}
             * @see resourceSmartLoad.extensionOptions
             */
            initExtension: function (extensionOptionsInit) {
                var app = this;

                // prevent duplicate initialization of extension
                if (!app.isInitialized) {
                    app.extensionOptions = extensionOptionsInit;
                    app.fn.assignPropertyInNamespace(app.extensionOptions.jsGlobalObjPublicPath, this);
                    app.isInitialized = true;
                }

                $(function () {
                    // This code after each AJAX request checks global $.ajaxSend handlers. If on AJAX request
                    // for any reason will be reloaded "jquery.js" (for example, at "activateOnAllPages" = false), all
                    // global handlers of jQuery events will be removed. In this case method restores handler of $.ajaxSend.
                    app.bindAjaxSendHandlerIfNotDefined();
                });
            },

            /**
             * Sets "client" variable for given AJAX request (inserts variable into HTTP headers and cookie).
             * Cookie will be deleted after complete of given request.
             * On server this variable can be accessed through Helper::getClientVar(name).
             *
             * @param {jqXHR}           jqXHR       jQuery.ajax-object of request (returns by function $.ajax,
             *                                      or passed as argument in handlers $.ajax.beforeSend, $.ajaxSend etc.).
             * @param {Object}          vars        Object with variables in format {name:value}.
             *                                      "value" cannot contain non english symbols.
             * @param {String}          [url='/']   URL, which will be sent to ajax-request
             *                                      (for this URL will be assigned a cookie).
             * @see http://api.jquery.com/jQuery.ajax/#jqXHR
             * @see http://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
             */
            setClientVar: function (jqXHR, vars, url) {
                var app = this,
                    prefix = 'clientvar';  // Should not contain extraneous characters, otherwise the cookie names/titles can be invalid
                url = url || '/';

                $.each(vars, function (key, value) {
                    jqXHR.setRequestHeader(prefix + key, value);
                    app.fn.setCookie(prefix + key, value, {path: url});
                });
                jqXHR.always(function () {
                    $.each(vars, function (key, value) {
                        app.fn.deleteCookie(prefix + key, url);
                    });
                });
            },

            /**
             * Adds to log given strings/variables
             *
             * @param {...*} *   Variable number of parameters
             */
            log: function () {
                var args,
                    app = this;

                if (app.extensionOptions.enableLog) {
                    args = ['ResourceSmartLoad >>  '];
                    $.each(arguments, function (key, val) { // copy values from "pseudo array" to normal array
                        args.push(val);
                    });
                    console.log.apply(console, args);
                }
            }
        };

        resourceSmartLoad.fn = {
            /**
             * Returns cookie by name.
             *
             * @param {String}  name    Cookie name
             * @returns {String}        Cookie value. If not found, returns undefined
             */
            getCookie: function (name) {
                var safeName = name.replace(/([\.$?*|{}\(\)\[\]\\\/\+\^])/g, '\\$1'),
                    matches = document.cookie.match(new RegExp("(?:^|; )" + safeName + "=([^;]*)"));

                return matches ? decodeURIComponent(matches[1]) : undefined;
            },

            /**
             * Sets cookie.
             *
             * @param {String} name         Cookie name
             * @param {String} value        Cookie value
             * @param {Object} [options={}] Additional options:
             *

             *

             *         expires - Expiry time of cookie. Is interpreted differently, depending on the type of:
             *

             *
{Number} — number of seconds. For example, expires: 3600 - Cookie for an hour.

             *
{Date} — expiration date.

             *
If expires in the past, the cookie will be deleted.

             *
If expires is undefined or 0, the cookie will be set as a session and will disappear
             *             when the browser is closed.

             *

             *

             *
path - Cookie path.

             *
domain - Cookie domain.

             *
secure - If =true, cookie will be sent over a secure connection.

             *

             */
            setCookie: function (name, value, options) {
                options = options || {};

                var d, updatedCookie, propName, propValue,
                    expires = options.expires;

                if (typeof expires === "number" && expires) {
                    d = new Date();
                    d.setTime(d.getTime() + expires * 1000);
                    expires = options.expires = d;
                }
                if (expires && expires.toUTCString) {
                    options.expires = expires.toUTCString();
                }

                value = encodeURIComponent(value);

                updatedCookie = name + "=" + value;

                for (propName in options) {
                    if (options.hasOwnProperty(propName)) {
                        updatedCookie += "; " + propName;
                        propValue = options[propName];
                        if (propValue !== true) {
                            updatedCookie += "=" + propValue;
                        }
                    }
                }

                document.cookie = updatedCookie;
            },

            /**
             * Deletes cookie by name (for given path).
             * If the cookie is set on a certain path, one name is not enough to read/delete.
             *
             * @param {String} name         Cookie name
             * @param {String} [url]        URL for cookie
             */
            deleteCookie: function (name, url) {
                var fn = this,
                    value = {expires: -1};  // "Cookie expired"

                if (url) {
                    $.extend(value, {path: url});
                }
                fn.setCookie(name, "", value);
            },

            /**
             * Assigns given value to given property (into namespace).
             * If given namespaces does not exist, they will be created (only if createNamespaces === true).
             *
             * @param {String}  path                        Property path with namespaces
             * @param {*}       value                       Any value
             * @param {Boolean} [createNamespaces=false]    If =true, nonexistent namespaces will be created,
             *                                              otherwise - throws exception
             * @example:
             *
             *      'yii.moduleName.property'           // property path with namespaces
             *      'window.yii.moduleName.property'    // equivalent path
             *
             */
            assignPropertyInNamespace: function (path, value, createNamespaces) {
                createNamespaces = (createNamespaces === undefined) ? false : createNamespaces;

                var fn = this,
                    namespaces = path.split('.'),
                    targetProp = namespaces.pop(),
                    targetObj = window;

                if (namespaces[0] === 'window') {
                    namespaces.shift();
                }

                namespaces.map(function (namespace) {
                    var exceptPrefix = '[resourceSmartLoad.fn.assignPropertyInNamespace] >> ';
                    if (targetObj[namespace] === undefined) {
                        if (!createNamespaces) {
                            throw exceptPrefix + 'Undefined namespace "' + namespace + '". See param "createNamespaces"';
                        }
                        targetObj[namespace] = {};
                    } else if (!fn.isObject(targetObj[namespace])) {
                        throw exceptPrefix + 'Property "' + namespace + '" is not an object, therefore, cannot be used as namespace.';
                    }
                    targetObj = targetObj[namespace];
                });

                targetObj[targetProp] = value;
            },

            /**
             * Checks whether the given variable is an object.
             *
             * @param {*} mixedVar
             * @return {Boolean}    Returns =true, if given variable is an object
             */
            isObject: function (mixedVar) {
                if (Object.prototype.toString.call(mixedVar) === '[object Array]') {
                    return false;
                }
                return mixedVar !== null && typeof mixedVar === 'object';
            }
        };

        return resourceSmartLoad;
    }(jQuery));
}());<|endoftext|>


<commit_before>/*
Copyright 2016 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
   http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function(scope) {
  // TODO: rootMargin are completely ignored for now

  var POLL_INTERVAL = 100;

  // Constructor
  var IntersectionObserver = function(callback, options) {
    options = options || {};

    if(!(callback instanceof Function)) {
      throw('callback needs to be a function');
    }

    if(options.root && !(options.root instanceof HTMLElement)) {
      throw('Root needs to be an HTMLElement');
    }

    this._callback = callback;
    this._root = options.root || null;
    this._rootMargin = options.rootMargin || [0, 0, 0, 0];
    this._thresholds = options.threshold || 0;
    this._init();
  };

  IntersectionObserver.prototype = {
    //
    // Public API
    //
    get root() {
      return this._root || document;
    },

    get rootMargin() {
      return '0';
    },

    get thresholds() {
      // 0 means call callback on every change
      // See note at http://rawgit.com/WICG/IntersectionObserver/master/index.html#intersection-observer-init
      if(this._thresholds === 0) {
        return 0;
      }
      if(this._thresholds instanceof Array) {
        return this._thresholds;
      }
      return [this._thresholds];
    },

    observe: function(target) {
      if(this._observationTargets.has(target)) {
        return;
      }
      if(!(target instanceof HTMLElement)) {
        throw('Target needs to be an HTMLelement');
      }

      var root = this.root;
      var ancestor = target.parentNode;
      if(!root.contains(target)) {
        throw('Target must be descendant of root');
      }
      this._mutationObserver.observe(target, {
        attributes: true,
        childList: true,
        characterData: true,
        subtree: true
      });
      this._observationTargets.set(target, {});
      this._update();
    },

    unobserve: function(target) {
      this._observationTargets.delete(target);
    },

    disconnect: function() {
      this._observationTargets.clear();
      this.root.removeEventListener('scroll', this._boundUpdate);
      this._mutationObserver.disconnect();
      this._descheduleCallback();
    },

    takeRecords: function() {
      this._update();
      this._descheduleCallback();
      var copy = this._queue.slice();
      this._queue = [];
      return copy;
    },

    //
    // Private API
    //
    _init: function() {
      this._observationTargets = new Map();
      this._boundUpdate = throttle(this._update.bind(this), POLL_INTERVAL);
      this.root.addEventListener('scroll', this._boundUpdate);
      this._mutationObserver = new MutationObserver(this._boundUpdate);
      this._mutationObserver.observe(this.root, {
        attributes: true,
        childList: true,
        characterData: true,
        subtree: true
      });
      this._queue = [];
    },

    _update: function() {
      var rootRect = this._rootRect();
      this._observationTargets.forEach(function(oldIntersectionEntry, target) {
        var targetRect = getBoundingClientRect(target);
        var intersectionRect = intersectRects(rootRect, targetRect);
        if(!intersectionRect) {
          return;
        }
        var targetArea = targetRect.width * targetRect.height;
        var intersectionArea = intersectionRect.width * intersectionRect.height;
        var intersectionRatio = intersectionArea / targetArea;
        if(!this._hasCrossedThreshold(oldIntersectionEntry.intersectionRatio, intersectionRatio)) {
          return;
        }
        var intersectionEntry = {
          time: scope.performance.now(),
          rootBounds: rootRect,
          boundingClientRect: targetRect,
          intersectionRect: intersectionRect,
          intersectionRatio: intersectionRatio,
          target: target
        };
        Object.freeze(intersectionEntry);
        this._queue.push(intersectionEntry);
        this._scheduleCallback();
        this._observationTargets.set(target, intersectionEntry);
      }.bind(this));
    },

    _scheduleCallback: function() {
      if(this._timeoutId) {
        return;
      }
      this._timeoutId = scope.setTimeout(function() {
        this._descheduleCallback();
        this._callback(this._queue, this);
        this._queue = [];
      }.bind(this), POLL_INTERVAL);
    },

    _descheduleCallback: function() {
      if(!this._timeoutId) {
        return;
      }
      scope.clearTimeout(this._timeoutId);
      this._timeoutId = null;
    },

    _rootRect: function() {
      if(this._root) {
        return getBoundingClientRect(this.root);
      }
      return {
        top: 0,
        left: 0,
        right: scope.innerWidth,
        width: scope.innerWidth,
        bottom: scope.innerHeight,
        height: scope.innerHeight
      };
    },

    // FIXME: so hack, much performance, very JSON
    _hasCrossedThreshold: function(oldRatio, newRatio) {
      if(this.thresholds === 0) {
        return newRatio != oldRatio;
      }
      var b1 = this.thresholds.map(function(threshold) {
        return threshold <= oldRatio;
      });
      var b2 = this.thresholds.map(function(threshold) {
        return threshold <= newRatio;
      });
      return JSON.stringify(b1) !== JSON.stringify(b2);
    }
  };

  var intersectRects = function(rect1, rect2) {
    var top = Math.max(rect1.top, rect2.top);
    var bottom = Math.min(rect1.bottom, rect2.bottom);
    var left = Math.max(rect1.left, rect2.left);
    var right = Math.min(rect1.right, rect2.right);
    var intersectionRect = {
      top: top,
      bottom: bottom,
      left: left,
      right: right,
      width: right-left,
      height: bottom-top
    };
    if(top > bottom) {
      intersectionRect.height = 0;
    }
    if(left > right) {
      intersectionRect.width = 0;
    }
    return intersectionRect;
  };

  scope.IntersectionObserver = scope.IntersectionObserver || IntersectionObserver;

  var throttle = function(fn, int) {
    var timer = null;
    return function () {
      if (timer) {
        return;
      }
      timer = setTimeout(function () {
        fn.apply(this, arguments);
        timer = null;
      }.bind(this), int);
    };
  };

  var getBoundingClientRect = function(el) {
    var r = el.getBoundingClientRect();
    r.width = r.width || r.right - r.left;
    r.height = r.height || r.bottom - r.top;
    return r;
  };
})(this);
<commit_msg>Improve intersection detection<commit_after>/*
Copyright 2016 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
   http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
(function(scope) {
  // TODO: rootMargin are completely ignored for now

  var POLL_INTERVAL = 100;

  // Constructor
  var IntersectionObserver = function(callback, options) {
    options = options || {};

    if(!(callback instanceof Function)) {
      throw('callback needs to be a function');
    }

    if(options.root && !(options.root instanceof HTMLElement)) {
      throw('Root needs to be an HTMLElement');
    }

    this._callback = callback;
    this._root = options.root || null;
    this._rootMargin = options.rootMargin || [0, 0, 0, 0];
    this._thresholds = options.threshold || [0];
    this._init();
  };

  IntersectionObserver.prototype = {
    //
    // Public API
    //
    get root() {
      return this._root || document;
    },

    get rootMargin() {
      return '0';
    },

    get thresholds() {
      if(this._thresholds instanceof Array) {
        return this._thresholds;
      }
      return [this._thresholds];
    },

    observe: function(target) {
      if(this._observationTargets.has(target)) {
        return;
      }
      if(!(target instanceof HTMLElement)) {
        throw('Target needs to be an HTMLelement');
      }

      var root = this.root;
      var ancestor = target.parentNode;
      if(!root.contains(target)) {
        throw('Target must be descendant of root');
      }
      this._mutationObserver.observe(target.parentNode, {
        attributes: true,
        childList: true,
        characterData: true,
        subtree: true
      });
      this._observationTargets.set(target, {});
      this._update();
    },

    unobserve: function(target) {
      this._observationTargets.delete(target);
    },

    disconnect: function() {
      this._observationTargets.clear();
      this.root.removeEventListener('scroll', this._boundUpdate);
      this._mutationObserver.disconnect();
      this._descheduleCallback();
    },

    takeRecords: function() {
      this._update();
      this._descheduleCallback();
      var copy = this._queue.slice();
      this._queue = [];
      return copy;
    },

    //
    // Private API
    //
    _init: function() {
      this._observationTargets = new Map();
      this._boundUpdate = throttle(this._update.bind(this), POLL_INTERVAL);
      this.root.addEventListener('scroll', this._boundUpdate);
      this._mutationObserver = new MutationObserver(this._boundUpdate);
      this._mutationObserver.observe(this.root, {
        attributes: true,
        childList: true,
        characterData: true,
        subtree: true
      });
      this._queue = [];
    },

    _update: function() {
      var rootRect = this._rootRect();
      this._observationTargets.forEach(function(oldIntersectionEntry, target) {
        var targetRect = getBoundingClientRect(target);
        var intersectionRect = intersectRects(rootRect, targetRect);
        if(!intersectionRect) {
          return;
        }
        var targetArea = targetRect.width * targetRect.height;
        var intersectionArea = intersectionRect.width * intersectionRect.height;
        var intersectionRatio = intersectionArea / targetArea;
        console.log(intersectionRatio);
        if(!this._hasCrossedThreshold(oldIntersectionEntry.intersectionRatio || 0, intersectionRatio)) {
          return;
        }
        var intersectionEntry = {
          time: scope.performance.now(),
          rootBounds: rootRect,
          boundingClientRect: targetRect,
          intersectionRect: intersectionRect,
          intersectionRatio: intersectionRatio,
          target: target
        };
        Object.freeze(intersectionEntry);
        this._queue.push(intersectionEntry);
        this._scheduleCallback();
        this._observationTargets.set(target, intersectionEntry);
      }.bind(this));
    },

    _scheduleCallback: function() {
      if(this._timeoutId) {
        return;
      }
      this._timeoutId = scope.setTimeout(function() {
        this._descheduleCallback();
        this._callback(this._queue, this);
        this._queue = [];
      }.bind(this), POLL_INTERVAL);
    },

    _descheduleCallback: function() {
      if(!this._timeoutId) {
        return;
      }
      scope.clearTimeout(this._timeoutId);
      this._timeoutId = null;
    },

    _rootRect: function() {
      if(this._root) {
        return getBoundingClientRect(this.root);
      }
      return {
        top: 0,
        left: 0,
        right: scope.innerWidth,
        width: scope.innerWidth,
        bottom: scope.innerHeight,
        height: scope.innerHeight
      };
    },

    _hasCrossedThreshold: function(oldRatio, newRatio) {
      if(oldRatio === newRatio) {
        return;
      }
      var isOnBoundary = this.thresholds.map(function(threshold) {
        return threshold == newRatio;
      }).indexOf(true) !== -1;

      var b1 = this.thresholds.map(function(threshold) {
        return threshold < oldRatio;
      });
      var b2 = this.thresholds.map(function(threshold) {
        return threshold < newRatio;
      });
      var hasCrossedThreshold = b1.map((_, idx) => b1[idx] !== b2[idx]).indexOf(true) !== -1;
      return isOnBoundary || hasCrossedThreshold;
    }
  };

  var intersectRects = function(rect1, rect2) {
    var top = Math.max(rect1.top, rect2.top);
    var bottom = Math.min(rect1.bottom, rect2.bottom);
    var left = Math.max(rect1.left, rect2.left);
    var right = Math.min(rect1.right, rect2.right);
    var intersectionRect = {
      top: top,
      bottom: bottom,
      left: left,
      right: right,
      width: right-left,
      height: bottom-top
    };
    if(top > bottom) {
      intersectionRect.height = 0;
    }
    if(left > right) {
      intersectionRect.width = 0;
    }
    return intersectionRect;
  };

  scope.IntersectionObserver = scope.IntersectionObserver || IntersectionObserver;

  var throttle = function(fn, int) {
    var timer = null;
    return function () {
      if (timer) {
        return;
      }
      timer = setTimeout(function () {
        fn.apply(this, arguments);
        timer = null;
      }.bind(this), int);
    };
  };

  var getBoundingClientRect = function(el) {
    var r = el.getBoundingClientRect();
    // Older IE
    r.width = r.width || r.right - r.left;
    r.height = r.height || r.bottom - r.top;
    return r;
  };
})(this);
<|endoftext|>


<commit_before>import {
  COMPOSE_MOUNT,
  COMPOSE_UNMOUNT,
  COMPOSE_CHANGE,
  COMPOSE_REPLY,
  COMPOSE_REPLY_CANCEL,
  COMPOSE_MENTION,
  COMPOSE_SUBMIT_REQUEST,
  COMPOSE_SUBMIT_SUCCESS,
  COMPOSE_SUBMIT_FAIL,
  COMPOSE_UPLOAD_REQUEST,
  COMPOSE_UPLOAD_SUCCESS,
  COMPOSE_UPLOAD_FAIL,
  COMPOSE_UPLOAD_UNDO,
  COMPOSE_UPLOAD_PROGRESS,
  COMPOSE_SUGGESTIONS_CLEAR,
  COMPOSE_SUGGESTIONS_READY,
  COMPOSE_SUGGESTION_SELECT,
  COMPOSE_ADVANCED_OPTIONS_CHANGE,
  COMPOSE_SENSITIVITY_CHANGE,
  COMPOSE_SPOILERNESS_CHANGE,
  COMPOSE_SPOILER_TEXT_CHANGE,
  COMPOSE_VISIBILITY_CHANGE,
  COMPOSE_EMOJI_INSERT,
  COMPOSE_UPLOAD_CHANGE_REQUEST,
  COMPOSE_UPLOAD_CHANGE_SUCCESS,
  COMPOSE_UPLOAD_CHANGE_FAIL,
  COMPOSE_DOODLE_SET,
  COMPOSE_RESET,
} from 'flavours/glitch/actions/compose';
import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines';
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import uuid from 'flavours/glitch/util/uuid';
import { me } from 'flavours/glitch/util/initial_state';
import { overwrite } from 'flavours/glitch/util/js_helpers';

const initialState = ImmutableMap({
  mounted: false,
  advanced_options: ImmutableMap({
    do_not_federate: false,
    threaded_mode: false,
  }),
  sensitive: false,
  spoiler: false,
  spoiler_text: '',
  privacy: null,
  text: '',
  focusDate: null,
  preselectDate: null,
  in_reply_to: null,
  is_submitting: false,
  is_uploading: false,
  progress: 0,
  media_attachments: ImmutableList(),
  suggestion_token: null,
  suggestions: ImmutableList(),
  default_advanced_options: ImmutableMap({
    do_not_federate: false,
    threaded_mode: null,  //  Do not reset
  }),
  default_privacy: 'public',
  default_sensitive: false,
  resetFileKey: Math.floor((Math.random() * 0x10000)),
  idempotencyKey: null,
  doodle: ImmutableMap({
    fg: 'rgb(  0,    0,    0)',
    bg: 'rgb(255,  255,  255)',
    swapped: false,
    mode: 'draw',
    size: 'normal',
    weight: 2,
    opacity: 1,
    adaptiveStroke: true,
    smoothing: false,
  }),
});

function statusToTextMentions(state, status) {
  let set = ImmutableOrderedSet([]);

  if (status.getIn(['account', 'id']) !== me) {
    set = set.add(`@${status.getIn(['account', 'acct'])} `);
  }

  return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
};

function apiStatusToTextMentions (state, status) {
  let set = ImmutableOrderedSet([]);

  if (status.account.id !== me) {
    set = set.add(`@${status.account.acct} `);
  }

  return set.union(status.mentions.filter(
    mention => mention.id !== me
  ).map(
    mention => `@${mention.acct} `
  )).join('');
}

function clearAll(state) {
  return state.withMutations(map => {
    map.set('text', '');
    map.set('spoiler', false);
    map.set('spoiler_text', '');
    map.set('is_submitting', false);
    map.set('in_reply_to', null);
    map.update(
      'advanced_options',
      map => map.mergeWith(overwrite, state.get('default_advanced_options'))
    );
    map.set('privacy', state.get('default_privacy'));
    map.set('sensitive', false);
    map.update('media_attachments', list => list.clear());
    map.set('idempotencyKey', uuid());
  });
};

function continueThread (state, status) {
  return state.withMutations(function (map) {
    map.set('text', apiStatusToTextMentions(state, status));
    if (status.spoiler_text) {
      map.set('spoiler', true);
      map.set('spoiler_text', status.spoiler_text);
    } else {
      map.set('spoiler', false);
      map.set('spoiler_text', '');
    }
    map.set('is_submitting', false);
    map.set('in_reply_to', status.id);
    map.update(
      'advanced_options',
      map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(status.content) }))
    );
    map.set('privacy', privacyPreference(status.visibility, state.get('default_privacy')));
    map.set('sensitive', false);
    map.update('media_attachments', list => list.clear());
    map.set('idempotencyKey', uuid());
    map.set('focusDate', new Date());
    map.set('preselectDate', new Date());
  });
}

function appendMedia(state, media) {
  const prevSize = state.get('media_attachments').size;

  return state.withMutations(map => {
    map.update('media_attachments', list => list.push(media));
    map.set('is_uploading', false);
    map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());

    if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
      map.set('sensitive', true);
    }
  });
};

function removeMedia(state, mediaId) {
  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
  const prevSize = state.get('media_attachments').size;

  return state.withMutations(map => {
    map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
    map.update('text', text => text.replace(media.get('text_url'), '').trim());
    map.set('idempotencyKey', uuid());

    if (prevSize === 1) {
      map.set('sensitive', false);
    }
  });
};

const insertSuggestion = (state, position, token, completion) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${completion}${completion[0] === ':' ? '\u200B' : ' '}${oldText.slice(position + token.length)}`);
    map.set('suggestion_token', null);
    map.update('suggestions', ImmutableList(), list => list.clear());
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertEmoji = (state, position, emojiData) => {
  const emoji = emojiData.native;

  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${emoji}\u200B${oldText.slice(position)}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const privacyPreference = (a, b) => {
  if (a === 'direct' || b === 'direct') {
    return 'direct';
  } else if (a === 'private' || b === 'private') {
    return 'private';
  } else if (a === 'unlisted' || b === 'unlisted') {
    return 'unlisted';
  } else {
    return 'public';
  }
};

const hydrate = (state, hydratedState) => {
  state = clearAll(state.merge(hydratedState));

  if (hydratedState.has('text')) {
    state = state.set('text', hydratedState.get('text'));
  }

  return state;
};

export default function compose(state = initialState, action) {
  switch(action.type) {
  case STORE_HYDRATE:
    return hydrate(state, action.state.get('compose'));
  case COMPOSE_MOUNT:
    return state.set('mounted', true);
  case COMPOSE_UNMOUNT:
    return state.set('mounted', false);
  case COMPOSE_ADVANCED_OPTIONS_CHANGE:
    return state
      .set('advanced_options', state.get('advanced_options').set(action.option, !!overwrite(!state.getIn(['advanced_options', action.option]), action.value)))
      .set('idempotencyKey', uuid());
  case COMPOSE_SENSITIVITY_CHANGE:
    return state.withMutations(map => {
      if (!state.get('spoiler')) {
        map.set('sensitive', !state.get('sensitive'));
      }

      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SPOILERNESS_CHANGE:
    return state.withMutations(map => {
      map.set('spoiler_text', '');
      map.set('spoiler', !state.get('spoiler'));
      map.set('idempotencyKey', uuid());

      if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
        map.set('sensitive', true);
      }
    });
  case COMPOSE_SPOILER_TEXT_CHANGE:
    return state
      .set('spoiler_text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_VISIBILITY_CHANGE:
    return state
      .set('privacy', action.value)
      .set('idempotencyKey', uuid());
  case COMPOSE_CHANGE:
    return state
      .set('text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_REPLY:
    return state.withMutations(map => {
      map.set('in_reply_to', action.status.get('id'));
      map.set('text', statusToTextMentions(state, action.status));
      map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
      map.update(
        'advanced_options',
        map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(action.status.get('content')) }))
      );
      map.set('focusDate', new Date());
      map.set('preselectDate', new Date());
      map.set('idempotencyKey', uuid());

      if (action.status.get('spoiler_text').length > 0) {
        map.set('spoiler', true);
        map.set('spoiler_text', action.status.get('spoiler_text'));
      } else {
        map.set('spoiler', false);
        map.set('spoiler_text', '');
      }
    });
  case COMPOSE_REPLY_CANCEL:
  case COMPOSE_RESET:
    return state.withMutations(map => {
      map.set('in_reply_to', null);
      map.set('text', '');
      map.set('spoiler', false);
      map.set('spoiler_text', '');
      map.set('privacy', state.get('default_privacy'));
      map.update(
        'advanced_options',
        map => map.mergeWith(overwrite, state.get('default_advanced_options'))
      );
      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SUBMIT_REQUEST:
  case COMPOSE_UPLOAD_CHANGE_REQUEST:
    return state.set('is_submitting', true);
  case COMPOSE_SUBMIT_SUCCESS:
    return action.status && state.getIn(['advanced_options', 'threaded_mode']) ? continueThread(state, action.status) : clearAll(state);
  case COMPOSE_SUBMIT_FAIL:
  case COMPOSE_UPLOAD_CHANGE_FAIL:
    return state.set('is_submitting', false);
  case COMPOSE_UPLOAD_REQUEST:
    return state.set('is_uploading', true);
  case COMPOSE_UPLOAD_SUCCESS:
    return appendMedia(state, fromJS(action.media));
  case COMPOSE_UPLOAD_FAIL:
    return state.set('is_uploading', false);
  case COMPOSE_UPLOAD_UNDO:
    return removeMedia(state, action.media_id);
  case COMPOSE_UPLOAD_PROGRESS:
    return state.set('progress', Math.round((action.loaded / action.total) * 100));
  case COMPOSE_MENTION:
    return state
      .update('text', text => `${text}@${action.account.get('acct')} `)
      .set('focusDate', new Date())
      .set('idempotencyKey', uuid());
  case COMPOSE_SUGGESTIONS_CLEAR:
    return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
  case COMPOSE_SUGGESTIONS_READY:
    return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
  case COMPOSE_SUGGESTION_SELECT:
    return insertSuggestion(state, action.position, action.token, action.completion);
  case TIMELINE_DELETE:
    if (action.id === state.get('in_reply_to')) {
      return state.set('in_reply_to', null);
    } else {
      return state;
    }
  case COMPOSE_EMOJI_INSERT:
    return insertEmoji(state, action.position, action.emoji);
  case COMPOSE_UPLOAD_CHANGE_SUCCESS:
    return state
      .set('is_submitting', false)
      .update('media_attachments', list => list.map(item => {
        if (item.get('id') === action.media.id) {
          return item.set('description', action.media.description);
        }

        return item;
      }));
  case COMPOSE_DOODLE_SET:
    return state.mergeIn(['doodle'], action.options);
  default:
    return state;
  }
};
<commit_msg>Threads now inherit privacy directly (#309)<commit_after>import {
  COMPOSE_MOUNT,
  COMPOSE_UNMOUNT,
  COMPOSE_CHANGE,
  COMPOSE_REPLY,
  COMPOSE_REPLY_CANCEL,
  COMPOSE_MENTION,
  COMPOSE_SUBMIT_REQUEST,
  COMPOSE_SUBMIT_SUCCESS,
  COMPOSE_SUBMIT_FAIL,
  COMPOSE_UPLOAD_REQUEST,
  COMPOSE_UPLOAD_SUCCESS,
  COMPOSE_UPLOAD_FAIL,
  COMPOSE_UPLOAD_UNDO,
  COMPOSE_UPLOAD_PROGRESS,
  COMPOSE_SUGGESTIONS_CLEAR,
  COMPOSE_SUGGESTIONS_READY,
  COMPOSE_SUGGESTION_SELECT,
  COMPOSE_ADVANCED_OPTIONS_CHANGE,
  COMPOSE_SENSITIVITY_CHANGE,
  COMPOSE_SPOILERNESS_CHANGE,
  COMPOSE_SPOILER_TEXT_CHANGE,
  COMPOSE_VISIBILITY_CHANGE,
  COMPOSE_EMOJI_INSERT,
  COMPOSE_UPLOAD_CHANGE_REQUEST,
  COMPOSE_UPLOAD_CHANGE_SUCCESS,
  COMPOSE_UPLOAD_CHANGE_FAIL,
  COMPOSE_DOODLE_SET,
  COMPOSE_RESET,
} from 'flavours/glitch/actions/compose';
import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines';
import { STORE_HYDRATE } from 'flavours/glitch/actions/store';
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import uuid from 'flavours/glitch/util/uuid';
import { me } from 'flavours/glitch/util/initial_state';
import { overwrite } from 'flavours/glitch/util/js_helpers';

const initialState = ImmutableMap({
  mounted: false,
  advanced_options: ImmutableMap({
    do_not_federate: false,
    threaded_mode: false,
  }),
  sensitive: false,
  spoiler: false,
  spoiler_text: '',
  privacy: null,
  text: '',
  focusDate: null,
  preselectDate: null,
  in_reply_to: null,
  is_submitting: false,
  is_uploading: false,
  progress: 0,
  media_attachments: ImmutableList(),
  suggestion_token: null,
  suggestions: ImmutableList(),
  default_advanced_options: ImmutableMap({
    do_not_federate: false,
    threaded_mode: null,  //  Do not reset
  }),
  default_privacy: 'public',
  default_sensitive: false,
  resetFileKey: Math.floor((Math.random() * 0x10000)),
  idempotencyKey: null,
  doodle: ImmutableMap({
    fg: 'rgb(  0,    0,    0)',
    bg: 'rgb(255,  255,  255)',
    swapped: false,
    mode: 'draw',
    size: 'normal',
    weight: 2,
    opacity: 1,
    adaptiveStroke: true,
    smoothing: false,
  }),
});

function statusToTextMentions(state, status) {
  let set = ImmutableOrderedSet([]);

  if (status.getIn(['account', 'id']) !== me) {
    set = set.add(`@${status.getIn(['account', 'acct'])} `);
  }

  return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
};

function apiStatusToTextMentions (state, status) {
  let set = ImmutableOrderedSet([]);

  if (status.account.id !== me) {
    set = set.add(`@${status.account.acct} `);
  }

  return set.union(status.mentions.filter(
    mention => mention.id !== me
  ).map(
    mention => `@${mention.acct} `
  )).join('');
}

function clearAll(state) {
  return state.withMutations(map => {
    map.set('text', '');
    map.set('spoiler', false);
    map.set('spoiler_text', '');
    map.set('is_submitting', false);
    map.set('in_reply_to', null);
    map.update(
      'advanced_options',
      map => map.mergeWith(overwrite, state.get('default_advanced_options'))
    );
    map.set('privacy', state.get('default_privacy'));
    map.set('sensitive', false);
    map.update('media_attachments', list => list.clear());
    map.set('idempotencyKey', uuid());
  });
};

function continueThread (state, status) {
  return state.withMutations(function (map) {
    map.set('text', apiStatusToTextMentions(state, status));
    if (status.spoiler_text) {
      map.set('spoiler', true);
      map.set('spoiler_text', status.spoiler_text);
    } else {
      map.set('spoiler', false);
      map.set('spoiler_text', '');
    }
    map.set('is_submitting', false);
    map.set('in_reply_to', status.id);
    map.update(
      'advanced_options',
      map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(status.content) }))
    );
    map.set('privacy', status.visibility);
    map.set('sensitive', false);
    map.update('media_attachments', list => list.clear());
    map.set('idempotencyKey', uuid());
    map.set('focusDate', new Date());
    map.set('preselectDate', new Date());
  });
}

function appendMedia(state, media) {
  const prevSize = state.get('media_attachments').size;

  return state.withMutations(map => {
    map.update('media_attachments', list => list.push(media));
    map.set('is_uploading', false);
    map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());

    if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
      map.set('sensitive', true);
    }
  });
};

function removeMedia(state, mediaId) {
  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
  const prevSize = state.get('media_attachments').size;

  return state.withMutations(map => {
    map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
    map.update('text', text => text.replace(media.get('text_url'), '').trim());
    map.set('idempotencyKey', uuid());

    if (prevSize === 1) {
      map.set('sensitive', false);
    }
  });
};

const insertSuggestion = (state, position, token, completion) => {
  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${completion}${completion[0] === ':' ? '\u200B' : ' '}${oldText.slice(position + token.length)}`);
    map.set('suggestion_token', null);
    map.update('suggestions', ImmutableList(), list => list.clear());
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const insertEmoji = (state, position, emojiData) => {
  const emoji = emojiData.native;

  return state.withMutations(map => {
    map.update('text', oldText => `${oldText.slice(0, position)}${emoji}\u200B${oldText.slice(position)}`);
    map.set('focusDate', new Date());
    map.set('idempotencyKey', uuid());
  });
};

const privacyPreference = (a, b) => {
  if (a === 'direct' || b === 'direct') {
    return 'direct';
  } else if (a === 'private' || b === 'private') {
    return 'private';
  } else if (a === 'unlisted' || b === 'unlisted') {
    return 'unlisted';
  } else {
    return 'public';
  }
};

const hydrate = (state, hydratedState) => {
  state = clearAll(state.merge(hydratedState));

  if (hydratedState.has('text')) {
    state = state.set('text', hydratedState.get('text'));
  }

  return state;
};

export default function compose(state = initialState, action) {
  switch(action.type) {
  case STORE_HYDRATE:
    return hydrate(state, action.state.get('compose'));
  case COMPOSE_MOUNT:
    return state.set('mounted', true);
  case COMPOSE_UNMOUNT:
    return state.set('mounted', false);
  case COMPOSE_ADVANCED_OPTIONS_CHANGE:
    return state
      .set('advanced_options', state.get('advanced_options').set(action.option, !!overwrite(!state.getIn(['advanced_options', action.option]), action.value)))
      .set('idempotencyKey', uuid());
  case COMPOSE_SENSITIVITY_CHANGE:
    return state.withMutations(map => {
      if (!state.get('spoiler')) {
        map.set('sensitive', !state.get('sensitive'));
      }

      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SPOILERNESS_CHANGE:
    return state.withMutations(map => {
      map.set('spoiler_text', '');
      map.set('spoiler', !state.get('spoiler'));
      map.set('idempotencyKey', uuid());

      if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
        map.set('sensitive', true);
      }
    });
  case COMPOSE_SPOILER_TEXT_CHANGE:
    return state
      .set('spoiler_text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_VISIBILITY_CHANGE:
    return state
      .set('privacy', action.value)
      .set('idempotencyKey', uuid());
  case COMPOSE_CHANGE:
    return state
      .set('text', action.text)
      .set('idempotencyKey', uuid());
  case COMPOSE_REPLY:
    return state.withMutations(map => {
      map.set('in_reply_to', action.status.get('id'));
      map.set('text', statusToTextMentions(state, action.status));
      map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
      map.update(
        'advanced_options',
        map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(action.status.get('content')) }))
      );
      map.set('focusDate', new Date());
      map.set('preselectDate', new Date());
      map.set('idempotencyKey', uuid());

      if (action.status.get('spoiler_text').length > 0) {
        map.set('spoiler', true);
        map.set('spoiler_text', action.status.get('spoiler_text'));
      } else {
        map.set('spoiler', false);
        map.set('spoiler_text', '');
      }
    });
  case COMPOSE_REPLY_CANCEL:
  case COMPOSE_RESET:
    return state.withMutations(map => {
      map.set('in_reply_to', null);
      map.set('text', '');
      map.set('spoiler', false);
      map.set('spoiler_text', '');
      map.set('privacy', state.get('default_privacy'));
      map.update(
        'advanced_options',
        map => map.mergeWith(overwrite, state.get('default_advanced_options'))
      );
      map.set('idempotencyKey', uuid());
    });
  case COMPOSE_SUBMIT_REQUEST:
  case COMPOSE_UPLOAD_CHANGE_REQUEST:
    return state.set('is_submitting', true);
  case COMPOSE_SUBMIT_SUCCESS:
    return action.status && state.getIn(['advanced_options', 'threaded_mode']) ? continueThread(state, action.status) : clearAll(state);
  case COMPOSE_SUBMIT_FAIL:
  case COMPOSE_UPLOAD_CHANGE_FAIL:
    return state.set('is_submitting', false);
  case COMPOSE_UPLOAD_REQUEST:
    return state.set('is_uploading', true);
  case COMPOSE_UPLOAD_SUCCESS:
    return appendMedia(state, fromJS(action.media));
  case COMPOSE_UPLOAD_FAIL:
    return state.set('is_uploading', false);
  case COMPOSE_UPLOAD_UNDO:
    return removeMedia(state, action.media_id);
  case COMPOSE_UPLOAD_PROGRESS:
    return state.set('progress', Math.round((action.loaded / action.total) * 100));
  case COMPOSE_MENTION:
    return state
      .update('text', text => `${text}@${action.account.get('acct')} `)
      .set('focusDate', new Date())
      .set('idempotencyKey', uuid());
  case COMPOSE_SUGGESTIONS_CLEAR:
    return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
  case COMPOSE_SUGGESTIONS_READY:
    return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
  case COMPOSE_SUGGESTION_SELECT:
    return insertSuggestion(state, action.position, action.token, action.completion);
  case TIMELINE_DELETE:
    if (action.id === state.get('in_reply_to')) {
      return state.set('in_reply_to', null);
    } else {
      return state;
    }
  case COMPOSE_EMOJI_INSERT:
    return insertEmoji(state, action.position, action.emoji);
  case COMPOSE_UPLOAD_CHANGE_SUCCESS:
    return state
      .set('is_submitting', false)
      .update('media_attachments', list => list.map(item => {
        if (item.get('id') === action.media.id) {
          return item.set('description', action.media.description);
        }

        return item;
      }));
  case COMPOSE_DOODLE_SET:
    return state.mergeIn(['doodle'], action.options);
  default:
    return state;
  }
};
<|endoftext|>


<commit_before>/*!
*  JqueryAsynchImageLoader (JAIL) : plugin for jQuery
*
* Developed by
* Sebastiano Armeli-Battana (@sebarmeli) - http://sebarmeli.com | http://blog.sebarmeli.com
*
* Licensed under MIT
*/
/* Copyright (c) 2010 Sebastiano Armeli-Battana (http://sebarmeli.com)

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/* JAIL helps loading images asynchronously and it can be used to make your page load faster.
* Selected images will be downloaded after the document is ready not blocking the page
* to render other elements. Images can be loaded after an event is triggered (like clicking
* on a link, mouseovering on some elements, scrolling up/down) or after some delay or simply the
* visible images will be loaded.
*
* First of all, this plugin requires you to make some HTML changes.
* E.g.
*
*
* You can define a noscript block in order to respect the principles of progressive
* enhancemnt
* E.g.
*
*
*
*
* You can call the function in this way
* E.g.
*	$(function(){
*		$('img.lazy').asynchImageLoader();
*	});
*
* You can also have different configurations:
*
* - timeout : number of msec after that the images will be loaded - Default: 10ms
* - effect : effect that makes the images display (Eg "fadein") - Default: "show"
* - speed : in case of fading in an image, you can set the speed - Default: 400
* - selector : selector that you need to bind the trigger event - Default: ""
* - event : event that triggers the image to load - Default: "load". You can choose "click", "mouseover", "scroll"
* - callback : function that will be called after the images are loaded	- Default: ""
* - placeholder: location of an image (such a loader) you want to display while waiting for the images to be loaded - Default: ""
*
*
* Tested with jQuery 1.3.2+ on FF 2/3, Opera 10+, Safari 4+, Chrome on Mac and IE 6/7/8 on Win.
*
* @link http://github.com/sebarmeli/JAIL
* @author Sebastiano Armeli-Battana
* @date 24/12/2010
* @version 0.2
*
*/

/*globals window,jQuery,setTimeout */
(function($){

    $.fn.asynchImageLoader = function(options) {

        // Configuration
        options = $.extend({
                timeout : 10,
                effect : 'fadein',
                speed : 400,
                selector: "",
                event : 'load',
                callback : false ,
                placeholder : false
        }, options);

        var images = this;

        if (options.placeholder !== false) {
	    images.each(function(){
	        $(this).attr("src", options.placeholder);
	    });
        }

	// Event spupported at the moment are : click, mouseover, scroll.
	// When the event is not specified the images will be loaded with a delay
        switch (options.event) {
	    case 'click' :
		$.asynchImageLoader.onEvent.apply(this, Array.prototype.slice.call(arguments));
                break;
	    case 'mouseover' :
		$.asynchImageLoader.onEvent.apply(this, Array.prototype.slice.call(arguments));
	        break;
	    case 'scroll' :
	        $.asynchImageLoader.onScroll.apply(this, Array.prototype.slice.call(arguments));
		break;
	    default:
		$.asynchImageLoader.later.apply(this, [options]);
	    }

        return images.each(function () {
            return this;
        });
    };

    // Methods cointaing the logic
    $.asynchImageLoader = {

	// Images loaded triggered by en event
	onEvent : function(options) {
	    var images = $(this);

	    // Check that "selector" parameter has passed
	    if ($(options.selector).length === 0) {

		// Bind the event to the images
		$(images).bind(options.event, function(e){

		    // Load images
		    if ($.data(this, "loaded") !== "true") {
			$.asynchImageLoader._loadImage(options, this);
		    }

		    if (!options.callback) {
			return false;
		    }

		    // Callback called in case it's been specified
		    return options.callback.call(this, options);
		});

	    } else {

		// Bind the event to the selector specified in the config obj
		$(options.selector).bind(options.event, function(e){

		    // Each image is loaded when the event is triggered
		    $(images).each(function(){

			// Check that the image hasn't been loaded before
			if ($.data(this, "loaded") !== "true") {
			    $.asynchImageLoader._loadImage(options, this);
							}
		        });

		        if (!options.callback) {
			    return false;
			}

			// Callback called in case it's been specified
		        return options.callback.call(this, options, images);
		    });
		}
	    },

	    // Images loaded triggered with some delay
	    later : function(options) {
		var images = $(this);

	        setTimeout(function() {

		    // Images visible loaded onload
		    images.each(function(){
			$.asynchImageLoader._checkTheImageInTheScreen(options, this);
		    });

		    $(window).bind("scroll", function() {
			images.each(function(){
		            if ($.data(this, "loaded") !== "true") {
				$.asynchImageLoader._checkTheImageInTheScreen(options, this);
                            }
			});
	            });
		}, options.timeout);

	    },

	    // Images loaded after the user scolls up/down
	    onScroll : function(options) {

		var images = $(this);

		// Load the images on  ce the user scolls up/down
		$(window).bind("scroll", function() {
		    images.each(function(){
			$.asynchImageLoader._checkTheImageInTheScreen(options, this);
		    });
		});
	    },

	    // Function that checks if the images have been loaded
	    _checkTheImageInTheScreen : function(options, image){

		if ($.data(image, "loaded") === "true") {
		    return;
		}

		if ($.asynchImageLoader._isInTheScreen(window, image)) {
		    $.asynchImageLoader._loadImage(options, image);
		}
	    },

	    // Function that returns true if the image is visible inside the "window"
	    _isInTheScreen : function(windowEl, image) {
		return ($(windowEl).scrollTop() <= $(image).offset().top) &&
		    (($(windowEl).scrollTop() + $(windowEl).height()) >= ($(image).offset().top) &&
			($(windowEl).scrollLeft() <= $(image).offset().left) &&
			    (($(windowEl).scrollLeft() + $(windowEl).width()) >= $(image).offset().left));
		},

	    // Main function --> Load the images copying the "name" attribute into the "src" attribute
	    _loadImage : function(options, image) {

                if (options.effect.match('/fadein/ig')) {
		    $(image).attr("src", $(image).attr("data-href")).fadeIn(options.speed);
		    $.data(image, "loaded","true");
		} else {
		    $(image).attr("src", $(image).attr("data-href")).show();
			$.data(image, "loaded","true");
                }
	    }
    };
}(jQuery));
<commit_msg>"data-href"  used over "name" attr<commit_after>/*!
*  JqueryAsynchImageLoader (JAIL) : plugin for jQuery
*
* Developed by
* Sebastiano Armeli-Battana (@sebarmeli) - http://sebarmeli.com | http://blog.sebarmeli.com
*
* Licensed under MIT
*/
/* Copyright (c) 2010 Sebastiano Armeli-Battana (http://sebarmeli.com)

	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:

	The above copyright notice and this permission notice shall be
	included in all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

/* JAIL helps loading images asynchronously and it can be used to make your page load faster.
* Selected images will be downloaded after the document is ready not blocking the page
* to render other elements. Images can be loaded after an event is triggered (like clicking
* on a link, mouseovering on some elements, scrolling up/down) or after some delay or simply the
* visible images will be loaded.
*
* First of all, this plugin requires you to make some HTML changes.
* E.g.
*
*
* You can define a noscript block in order to respect the principles of progressive
* enhancemnt
* E.g.
*
*
*
*
* You can call the function in this way
* E.g.
*	$(function(){
*		$('img.lazy').asynchImageLoader();
*	});
*
* You can also have different configurations:
*
* - timeout : number of msec after that the images will be loaded - Default: 10ms
* - effect : effect that makes the images display (Eg "fadein") - Default: "show"
* - speed : in case of fading in an image, you can set the speed - Default: 400
* - selector : selector that you need to bind the trigger event - Default: ""
* - event : event that triggers the image to load - Default: "load". You can choose "click", "mouseover", "scroll"
* - callback : function that will be called after the images are loaded	- Default: ""
* - placeholder: location of an image (such a loader) you want to display while waiting for the images to be loaded - Default: ""
*
*
* Tested with jQuery 1.3.2+ on FF 2/3, Opera 10+, Safari 4+, Chrome on Mac and IE 6/7/8 on Win.
*
* @link http://github.com/sebarmeli/JAIL
* @author Sebastiano Armeli-Battana
* @date 24/12/2010
* @version 0.2
*
*/

/*globals window,jQuery,setTimeout */
(function($){

    $.fn.asynchImageLoader = function(options) {

        // Configuration
        options = $.extend({
                timeout : 10,
                effect : 'fadein',
                speed : 400,
                selector: "",
                event : 'load',
                callback : false ,
                placeholder : false
        }, options);

        var images = this;

        if (options.placeholder !== false) {
	    images.each(function(){
	        $(this).attr("src", options.placeholder);
	    });
        }

	// Event spupported at the moment are : click, mouseover, scroll.
	// When the event is not specified the images will be loaded with a delay
        switch (options.event) {
	    case 'click' :
		$.asynchImageLoader.onEvent.apply(this, Array.prototype.slice.call(arguments));
                break;
	    case 'mouseover' :
		$.asynchImageLoader.onEvent.apply(this, Array.prototype.slice.call(arguments));
	        break;
	    case 'scroll' :
	        $.asynchImageLoader.onScroll.apply(this, Array.prototype.slice.call(arguments));
		break;
	    default:
		$.asynchImageLoader.later.apply(this, [options]);
	    }

        return images.each(function () {
            return this;
        });
    };

    // Methods cointaing the logic
    $.asynchImageLoader = {

	// Images loaded triggered by en event
	onEvent : function(options) {
	    var images = $(this);

	    // Check that "selector" parameter has passed
	    if ($(options.selector).length === 0) {

		// Bind the event to the images
		$(images).bind(options.event, function(e){

		    // Load images
		    if ($.data(this, "loaded") !== "true") {
			$.asynchImageLoader._loadImage(options, this);
		    }

		    if (!options.callback) {
			return false;
		    }

		    // Callback called in case it's been specified
		    return options.callback.call(this, options);
		});

	    } else {

		// Bind the event to the selector specified in the config obj
		$(options.selector).bind(options.event, function(e){

		    // Each image is loaded when the event is triggered
		    $(images).each(function(){

			// Check that the image hasn't been loaded before
			if ($.data(this, "loaded") !== "true") {
			    $.asynchImageLoader._loadImage(options, this);
							}
		        });

		        if (!options.callback) {
			    return false;
			}

			// Callback called in case it's been specified
		        return options.callback.call(this, options, images);
		    });
		}
	    },

	    // Images loaded triggered with some delay
	    later : function(options) {
		var images = $(this);

	        setTimeout(function() {

		    // Images visible loaded onload
		    images.each(function(){
			$.asynchImageLoader._checkTheImageInTheScreen(options, this);
		    });

		    $(window).bind("scroll", function() {
			images.each(function(){
		            if ($.data(this, "loaded") !== "true") {
				$.asynchImageLoader._checkTheImageInTheScreen(options, this);
                            }
			});
	            });
		}, options.timeout);

	    },

	    // Images loaded after the user scolls up/down
	    onScroll : function(options) {

		var images = $(this);

		// Load the images on  ce the user scolls up/down
		$(window).bind("scroll", function() {
		    images.each(function(){
			$.asynchImageLoader._checkTheImageInTheScreen(options, this);
		    });
		});
	    },

	    // Function that checks if the images have been loaded
	    _checkTheImageInTheScreen : function(options, image){

		if ($.data(image, "loaded") === "true") {
		    return;
		}

		if ($.asynchImageLoader._isInTheScreen(window, image)) {
		    $.asynchImageLoader._loadImage(options, image);
		}
	    },

	    // Function that returns true if the image is visible inside the "window"
	    _isInTheScreen : function(windowEl, image) {
		return ($(windowEl).scrollTop() <= $(image).offset().top) &&
		    (($(windowEl).scrollTop() + $(windowEl).height()) >= ($(image).offset().top) &&
			($(windowEl).scrollLeft() <= $(image).offset().left) &&
			    (($(windowEl).scrollLeft() + $(windowEl).width()) >= $(image).offset().left));
		},

	    // Main function --> Load the images copying the "data-href" attribute into the "src" attribute
	    _loadImage : function(options, image) {

                if (options.effect.match('/fadein/ig')) {
		    $(image).attr("src", $(image).attr("data-href")).fadeIn(options.speed);
		    $.data(image, "loaded","true");
		} else {
		    $(image).attr("src", $(image).attr("data-href")).show();
		$.data(image, "loaded","true");
                }
	    }
    };
}(jQuery));
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import Tab from './../../generic/Tab';
import Tabs from './../../generic/Tabs';
import TracksContainer from './../../../containers/library/TracksContainer';

// const { dialog } = require('electron').remote;

class Library extends Component {

  componentDidMount() {
    // dialog.showOpenDialog({properties: ['openFile', 'multiSelections']}, (files) => {
    //   console.log(files);
    // });
  }

  render() {
    return (
      <Tabs
        tabData={[
          { keyID: 1, id: 'page1', title: 'Tracks', active:'true' },
          { keyID: 2, id: 'page2', title: 'Artists' },
          { keyID: 3, id: 'page3', title: 'Albums'}
        ]}
      >
        <Tab tabID={'page1'} active={true}>
          <TracksContainer />
        </Tab>

        <Tab tabID={'page2'}>
          Page2
        </Tab>

        <Tab tabID={'page3'}>
          Page3
        </Tab>
      </Tabs>
    );
  }
}

export default Library;
<commit_msg>Remove unnecessary code<commit_after>import React, { Component } from 'react';
import Tab from './../../generic/Tab';
import Tabs from './../../generic/Tabs';
import TracksContainer from './../../../containers/library/TracksContainer';

class Library extends Component {

  render() {
    return (
      <Tabs
        tabData={[
          { keyID: 1, id: 'page1', title: 'Tracks', active:'true' },
          { keyID: 2, id: 'page2', title: 'Artists' },
          { keyID: 3, id: 'page3', title: 'Albums'}
        ]}
      >
        <Tab tabID={'page1'} active={true}>
          <TracksContainer />
        </Tab>

        <Tab tabID={'page2'}>
          Page2
        </Tab>

        <Tab tabID={'page3'}>
          Page3
        </Tab>
      </Tabs>
    );
  }
}

export default Library;
<|endoftext|>


<commit_before>import template from './customer-manage.html';

const customerManage = {
  template: template,
  controller: CustomerManageController,
  controllerAs: 'ManageController',
};

export default customerManage;

// @ngInject
function CustomerManageController(baseControllerClass,
                                  customersService,
                                  paymentDetailsService,
                                  usersService,
                                  currentStateService,
                                  features,
                                  ncUtilsFlash,
                                  $uibModal,
                                  $state,
                                  $q,
                                  $filter,
                                  ENV,
                                  ISSUE_IDS) {
  let controllerScope = this;
  let ManageController = baseControllerClass.extend({
    init: function () {
      this.controllerScope = controllerScope;
      this._super();
      this.paymentDetails = null;
      this.loadInitial();
    },
    loadInitial: function () {
      let vm = this;
      vm.loading = true;
      return currentStateService.getCustomer().then(function (customer) {
        vm.customer = customer;
        vm.getPaymentDetails();
        return vm.getCustomerPermissions(customer).then(function (result) {
          vm.canDeleteCustomer = result.canDeleteCustomer;
          vm.canUpdateCustomerPolicies = result.canUpdateCustomerPolicies;
        });
      }).finally(function () {
        vm.loading = false;
      });
    },
    getPaymentDetails: function () {
      let vm = this;
      paymentDetailsService.getList({
        customer_uuid: vm.customer.uuid
      }).then(function (result) {
        if (result) {
          vm.paymentDetails = result[0];
        }
      });
    },
    getCustomerPermissions: function (customer) {
      return usersService.getCurrentUser().then(function (user) {
        if (user.is_staff) {
          return $q.when({
            canDeleteCustomer: true,
            canUpdateCustomerPolicies: true
          });
        }
        for (let i = 0; i < customer.owners.length; i++) {
          if (user.uuid === customer.owners[i].uuid) {
            return $q.when({
              canDeleteCustomer: ENV.ownerCanDeleteCustomer,
              canUpdateCustomerPolicies: ENV.ownerCanUpdateCustomerPolicies
            });
          }
        }
        return $q.when(false);
      });
    },
    removeCustomer: function () {
      let vm = this;
      if (this.customer.projects.length > 0) {
        if (!features.isVisible('support')) {
          return ncUtilsFlash.error($filter('translate')(gettext('Organization contains projects. Please remove them first.')));
        }
        return $uibModal.open({
          component: 'issueCreateDialog',
          resolve: {
            issue: () => ({
              customer: vm.customer,
              type: ISSUE_IDS.CHANGE_REQUEST,
              summary: gettext('Organization removal'),
            }),
            options: {
              title: gettext('Organization removal'),
              hideTitle: true,
              descriptionLabel: gettext('Reason'),
              descriptionPlaceholder: gettext('Why do you need to remove organization with existing projects?'),
              submitTitle: gettext('Request removal')
            }
          }
        });
      }
      let confirmDelete = confirm($filter('translate')(gettext('Confirm deletion?')));
      if (confirmDelete) {
        currentStateService.setCustomer(null);
        this.customer.$delete().then(function () {
          customersService.clearAllCacheForCurrentEndpoint();
          $state.go('profile.details');
        }, function () {
          currentStateService.setCustomer(vm.customer);
        });
      }
    },
  });

  controllerScope.__proto__ = new ManageController();
}

<commit_msg>Revert identation of input parameters [WAL-765]<commit_after>import template from './customer-manage.html';

const customerManage = {
  template: template,
  controller: CustomerManageController,
  controllerAs: 'ManageController',
};

export default customerManage;

// @ngInject
function CustomerManageController(
  baseControllerClass,
  customersService,
  paymentDetailsService,
  usersService,
  currentStateService,
  features,
  ncUtilsFlash,
  $uibModal,
  $state,
  $q,
  $filter,
  ENV,
  ISSUE_IDS) {
  let controllerScope = this;
  let ManageController = baseControllerClass.extend({
    init: function () {
      this.controllerScope = controllerScope;
      this._super();
      this.paymentDetails = null;
      this.loadInitial();
    },
    loadInitial: function () {
      let vm = this;
      vm.loading = true;
      return currentStateService.getCustomer().then(function (customer) {
        vm.customer = customer;
        vm.getPaymentDetails();
        return vm.getCustomerPermissions(customer).then(function (result) {
          vm.canDeleteCustomer = result.canDeleteCustomer;
          vm.canUpdateCustomerPolicies = result.canUpdateCustomerPolicies;
        });
      }).finally(function () {
        vm.loading = false;
      });
    },
    getPaymentDetails: function () {
      let vm = this;
      paymentDetailsService.getList({
        customer_uuid: vm.customer.uuid
      }).then(function (result) {
        if (result) {
          vm.paymentDetails = result[0];
        }
      });
    },
    getCustomerPermissions: function (customer) {
      return usersService.getCurrentUser().then(function (user) {
        if (user.is_staff) {
          return $q.when({
            canDeleteCustomer: true,
            canUpdateCustomerPolicies: true
          });
        }
        for (let i = 0; i < customer.owners.length; i++) {
          if (user.uuid === customer.owners[i].uuid) {
            return $q.when({
              canDeleteCustomer: ENV.ownerCanDeleteCustomer,
              canUpdateCustomerPolicies: ENV.ownerCanUpdateCustomerPolicies
            });
          }
        }
        return $q.when(false);
      });
    },
    removeCustomer: function () {
      let vm = this;
      if (this.customer.projects.length > 0) {
        if (!features.isVisible('support')) {
          return ncUtilsFlash.error($filter('translate')(gettext('Organization contains projects. Please remove them first.')));
        }
        return $uibModal.open({
          component: 'issueCreateDialog',
          resolve: {
            issue: () => ({
              customer: vm.customer,
              type: ISSUE_IDS.CHANGE_REQUEST,
              summary: gettext('Organization removal'),
            }),
            options: {
              title: gettext('Organization removal'),
              hideTitle: true,
              descriptionLabel: gettext('Reason'),
              descriptionPlaceholder: gettext('Why do you need to remove organization with existing projects?'),
              submitTitle: gettext('Request removal')
            }
          }
        });
      }
      let confirmDelete = confirm($filter('translate')(gettext('Confirm deletion?')));
      if (confirmDelete) {
        currentStateService.setCustomer(null);
        this.customer.$delete().then(function () {
          customersService.clearAllCacheForCurrentEndpoint();
          $state.go('profile.details');
        }, function () {
          currentStateService.setCustomer(vm.customer);
        });
      }
    },
  });

  controllerScope.__proto__ = new ManageController();
}

<|endoftext|>