<commit_before>/**!
 * Greenpeace Email-only Pledge Signing for Action Template v0.3
 * @name            p3.pledge_with_email_only.js
 * @fileOverview    Checks if visitor can sign in using only an email address
 *                  Prompts for missing fields
 * @copyright       Copyright 2013, Greenpeace International
 * @license         MIT License (opensource.org/licenses/MIT)
 * @version         0.3.7
 * @author          Ray Walker <hello@raywalker.it>
 * @requires        jQuery 1.6+,
 *                  Modernizr,
 *                  p3.validation.js
 *                  p3.request.js,
 *                  p3.selectors.js
 * @example         $.p3.pledge_with_email_only('#action-form' [, options]);
 *
 */
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, jquery:true, indent:4, maxerr:50 */
/*global Modernizr */
(function($, M, undef) {
    'use strict';

    var _p3 = $.p3 || {},
    defaults = {
        /* Selector for the email input field */
        emailField:             '#UserEmail',
        /* Valid options: 'email', 'uuid' (uuid not yet implemented) */
        identifyUserBy:         'email',
        /* Selector for the registration fields */
        registrationFields:     '#action-form-register',
        /* Selector(s) for the fields we DO NOT want to hide */
        exceptionFields:        '#action-form-message, #action-smallprints',
        /* Endpoint URL to check if the user can sign using only email address */
        signerCheckURL:         'https://secured.greenpeace.org/international/en/api/v2/pledges/signercheck/',
        /* Use p3.validation plugin to validate the form */
        validateForm:           true,
        /* Endpoint for form validation rules, passed to $.p3.validation */
        validationRulesURL:     'https://secured.greenpeace.org/international/en/api/v2/pledges/validation/',
        /* Duration to animate the display of hidden missing fields
         * Set to 0 to disable */
        animationDuration:      350,
        /* Disable form input if an error occurs */
        disableOnError:         false,
        /* GET variables to be added to both the signer check and form validation requests */
        params:                 {},
        showSummary:            false,
        messageElement:         '

'
    };

    // Custom selector to match country codes to country names
    $.expr[":"].p3_empty = function(el) {
        var $el = $(el);
        if ($el.attr('name') === undef) {
            return false;
        }
        return $el.val() === '';
    };

    _p3.pledge_with_email_only = function(el, options) {
        var config = $.extend(true, defaults, options || {}),
        $el = $(el),
        $form = ($el.is('form')) ? $el : $('form', $el),
        $emailField = $(config.emailField),
        $submit = $('input[type=submit]', $form),
        // Keep track of emails we've tested against the signer check endpoint
        checkedUserEmails = [],
        request = $.p3.request(config.signerCheckURL),
        prefix = '$.p3.pledge_with_email_only :: ',
        query = {
            url: request.url,
            parameters: $.extend(true, request.parameters, config.params)
        },
        /**
         * Ensures the page identifier is set, throws an error if not defined
         */
        setPageIdentifier = function() {
            if (query.parameters.page === undef && query.parameters.action === undef) {
                throw new Error(prefix + 'Page or Action identifier not found');
            }
        },
        /**
         * Loads user identifier from the appropriate place
         */
        setUserIdentifier = function() {
            switch (config.identifyUserBy) {
                case 'email':
                    query.parameters.user = $emailField.val();
                    break;
                case 'uuid':
                    throw new Error(prefix + 'uuid not implemented');
                default:
                    throw new Error(prefix + 'config.identifyUserBy: ' + config.identifyUserBy + ' invalid');
            }
        },
        /**
         * Shows a console warning if the expiry parameter is not set
         */
        setExpiryDate = function() {
            if (query.parameters.expire === undef) {
                console.warn(prefix + 'No expiry date set');
            }
        },
        /**
         * Performs the json query against signer check endpoint
         * @returns     {boolean} True if user can pledge using only email address, false if not
         */
        checkEmail = function(hash) {

            // Disable form input until we've obtained signer check status
            $submit.prop('disabled', 'disabled').addClass('disabled');

            checkedUserEmails[hash] = {
                checked: false
            };

            $.getJSON(query.url, query.parameters, function(response) {
                // Mark this email as having been tested against the endpoint
                checkedUserEmails[hash].checked = true;

                // Re-enable form submit
                $submit.removeProp('disabled').removeClass('disabled');

                if (response.status === 'success') {
                    // User can sign using email only

                    // Hide and disable unnecessary fields, in case they were
                    // previously displayed for a different email
                    hideFormFields();

                    // and then submit the form
                    submitForm(hash);
                } else {
                    // This user cannot sign with email only

                    // Error handling
                    switch (response.error.code) {
                    case 1:
                        throw new Error(prefix + 'Invalid Key');
                    case 2:
                    case 3:
                    case 4:
                    case 5:
                        // Errors 1 through 5 indicate an invalid page
                        console.error(response.error);
                        throw new Error(prefix + 'Invalid parameters: ', query.parameters);
                        // Errors 6 through 12 are not relevant to this operation
                    case 13:
                        // This user has already signed this pledge
//                        console.log(prefix + 'User has already signed this pledge');
                        var $emailContainer = $emailField.parents('.email:first'),
                        $message = $('.message', $emailContainer);

                        if (!$message.length) {
                            // Message container doesn't exist, so add it
                            $emailContainer.append(config.messageElement);
                            $message = $('.message', $emailContainer);
                        }
                        $message.append('' + response.error.pledge.unique + '');
                        $emailField.addClass('error');
                        break;
                    case 15:
                        // User does not exist
//                        console.log(prefix + 'New user, show all fields');
                        $('.first-time', $form).show(config.animationDuration);
                        showAllFormFields();
                        break;
                    case 16:
                        // User exists, but is missing required fields
//                        console.warn(prefix + 'User exists, but is missing fields');
                        $('.first-time', $form).html('
Welcome back!
We just need a little more information for this pledge
').show(config.animationDuration);
                        showMissingFields(response.user);
                        break;
                    default:
                        console.warn('Unhandled error code: ' + response.error.code, response.error);
                    }
                }

            }).fail(function() {
                if (!config.disableOnError) {
                    console.warn(prefix + 'Failed to load JSON, all form inputs re-enabled');
                    showAllFormFields();
                    $submit.removeProp('disabled').removeClass('disabled');
                } else {
                    throw new Error('$.p3.pledge_with_email_only.js :: Signer API request failed');
                }
            });
        },
        /* Executes the email-specific form submission event */
        submitForm = function (hash) {
//            console.log('Submitting form with hash ' + hash);
            $(window).trigger('submit_' + hash);
        },
        /**
         * @param   {obj} fields JSON response.user property
         */
        showMissingFields = function(fields) {
            console.log(prefix + 'showMissingFields');
            $.each(fields, function(label) {
                // API returns { field: false } on missing fields
                if (fields[label] === false) {
                    var $field = $('div:classNoCase("' + label + '")', $form),
                        $input = $(':input', $field);

                    if ($input) {
                        // Enable field
                        $input.removeProp('disabled');

                        // Display the field
                        $field.show(config.animationDuration);
                    } else {
                        console.warn(prefix + '' + label + ' not found');
                    }
                }

            });
        },
        /* Hides all form fields except email,
         * also resets invalid state */
        hideFormFields = function () {
            $(config.registrationFields + ' > :not(' + config.exceptionFields + ', #action-submit-full)').hide().find(':input').removeClass('error').prop('disabled', 'disabled');
        },
        /* Shows all form fields */
        showAllFormFields = function () {
            $(config.registrationFields + ' > :not(' + config.exceptionFields + ', #action-submit-full)').show(config.animationDuration).find(':input').removeProp('disabled');
        },
        /* Main application chunk, executed once Modernizr is satisfied */
        init = function() {
            // Hide unwanted form elements
            hideFormFields();

            setPageIdentifier();
            setExpiryDate();

            // Intercept form submission on regular fields by the enter key
            $(':input[type!=submit]', $form).keypress(function (e) {
                if (e.which === 13) {
                    var $this = $(this);

                    if ($this.is('textarea')) {
                        return true;
                    } else  {
                        e.preventDefault();
                        $(this).blur();
                        $submit.focus().click();
                        return false;
                    }

                }
            });

            $submit.click( function (e) {
                // Initialise user parameter with email form field
                setUserIdentifier();

                if (query.parameters.user) {

                    if (checkedUserEmails[query.parameters.user] && checkedUserEmails[query.parameters.user].checked) {
//                        console.log('Already tested this email');
                        $(window).trigger('submit_' + query.parameters.user);
                    } else {
//                        console.log('Haven\'t tested this email yet...');
                        // Haven't checked this email, so prevent form submission
                        e.preventDefault();

                        hideFormFields();

                        if ($emailField.valid()) {
                            // listen for successful API check
                            $(window).on('submit_' + query.parameters.user, function () {
                                // submit the form
                                $form.submit();
                            });

                            // test the API for this email
                            checkEmail(query.parameters.user);
                        } else {
                            $form.submit();
                        }
                    }
                } else {
                    // Skip the theatrics if there's no email address
                    $form.submit();
                }

            });

            if (config.validateForm) {
                // Initialise validation plugin
                $.p3.validation($form, {
                    jsonURL: config.validationRulesURL,
                    params: query.parameters,
                    debug:true,
                    showSummary: config.showSummary,
                    disableOnError: config.disableOnError
                });
            }

        };

        M.load({
            test: window.JSON,
            nope: [
                'dist/js/compat/json.min.js'
            ],
            complete: init
        });

        $.p3 = _p3;
    };

}(jQuery, Modernizr));
<commit_msg>p3.pledge_with_email_only - 0.3.8 - Bug and appearance fixes when resubmitting previously tested emails.  Also fixed message error when form has already pledged<commit_after>/**!
 * Greenpeace Email-only Pledge Signing for Action Template v0.3
 * @name            p3.pledge_with_email_only.js
 * @fileOverview    Checks if visitor can sign in using only an email address
 *                  Prompts for missing fields
 * @copyright       Copyright 2013, Greenpeace International
 * @license         MIT License (opensource.org/licenses/MIT)
 * @version         0.3.8
 * @author          Ray Walker <hello@raywalker.it>
 * @requires        jQuery 1.6+,
 *                  Modernizr,
 *                  p3.validation.js
 *                  p3.request.js,
 *                  p3.selectors.js
 * @example         $.p3.pledge_with_email_only('#action-form' [, options]);
 *
 */
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, jquery:true, indent:4, maxerr:50 */
/*global Modernizr */
(function($, M, global, undef) {
    'use strict';

    var _p3 = $.p3 || {},
        defaults = {
            /* Selector for the email input field */
            emailField: '#UserEmail',
            /* Valid options: 'email', 'uuid' (uuid not yet implemented) */
            identifyUserBy: 'email',
            /* Selector for the registration fields */
            registrationFields: '#action-form-register',
            /* Selector(s) for the fields we DO NOT want to hide */
            exceptionFields: '#action-form-message, #action-smallprints',
            /* Endpoint URL to check if the user can sign using only email address */
            signerCheckURL: 'https://secured.greenpeace.org/international/en/api/v2/pledges/signercheck/',
            /* Use p3.validation plugin to validate the form */
            validateForm: true,
            /* Endpoint for form validation rules, passed to $.p3.validation */
            validationRulesURL: 'https://secured.greenpeace.org/international/en/api/v2/pledges/validation/',
            /* Duration to animate the display of hidden missing fields
             * Set to 0 to disable */
            animationDuration: 350,
            /* Disable form input if an error occurs */
            disableOnError: false,
            /* GET variables to be added to both the signer check and form validation requests */
            params: {},
            showSummary: false,
            messageElement: '

'
        };

    // Custom selector to match country codes to country names
    $.expr[":"].p3_empty = function(el) {
        var $el = $(el);
        if ($el.attr('name') === undef) {
            return false;
        }
        return $el.val() === '';
    };

    _p3.pledge_with_email_only = function(el, options) {
        var config = $.extend(true, defaults, options || {}),
            $el = $(el),
            $form = ($el.is('form')) ? $el : $('form', $el),
            $emailField = $(config.emailField),
            $submit = $('input[type=submit]', $form),
            // Keep track of emails we've tested against the signer check endpoint
            checkedUserEmails = [],
            request = $.p3.request(config.signerCheckURL),
            prefix = '$.p3.pledge_with_email_only :: ',
            query = {
                url: request.url,
                parameters: $.extend(true, request.parameters, config.params)
            },
            /**
             * Ensures the page identifier is set, throws an error if not defined
             */
            setPageIdentifier = function() {
                if (query.parameters.page === undef && query.parameters.action === undef) {
                    throw new Error(prefix + 'Page or Action identifier not found');
                }
            },
            /**
             * Loads user identifier from the appropriate place
             */
            setUserIdentifier = function() {
                switch (config.identifyUserBy) {
                    case 'email':
                        query.parameters.user = $emailField.val();
                        break;
                    case 'uuid':
                        throw new Error(prefix + 'uuid not implemented');
                    default:
                        throw new Error(prefix + 'config.identifyUserBy: ' + config.identifyUserBy + ' invalid');
                }

                return query.parameters.user;
            },
            /**
             * Shows a console warning if the expiry parameter is not set
             */
            setExpiryDate = function() {
                if (query.parameters.expire === undef) {
                    console.warn(prefix + 'No expiry date set');
                }
            },
            /**
             * Performs the json query against signer check endpoint
             * @returns     {boolean} True if user can pledge using only email address, false if not
             */
            checkEmail = function(hash) {

                // Disable form input until we've obtained signer check status
                $submit.prop('disabled', 'disabled').addClass('disabled');

                checkedUserEmails[hash] = {
                    checked: false,
                    valid: false
                };

                $.getJSON(query.url, query.parameters, function(response) {
                    // Mark this email as having been tested against the endpoint
                    checkedUserEmails[hash].checked = true;

                    // Re-enable form submit
                    $submit.removeProp('disabled').removeClass('disabled');

                    if (response.status === 'success') {
                        // User can sign using email only
                        checkedUserEmails[hash].valid = true;

                        // Hide and disable unnecessary fields, in case they were
                        // previously displayed for a different email
                        hideFormFields();

                        // and then submit the form
                        submitForm(hash);
                    } else {
                        // This user cannot sign with email only

                        // Error handling
                        switch (response.error.code) {
                            case 1:
                                throw new Error(prefix + 'Invalid Key');
                            case 2:
                            case 3:
                            case 4:
                            case 5:
                                // Errors 1 through 5 indicate an invalid page
                                console.error(response.error);
                                throw new Error(prefix + 'Invalid parameters: ', query.parameters);
                                // Errors 6 through 12 are not relevant to this operation
                            case 13:
                                // This user has already signed this pledge
//                        console.log(prefix + 'User has already signed this pledge');
                                var $emailContainer = $emailField.parents('.email:first'),
                                    $message = $('.message', $emailContainer);

                                if (!$message.length) {
                                    // Message container doesn't exist, so add it
                                    $emailContainer.append(config.messageElement);
                                    $message = $('.message', $emailContainer);
                                }

                                checkedUserEmails[hash].valid = false;

                                $message.append('' + response.error.message + '');
                                $emailField.addClass('error');
                                break;
                            case 15:
                                // User does not exist
//                        console.log(prefix + 'New user, show all fields');
                                $('.first-time', $form).show(config.animationDuration);
                                checkedUserEmails[hash].valid = true;
                                showAllFormFields();
                                break;
                            case 16:
                                // User exists, but is missing required fields
//                        console.warn(prefix + 'User exists, but is missing fields');
                                checkedUserEmails[hash].valid = true;
                                $('.first-time', $form).html('
Welcome back!
We just need a little more information for this pledge
').show(config.animationDuration);
                                showMissingFields(response.user);
                                break;
                            default:
                                console.warn('Unhandled error code: ' + response.error.code, response.error);
                        }
                    }

                }).fail(function() {
                    if (!config.disableOnError) {
                        console.warn(prefix + 'Failed to load JSON, all form inputs re-enabled');
                        showAllFormFields();
                        $submit.removeProp('disabled').removeClass('disabled');
                    } else {
                        throw new Error('$.p3.pledge_with_email_only.js :: Signer API request failed');
                    }
                });
            },
            /* Executes the email-specific form submission event */
            submitForm = function(hash) {
                if (hash) {
                    $.event.trigger('submit_' + hash);
                } else {
                    if (!$(':input[type=submit]', $form).is(':disabled')) {
                        $form.submit();
                    }
                }
            },
            /**
             * @param   {obj} fields JSON response.user property
             */
            showMissingFields = function(fields) {
//            console.log(prefix + 'showMissingFields');
                $.each(fields, function(label) {
                    // API returns { field: false } on missing fields
                    if (fields[label] === false) {
                        var $field = $('div:classNoCase("' + label + '")', $form),
                            $input = $(':input', $field);

                        if ($input) {
                            // Enable field
                            $input.removeProp('disabled');

                            // Display the field
                            $field.show(config.animationDuration);
                        } else {
                            console.warn(prefix + '' + label + ' not found');
                        }
                    }

                });
            },
            /* Hides all form fields except email,
             * also resets invalid state */
            hideFormFields = function() {
                $(config.registrationFields + ' > :not(' + config.exceptionFields + ', #action-submit-full)').hide().find(':input').removeClass('error').prop('disabled', 'disabled');
            },
            /* Shows all form fields */
            showAllFormFields = function() {
                $(config.registrationFields + ' > :not(' + config.exceptionFields + ', #action-submit-full)').show(config.animationDuration).find(':input').removeProp('disabled');
            },
            /* Main application chunk, executed once Modernizr is satisfied */
            init = function() {
                // Hide unwanted form elements
                hideFormFields();

                setPageIdentifier();
                setExpiryDate();

                // Intercept form submission on regular form fields by the enter key
                $(':input[type!=submit]', $form).keypress(function(e) {
                    if (e.which === 13) {
                        var $this = $(this);

                        if ($this.is('textarea')) {
                            return true;
                        } else {
                            e.preventDefault();
                            $this.blur();
                            $submit.focus().click();
                            return false;
                        }
                    }
                });

                $submit.click(function(e) {
                    // Initialise user parameter with email form field
                    var user = setUserIdentifier();

                    if (user) {
                        if (checkedUserEmails[user] && checkedUserEmails[user].checked) {
                            if (checkedUserEmails[user].valid) {
                                $.event.trigger('submit_' + user);
                            } else {
                                setTimeout(function() {
                                    $emailField.parent().find('.error').show(config.animationDuration);
                                }, config.animationDuration);
                            }
                        } else {
                            // Haven't checked this email, so prevent form submission
                            e.preventDefault();

                            hideFormFields();

                            if ($emailField.valid()) {
                                // listen for successful API check
                                $(global).on('submit_' + user, function() {
                                    // submit the form
                                    submitForm();
                                });

                                // test the API for this email
                                checkEmail(user);
                            } else {
                                submitForm();
                            }
                        }
                    } else {
                        hideFormFields();
                        submitForm();
                    }

                });

                if (config.validateForm) {
                    // Initialise validation plugin
                    $.p3.validation($form, {
                        jsonURL: config.validationRulesURL,
                        params: query.parameters,
                        debug: true,
                        showSummary: config.showSummary,
                        disableOnError: config.disableOnError
                    });
                }

            };

        M.load({
            test: global.JSON,
            nope: [
                'js/compat/json.min.js'
            ],
            complete: init
        });

        $.p3 = _p3;
    };

}(jQuery, Modernizr, this));
<|endoftext|>


<commit_before>'use strict';

const some = require('lodash.some');

module.exports = ({ Plugin, types: t }) => {
  const shouldRevisit = Symbol('shouldRevisit');

  const main = {
    // remove side effectless statement
    ExpressionStatement(path) {
      if (path.get('expression').isPure() && !path.isCompletionRecord()) {
        if (!path.parentPath.isBlockStatement()) {
          path.replaceWith(t.emptyStatement());
        } else {
          path.remove();
        }
      }
    },

    Function: {

      // Let's take all the vars in a function that are not in the top level scope and hoist them
      // with the first var declaration in the top-level scope. This transform in itself may
      // not yield much returns (or even can be marginally harmful to size). However it's great
      // for taking away statements from blocks that can be only expressions which the `simplify`
      // plugin can turn into other things (e.g. if => conditional).
      exit(path) {
        const { node, scope } = path;
        const seen = new Set();
        const declars = [];
        for (let name in scope.bindings) {
          let binding = scope.bindings[name];
          if (!binding.path.isVariableDeclarator()) {
            continue;
          }

          const declarPath = binding.path.parentPath;
          if (seen.has(declarPath)) {
            continue;
          }
          seen.add(declarPath);

          if (declarPath.parentPath.isForInStatement()) {
            continue;
          }

          if (declarPath.parentPath.parentPath.isFunction()) {
            continue;
          }

          if (!declarPath.node || !declarPath.node.declarations) {
            continue;
          }

          const assignmentSequence = [];
          for (let declar of declarPath.node.declarations) {
            declars.push(declar);
            if (declar.init) {
              assignmentSequence.push(t.assignmentExpression('=', declar.id, declar.init));
              declar.init = null;
            }
          }

          if (assignmentSequence.length) {
            declarPath.replaceWith(t.sequenceExpression(assignmentSequence));
          } else {
            declarPath.remove();
          }
        }

        if (declars.length) {
          for (let statement of node.body.body) {
            if (t.isVariableDeclaration(statement)) {
              statement.declarations.push(...declars);
              return;
            }
          }
          const varDecl = t.variableDeclaration('var', declars);
          node.body.body.unshift(varDecl);
        }
      },
    },

    // Remove bindings with no references.
    Scope: {
      exit(path) {
        if (path.node[shouldRevisit]) {
          delete path.node[shouldRevisit];
          path.visit();
        }
      },

      enter(path) {
        if (path.isProgram()) {
          return;
        }

        const { scope } = path;
        for (let name in scope.bindings) {
          let binding = scope.bindings[name];
          if (!binding.referenced && binding.kind !== 'param' && binding.kind !== 'module') {
            if (binding.path.isVariableDeclarator()) {
              if (binding.path.parentPath.parentPath &&
                binding.path.parentPath.parentPath.isForInStatement()
              ) {
                // Can't remove if in a for in statement `for (var x in wat)`.
                continue;
              }
            } else if (!scope.isPure(binding.path.node)) {
              continue;
            } else if (binding.path.isFunctionExpression()) {
              // `bar(function foo() {})` foo is not referenced but it's used.
              continue;
            }

            const mutations = [];
            let bail = false;
            // Make sure none of the assignments value is used
            binding.constantViolations.forEach(p => {
              if (bail || p === binding.path) {
                return;
              }

              if (!p.parentPath.isExpressionStatement()) {
                bail = true;
              }

              if (p.isAssignmentExpression() && !p.get('right').isPure()) {
                mutations.push(() => p.replaceWith(p.get('right')));
              } else {
                mutations.push(() => p.remove());
              }
            });

            if (bail) {
              continue;
            }

            if (binding.path.isVariableDeclarator() && binding.path.node.init &&
              !scope.isPure(binding.path.node.init)
            ) {
              if (binding.path.parentPath.node.declarations.length !== 1) {
                continue;
              }

              binding.path.parentPath.replaceWith(binding.path.node.init);
            } else {
              updateReferences(binding.path, this);
              binding.path.remove();
            }

            mutations.forEach(f => f());
            scope.removeBinding(name);
          } else if (binding.constant) {
            if (binding.path.isFunctionDeclaration() ||
                (binding.path.isVariableDeclarator() && binding.path.get('init').isFunction())) {
                  const fun = binding.path.isFunctionDeclaration() ? binding.path : binding.path.get('init');
                  let allInside = true;
                  for (let ref of binding.referencePaths) {
                    if (!ref.find(p => p.node === fun.node)) {
                      allInside = false;
                      break;
                    }
                  }

                  if (allInside) {
                    scope.removeBinding(name);
                    updateReferences(binding.path, this);
                    binding.path.remove();
                    continue;
                  }
            }

            if (binding.references === 1 && binding.kind !== 'param' && binding.kind !== 'module' && binding.constant) {
              let replacement = binding.path.node;
              if (t.isVariableDeclarator(replacement)) {
                replacement = replacement.init;
              }
              if (!replacement) {
                continue;
              }

              if (!scope.isPure(replacement, true)) {
                continue;
              }

              if (binding.referencePaths.length > 1) {
                throw new Error('Expected only one reference');
              }

              let parent = binding.path.parent;
              if (t.isVariableDeclaration(parent)) {
                parent = binding.path.parentPath.parent;
              }

              const refPath = binding.referencePaths[0];

              // 1. Make sure we share the parent with the node. In other words it's lexically defined
              // and not in an if statement or otherwise.
              // 2. If the replacement is an object then we have to make sure we are not in a loop or a function
              // because otherwise we'll be inlining and doing a lot more allocation than we have to
              // which would also could affect correctness in that they are not the same reference.
              let mayLoop = false;
              const sharesRoot = refPath.find(({ node }) => {
                if (!mayLoop) {
                  mayLoop = t.isWhileStatement(node) || t.isFor(node) || t.isFunction(node);
                }
                return node === parent;
              });

              // Anything that inherits from Object.
              const isObj = (n) => t.isFunction(n) || t.isObjectExpression(n) || t.isArrayExpression(n);
              const isReplacementObj = isObj(replacement) || some(replacement, isObj);

              if (!sharesRoot || (isReplacementObj && mayLoop)) {
                continue;
              }

              const replaced = replace(binding.referencePaths[0], {
                binding,
                scope,
                replacement,
              });

              if (replaced) {
                scope.removeBinding(name);
                if (binding.path.node) {
                  binding.path.remove();
                }
              }
            }
          }
        }
      },
    },

    // Remove unreachable code.
    BlockStatement(path) {
      const paths = path.get('body');

      let purge = false;

      for (let i = 0; i < paths.length; i++) {
        const p = paths[i];

        if (!purge && p.isCompletionStatement()) {
          purge = true;
          continue;
        }

        if (purge && !p.isFunctionDeclaration()) {
          p.remove();
        }
      }
    },

    // Double check unreachable code and remove return statements that
    // have no semantic meaning
    ReturnStatement(path) {
      const { node } = path;
      if (!path.inList) {
        return;
      }

      // Not last in it's block? (See BlockStatement visitor)
      if (path.container.length - 1 !== path.key &&
          !path.getSibling(path.key + 1).isFunctionDeclaration() &&
          path.parentPath.isBlockStatement()
      ) {
        // This is probably a new oppurtinity by some other transform
        // let's call the block visitor on this again before proceeding.
        path.parentPath.pushContext(path.context);
        path.parentPath.visit();
        path.parentPath.popContext();

        return;
      }

      if (node.argument) {
        return;
      }

      let noNext = true;
      let parentPath = path.parentPath;
      while (parentPath && !parentPath.isFunction() && noNext) {
        const nextPath = parentPath.getSibling(parentPath.key + 1);
        if (nextPath.node) {
          if (nextPath.isReturnStatement()) {
            nextPath.pushContext(path.context);
            nextPath.visit();
            nextPath.popContext();
            if (parentPath.getSibling(parentPath.key + 1).node) {
              noNext = false;
              break;
            }
          } else {
            noNext = false;
            break;
          }
        }

        parentPath = parentPath.parentPath;
      }

      if (noNext) {
        path.remove();
      }
    },

    ConditionalExpression(path) {
      const { node } = path;
      const evaluateTest = path.get('test').evaluateTruthy();
      if (evaluateTest === true) {
        path.replaceWith(node.consequent);
      } else if (evaluateTest === false) {
        path.replaceWith(node.alternate);
      }
    },

    IfStatement: {
      exit(path) {
        const { node } = path;
        let { consequent, alternate, test } = node;

        const evaluateTest = path.get('test').evaluateTruthy();

        // we can check if a test will be truthy 100% and if so then we can inline
        // the consequent and completely ignore the alternate
        //
        //   if (true) { foo; } -> { foo; }
        //   if ("foo") { foo; } -> { foo; }
        //
        if (evaluateTest === true) {
          path.replaceWithMultiple(toStatements(consequent));
          return;
        }

        // we can check if a test will be falsy 100% and if so we can inline the
        // alternate if there is one and completely remove the consequent
        //
        //   if ("") { bar; } else { foo; } -> { foo; }
        //   if ("") { bar; } ->
        //
        if (evaluateTest === false) {
          if (alternate) {
            path.replaceWithMultiple(toStatements(alternate));
            return;
          } else {
            path.remove();
          }
        }

        // remove alternate blocks that are empty
        //
        //   if (foo) { foo; } else {} -> if (foo) { foo; }
        //
        if (t.isBlockStatement(alternate) && !alternate.body.length) {
          alternate = node.alternate = null;
        }

        // if the consequent block is empty turn alternate blocks into a consequent
        // and flip the test
        //
        //   if (foo) {} else { bar; } -> if (!foo) { bar; }
        //

        if (t.isBlockStatement(consequent) && !consequent.body.length &&
            t.isBlockStatement(alternate) && alternate.body.length
        ) {
          node.consequent = node.alternate;
          node.alternate = null;
          node.test = t.unaryExpression('!', test, true);
        }
      },
    },

    // Join assignment and definition when in sequence.
    // var x; x = 1; -> var x = 1;
    AssignmentExpression(path) {
      if (!path.get('left').isIdentifier() ||
          !path.parentPath.isExpressionStatement()
      ) {
        return;
      }

      const prev = path.parentPath.getSibling(path.parentPath.key - 1);
      if (!(prev && prev.isVariableDeclaration())) {
        return;
      }

      const declars = prev.node.declarations;
      if (declars.length !== 1 || declars[0].init ||
          declars[0].id.name !== path.get('left').node.name
      ) {
        return;
      }
      declars[0].init = path.node.right;
      path.remove();
    },

    // Remove named function expression name. While this is dangerous as it changes
    // `function.name` all minifiers do it and hence became a standard.
    FunctionExpression(path) {
      const id = path.get('id').node;
      if (!id) {
        return;
      }

      const { node, scope } = path;

      const binding = scope.getBinding(id.name);

      // Check if shadowed or is not referenced.
      if (binding.path.node !== node || !binding.referenced) {
        node.id = null;
      }
    },

    // Put the `var` in the left if feasible.
    ForInStatement(path) {
      const left = path.get('left');
      if (!left.isIdentifier()) {
        return;
      }

      const binding = path.scope.getBinding(left.node.name);
      if (binding.scope.getFunctionParent() !== path.scope.getFunctionParent()) {
        return;
      }

      if (!binding.path.isVariableDeclarator()) {
        return;
      }

      // If it has company then it's probably more efficient to keep.
      if (binding.path.parent.declarations.length > 1) {
        return;
      }

      // meh
      if (binding.path.node.init) {
        return;
      }

      binding.path.remove();
      path.node.left = t.variableDeclaration('var', [t.variableDeclarator(left.node)]);
    },
  };

  return {
    visitor: {
      Program(path) {
        // We need to run this plugin in isolation.
        path.traverse(main, { functionToBindings: new Map() });
      },
    },
  };

  function toStatements(node) {
    if (t.isBlockStatement(node)) {
      let hasBlockScoped = false;

      for (let i = 0; i < node.body.length; i++) {
        let bodyNode = node.body[i];
        if (t.isBlockScoped(bodyNode)) {
          hasBlockScoped = true;
        }
      }

      if (!hasBlockScoped) {
        return node.body;
      }
    }
    return node;
  }

  function replace(path, options) {
    const { replacement, scope, binding } = options;

    // Same name, different binding.
    if (scope.getBinding(path.node.name) !== binding) {
      return;
    }

    // Don't want to put functions in loops in stuff.
    if ((t.isClass(replacement) || t.isFunction(replacement))
        && scope !== path.scope) {
          return;
    }

    // Avoid recursion.
    if (path.find(({ node }) => node === replacement)) {
      return;
    }

    t.toExpression(replacement);

    // This changes `function.name` but all the other minifier
    // do it :/
    if (t.isFunction(replacement)) {
      replacement.id = null;
    }

    path.replaceWith(replacement);
    return true;
  }

  function updateReferences(fnToDeletePath) {
    if (!fnToDeletePath.isFunction()) {
      return;
    }

    fnToDeletePath.traverse({
      ReferencedIdentifier(path) {
        const { node, scope } = path;
        const binding = scope.getBinding(node.name);

        if (!binding || !binding.path.isFunction() || binding.scope === scope || !binding.constant) {
          return;
        }

        let index = binding.referencePaths.indexOf(path);
        if (index === -1) {
          return;
        }
        binding.references--;
        binding.referencePaths.splice(index, 1);
        if (binding.references === 0) {
          binding.referenced = false;
        }

        if (binding.references <= 1) {
          binding.scope.path.node[shouldRevisit] = true;
        }
      },
    });
  }
};
<commit_msg>we don't really care about completion records<commit_after>'use strict';

const some = require('lodash.some');

module.exports = ({ Plugin, types: t }) => {
  const shouldRevisit = Symbol('shouldRevisit');

  const main = {
    // remove side effectless statement
    ExpressionStatement(path) {
      if (path.get('expression').isPure()) {
        if (!path.parentPath.isBlockStatement()) {
          path.replaceWith(t.emptyStatement());
        } else {
          path.remove();
        }
      }
    },

    Function: {

      // Let's take all the vars in a function that are not in the top level scope and hoist them
      // with the first var declaration in the top-level scope. This transform in itself may
      // not yield much returns (or even can be marginally harmful to size). However it's great
      // for taking away statements from blocks that can be only expressions which the `simplify`
      // plugin can turn into other things (e.g. if => conditional).
      exit(path) {
        const { node, scope } = path;
        const seen = new Set();
        const declars = [];
        for (let name in scope.bindings) {
          let binding = scope.bindings[name];
          if (!binding.path.isVariableDeclarator()) {
            continue;
          }

          const declarPath = binding.path.parentPath;
          if (seen.has(declarPath)) {
            continue;
          }
          seen.add(declarPath);

          if (declarPath.parentPath.isForInStatement()) {
            continue;
          }

          if (declarPath.parentPath.parentPath.isFunction()) {
            continue;
          }

          if (!declarPath.node || !declarPath.node.declarations) {
            continue;
          }

          const assignmentSequence = [];
          for (let declar of declarPath.node.declarations) {
            declars.push(declar);
            if (declar.init) {
              assignmentSequence.push(t.assignmentExpression('=', declar.id, declar.init));
              declar.init = null;
            }
          }

          if (assignmentSequence.length) {
            declarPath.replaceWith(t.sequenceExpression(assignmentSequence));
          } else {
            declarPath.remove();
          }
        }

        if (declars.length) {
          for (let statement of node.body.body) {
            if (t.isVariableDeclaration(statement)) {
              statement.declarations.push(...declars);
              return;
            }
          }
          const varDecl = t.variableDeclaration('var', declars);
          node.body.body.unshift(varDecl);
        }
      },
    },

    // Remove bindings with no references.
    Scope: {
      exit(path) {
        if (path.node[shouldRevisit]) {
          delete path.node[shouldRevisit];
          path.visit();
        }
      },

      enter(path) {
        if (path.isProgram()) {
          return;
        }

        const { scope } = path;
        for (let name in scope.bindings) {
          let binding = scope.bindings[name];
          if (!binding.referenced && binding.kind !== 'param' && binding.kind !== 'module') {
            if (binding.path.isVariableDeclarator()) {
              if (binding.path.parentPath.parentPath &&
                binding.path.parentPath.parentPath.isForInStatement()
              ) {
                // Can't remove if in a for in statement `for (var x in wat)`.
                continue;
              }
            } else if (!scope.isPure(binding.path.node)) {
              continue;
            } else if (binding.path.isFunctionExpression()) {
              // `bar(function foo() {})` foo is not referenced but it's used.
              continue;
            }

            const mutations = [];
            let bail = false;
            // Make sure none of the assignments value is used
            binding.constantViolations.forEach(p => {
              if (bail || p === binding.path) {
                return;
              }

              if (!p.parentPath.isExpressionStatement()) {
                bail = true;
              }

              if (p.isAssignmentExpression() && !p.get('right').isPure()) {
                mutations.push(() => p.replaceWith(p.get('right')));
              } else {
                mutations.push(() => p.remove());
              }
            });

            if (bail) {
              continue;
            }

            if (binding.path.isVariableDeclarator() && binding.path.node.init &&
              !scope.isPure(binding.path.node.init)
            ) {
              if (binding.path.parentPath.node.declarations.length !== 1) {
                continue;
              }

              binding.path.parentPath.replaceWith(binding.path.node.init);
            } else {
              updateReferences(binding.path, this);
              binding.path.remove();
            }

            mutations.forEach(f => f());
            scope.removeBinding(name);
          } else if (binding.constant) {
            if (binding.path.isFunctionDeclaration() ||
                (binding.path.isVariableDeclarator() && binding.path.get('init').isFunction())) {
                  const fun = binding.path.isFunctionDeclaration() ? binding.path : binding.path.get('init');
                  let allInside = true;
                  for (let ref of binding.referencePaths) {
                    if (!ref.find(p => p.node === fun.node)) {
                      allInside = false;
                      break;
                    }
                  }

                  if (allInside) {
                    scope.removeBinding(name);
                    updateReferences(binding.path, this);
                    binding.path.remove();
                    continue;
                  }
            }

            if (binding.references === 1 && binding.kind !== 'param' && binding.kind !== 'module' && binding.constant) {
              let replacement = binding.path.node;
              if (t.isVariableDeclarator(replacement)) {
                replacement = replacement.init;
              }
              if (!replacement) {
                continue;
              }

              if (!scope.isPure(replacement, true)) {
                continue;
              }

              if (binding.referencePaths.length > 1) {
                throw new Error('Expected only one reference');
              }

              let parent = binding.path.parent;
              if (t.isVariableDeclaration(parent)) {
                parent = binding.path.parentPath.parent;
              }

              const refPath = binding.referencePaths[0];

              // 1. Make sure we share the parent with the node. In other words it's lexically defined
              // and not in an if statement or otherwise.
              // 2. If the replacement is an object then we have to make sure we are not in a loop or a function
              // because otherwise we'll be inlining and doing a lot more allocation than we have to
              // which would also could affect correctness in that they are not the same reference.
              let mayLoop = false;
              const sharesRoot = refPath.find(({ node }) => {
                if (!mayLoop) {
                  mayLoop = t.isWhileStatement(node) || t.isFor(node) || t.isFunction(node);
                }
                return node === parent;
              });

              // Anything that inherits from Object.
              const isObj = (n) => t.isFunction(n) || t.isObjectExpression(n) || t.isArrayExpression(n);
              const isReplacementObj = isObj(replacement) || some(replacement, isObj);

              if (!sharesRoot || (isReplacementObj && mayLoop)) {
                continue;
              }

              const replaced = replace(binding.referencePaths[0], {
                binding,
                scope,
                replacement,
              });

              if (replaced) {
                scope.removeBinding(name);
                if (binding.path.node) {
                  binding.path.remove();
                }
              }
            }
          }
        }
      },
    },

    // Remove unreachable code.
    BlockStatement(path) {
      const paths = path.get('body');

      let purge = false;

      for (let i = 0; i < paths.length; i++) {
        const p = paths[i];

        if (!purge && p.isCompletionStatement()) {
          purge = true;
          continue;
        }

        if (purge && !p.isFunctionDeclaration()) {
          p.remove();
        }
      }
    },

    // Double check unreachable code and remove return statements that
    // have no semantic meaning
    ReturnStatement(path) {
      const { node } = path;
      if (!path.inList) {
        return;
      }

      // Not last in it's block? (See BlockStatement visitor)
      if (path.container.length - 1 !== path.key &&
          !path.getSibling(path.key + 1).isFunctionDeclaration() &&
          path.parentPath.isBlockStatement()
      ) {
        // This is probably a new oppurtinity by some other transform
        // let's call the block visitor on this again before proceeding.
        path.parentPath.pushContext(path.context);
        path.parentPath.visit();
        path.parentPath.popContext();

        return;
      }

      if (node.argument) {
        return;
      }

      let noNext = true;
      let parentPath = path.parentPath;
      while (parentPath && !parentPath.isFunction() && noNext) {
        const nextPath = parentPath.getSibling(parentPath.key + 1);
        if (nextPath.node) {
          if (nextPath.isReturnStatement()) {
            nextPath.pushContext(path.context);
            nextPath.visit();
            nextPath.popContext();
            if (parentPath.getSibling(parentPath.key + 1).node) {
              noNext = false;
              break;
            }
          } else {
            noNext = false;
            break;
          }
        }

        parentPath = parentPath.parentPath;
      }

      if (noNext) {
        path.remove();
      }
    },

    ConditionalExpression(path) {
      const { node } = path;
      const evaluateTest = path.get('test').evaluateTruthy();
      if (evaluateTest === true) {
        path.replaceWith(node.consequent);
      } else if (evaluateTest === false) {
        path.replaceWith(node.alternate);
      }
    },

    IfStatement: {
      exit(path) {
        const { node } = path;
        let { consequent, alternate, test } = node;

        const evaluateTest = path.get('test').evaluateTruthy();

        // we can check if a test will be truthy 100% and if so then we can inline
        // the consequent and completely ignore the alternate
        //
        //   if (true) { foo; } -> { foo; }
        //   if ("foo") { foo; } -> { foo; }
        //
        if (evaluateTest === true) {
          path.replaceWithMultiple(toStatements(consequent));
          return;
        }

        // we can check if a test will be falsy 100% and if so we can inline the
        // alternate if there is one and completely remove the consequent
        //
        //   if ("") { bar; } else { foo; } -> { foo; }
        //   if ("") { bar; } ->
        //
        if (evaluateTest === false) {
          if (alternate) {
            path.replaceWithMultiple(toStatements(alternate));
            return;
          } else {
            path.remove();
          }
        }

        // remove alternate blocks that are empty
        //
        //   if (foo) { foo; } else {} -> if (foo) { foo; }
        //
        if (t.isBlockStatement(alternate) && !alternate.body.length) {
          alternate = node.alternate = null;
        }

        // if the consequent block is empty turn alternate blocks into a consequent
        // and flip the test
        //
        //   if (foo) {} else { bar; } -> if (!foo) { bar; }
        //

        if (t.isBlockStatement(consequent) && !consequent.body.length &&
            t.isBlockStatement(alternate) && alternate.body.length
        ) {
          node.consequent = node.alternate;
          node.alternate = null;
          node.test = t.unaryExpression('!', test, true);
        }
      },
    },

    // Join assignment and definition when in sequence.
    // var x; x = 1; -> var x = 1;
    AssignmentExpression(path) {
      if (!path.get('left').isIdentifier() ||
          !path.parentPath.isExpressionStatement()
      ) {
        return;
      }

      const prev = path.parentPath.getSibling(path.parentPath.key - 1);
      if (!(prev && prev.isVariableDeclaration())) {
        return;
      }

      const declars = prev.node.declarations;
      if (declars.length !== 1 || declars[0].init ||
          declars[0].id.name !== path.get('left').node.name
      ) {
        return;
      }
      declars[0].init = path.node.right;
      path.remove();
    },

    // Remove named function expression name. While this is dangerous as it changes
    // `function.name` all minifiers do it and hence became a standard.
    FunctionExpression(path) {
      const id = path.get('id').node;
      if (!id) {
        return;
      }

      const { node, scope } = path;

      const binding = scope.getBinding(id.name);

      // Check if shadowed or is not referenced.
      if (binding.path.node !== node || !binding.referenced) {
        node.id = null;
      }
    },

    // Put the `var` in the left if feasible.
    ForInStatement(path) {
      const left = path.get('left');
      if (!left.isIdentifier()) {
        return;
      }

      const binding = path.scope.getBinding(left.node.name);
      if (binding.scope.getFunctionParent() !== path.scope.getFunctionParent()) {
        return;
      }

      if (!binding.path.isVariableDeclarator()) {
        return;
      }

      // If it has company then it's probably more efficient to keep.
      if (binding.path.parent.declarations.length > 1) {
        return;
      }

      // meh
      if (binding.path.node.init) {
        return;
      }

      binding.path.remove();
      path.node.left = t.variableDeclaration('var', [t.variableDeclarator(left.node)]);
    },
  };

  return {
    visitor: {
      Program(path) {
        // We need to run this plugin in isolation.
        path.traverse(main, { functionToBindings: new Map() });
      },
    },
  };

  function toStatements(node) {
    if (t.isBlockStatement(node)) {
      let hasBlockScoped = false;

      for (let i = 0; i < node.body.length; i++) {
        let bodyNode = node.body[i];
        if (t.isBlockScoped(bodyNode)) {
          hasBlockScoped = true;
        }
      }

      if (!hasBlockScoped) {
        return node.body;
      }
    }
    return node;
  }

  function replace(path, options) {
    const { replacement, scope, binding } = options;

    // Same name, different binding.
    if (scope.getBinding(path.node.name) !== binding) {
      return;
    }

    // Don't want to put functions in loops in stuff.
    if ((t.isClass(replacement) || t.isFunction(replacement))
        && scope !== path.scope) {
          return;
    }

    // Avoid recursion.
    if (path.find(({ node }) => node === replacement)) {
      return;
    }

    t.toExpression(replacement);

    // This changes `function.name` but all the other minifier
    // do it :/
    if (t.isFunction(replacement)) {
      replacement.id = null;
    }

    path.replaceWith(replacement);
    return true;
  }

  function updateReferences(fnToDeletePath) {
    if (!fnToDeletePath.isFunction()) {
      return;
    }

    fnToDeletePath.traverse({
      ReferencedIdentifier(path) {
        const { node, scope } = path;
        const binding = scope.getBinding(node.name);

        if (!binding || !binding.path.isFunction() || binding.scope === scope || !binding.constant) {
          return;
        }

        let index = binding.referencePaths.indexOf(path);
        if (index === -1) {
          return;
        }
        binding.references--;
        binding.referencePaths.splice(index, 1);
        if (binding.references === 0) {
          binding.referenced = false;
        }

        if (binding.references <= 1) {
          binding.scope.path.node[shouldRevisit] = true;
        }
      },
    });
  }
};
<|endoftext|>


<commit_before>/**
 * @module Structure
 * @submodule Structure
 * @for p5
 * @requires constants
 */

'use strict';

require('./shim');

// Core needs the PVariables object
var constants = require('./constants');

/**
 * This is the p5 instance constructor.
 *
 * A p5 instance holds all the properties and methods related to
 * a p5 sketch.  It expects an incoming sketch closure and it can also
 * take an optional node parameter for attaching the generated p5 canvas
 * to a node.  The sketch closure takes the newly created p5 instance as
 * its sole argument and may optionally set preload(), setup(), and/or
 * draw() properties on it for running a sketch.
 *
 * A p5 sketch can run in "global" or "instance" mode:
 * "global"   - all properties and methods are attached to the window
 * "instance" - all properties and methods are bound to this p5 object
 *
 * @param  {Function}    sketch a closure that can set optional preload(),
 *                              setup(), and/or draw() properties on the
 *                              given p5 instance
 * @param  {HTMLElement|boolean} node element to attach canvas to, if a
 *                                    boolean is passed in use it as sync
 * @param  {boolean}     [sync] start synchronously (optional)
 * @return {p5}                 a p5 instance
 */
var p5 = function(sketch, node, sync) {

  if (arguments.length === 2 && typeof node === 'boolean') {
    sync = node;
    node = undefined;
  }

  //////////////////////////////////////////////
  // PUBLIC p5 PROPERTIES AND METHODS
  //////////////////////////////////////////////

  /**
   * Called directly before setup(), the preload() function is used to handle
   * asynchronous loading of external files. If a preload function is
   * defined, setup() will wait until any load calls within have finished.
   * Nothing besides load calls should be inside preload (loadImage,
   * loadJSON, loadFont, loadStrings, etc).
   *
   * @method preload
   * @example
   *

   * var img;
   * var c;
   * function preload() {  // preload() runs once
   *   img = loadImage('assets/laDefense.jpg');
   * }
   *
   * function setup() {  // setup() waits until preload() is done
   *   img.loadPixels();
   *   // get color of middle pixel
   *   c = img.get(img.width/2, img.height/2);
   * }
   *
   * function draw() {
   *   background(c);
   *   image(img, 25, 25, 50, 50);
   * }
   *

   */

  /**
   * The setup() function is called once when the program starts. It's used to
   * define initial environment properties such as screen size and background
   * color and to load media such as images and fonts as the program starts.
   * There can only be one setup() function for each program and it shouldn't
   * be called again after its initial execution. Note: Variables declared
   * within setup() are not accessible within other functions, including
   * draw().
   *
   * @method setup
   * @example
   *

   * var a = 0;
   *
   * function setup() {
   *   background(0);
   *   noStroke();
   *   fill(102);
   * }
   *
   * function draw() {
   *   rect(a++%width, 10, 2, 80);
   * }
   *

   */

  /**
   * Called directly after setup(), the draw() function continuously executes
   * the lines of code contained inside its block until the program is stopped
   * or noLoop() is called. draw() is called automatically and should never be
   * called explicitly.
   *
   * It should always be controlled with noLoop(), redraw() and loop(). After
   * noLoop() stops the code in draw() from executing, redraw() causes the
   * code inside draw() to execute once, and loop() will cause the code
   * inside draw() to resume executing continuously.
   *
   * The number of times draw() executes in each second may be controlled with
   * the frameRate() function.
   *
   * There can only be one draw() function for each sketch, and draw() must
   * exist if you want the code to run continuously, or to process events such
   * as mousePressed(). Sometimes, you might have an empty call to draw() in
   * your program, as shown in the above example.
   *
   * @method draw
   * @example
   *

   * var yPos = 0;
   * function setup() {  // setup() runs once
   *   frameRate(30);
   * }
   * function draw() {  // draw() loops forever, until stopped
   *   background(204);
   *   yPos = yPos - 1;
   *   if (yPos < 0) {
   *     yPos = height;
   *   }
   *   line(0, yPos, width, yPos);
   * }
   *

   */

  //////////////////////////////////////////////
  // PRIVATE p5 PROPERTIES AND METHODS
  //////////////////////////////////////////////

  this._setupDone = false;
  this.pixelDensity = window.devicePixelRatio || 1; // for handling hidpi
  this._userNode = node;
  this._curElement = null;
  this._elements = [];
  this._preloadCount = 0;
  this._updateInterval = 0;
  this._isGlobal = false;
  this._loop = true;
  this._styles = [];
  this._defaultCanvasSize = {
    width: 100,
    height: 100
  };
  this._events = { // keep track of user-events for unregistering later
    'mousemove': null,
    'mousedown': null,
    'mouseup': null,
    'click': null,
    'mouseover': null,
    'mouseout': null,
    'keydown': null,
    'keyup': null,
    'keypress': null,
    'touchstart': null,
    'touchmove': null,
    'touchend': null,
    'resize': null,
    'blur': null
  };

  if (window.DeviceOrientationEvent) {
    this._events.deviceorientation = null;
  } else if (window.DeviceMotionEvent) {
    this._events.devicemotion = null;
  } else {
    this._events.MozOrientation = null;
  }

  //FF doesn't recognize mousewheel as of FF3.x
  if (/Firefox/i.test(navigator.userAgent)) {
    this._events.DOMMouseScroll = null;
  } else {
    this._events.mousewheel = null;
  }

  this._loadingScreenId = 'p5_loading';

  this._start = function () {
    // Find node if id given
    if (this._userNode) {
      if (typeof this._userNode === 'string') {
        this._userNode = document.getElementById(this._userNode);
      }
    }

    // Always create a default canvas.
    // Later on if the user calls createCanvas, this default one
    // will be replaced
    this.createCanvas(
      this._defaultCanvasSize.width,
      this._defaultCanvasSize.height,
      'p2d',
      true
    );

    var userPreload = this.preload || window.preload; // look for "preload"
    var context = this._isGlobal ? window : this;
    if (userPreload) {

      // Setup loading screen
      // Set loading scfeen into dom if not present
      // Otherwise displays and removes user provided loading screen
      var loadingScreen = document.getElementById(this._loadingScreenId);
      if(!loadingScreen){
        loadingScreen = document.createElement('div');
        loadingScreen.innerHTML = 'Loading...';
        loadingScreen.style.position = 'absolute';
        loadingScreen.id = this._loadingScreenId;
        var node = this._userNode || document.body;
        node.appendChild(loadingScreen);
      }

      var methods = this._preloadMethods;
      Object.keys(methods).forEach(function(f) {
        var methodContext = methods[f];
        var originalFunc = methodContext[f];
        methodContext[f] = function() {
          var argsArray = Array.prototype.slice.call(arguments);
          return context._preload(originalFunc, methodContext, argsArray);
        };
      });

      userPreload();
    } else {
      this._setup();
      this._runFrames();
      this._draw();
    }
  }.bind(this);

  this._preload = function (func, obj, args) {
    var context = this._isGlobal ? window : this;
    context._setProperty('_preloadCount', context._preloadCount + 1);
    var preloadCallback = function (resp) {
      context._setProperty('_preloadCount', context._preloadCount - 1);
      if (context._preloadCount === 0) {
        var loadingScreen = document.getElementById(context._loadingScreenId);
        if (loadingScreen) {
          loadingScreen.parentNode.removeChild(loadingScreen);
        }
        context._setup();
        context._runFrames();
        context._draw();
      }
    };
    args.push(preloadCallback);
    return func.apply(obj, args);
  }.bind(this);

  this._setup = function() {

    // return preload functions to their normal vals if switched by preload
    var context = this._isGlobal ? window : this;
    if (typeof context.preload === 'function') {
      for (var f in this._preloadMethods) {
        //var o = this._preloadMethods[f];
        context[f] = this._preloadMethods[f][f];
      }
    }

    // Short-circuit on this, in case someone used the library in "global"
    // mode earlier
    if (typeof context.setup === 'function') {
      context.setup();
    }

    // // unhide hidden canvas that was created
    // this.canvas.style.visibility = '';
    // this.canvas.className = this.canvas.className.replace('p5_hidden', '');

    // unhide any hidden canvases that were created
    var reg = new RegExp(/(^|\s)p5_hidden(?!\S)/g);
    var canvases = document.getElementsByClassName('p5_hidden');
    for (var i = 0; i < canvases.length; i++) {
      var k = canvases[i];
      k.style.visibility = '';
      k.className = k.className.replace(reg, '');
    }
    this._setupDone = true;
  }.bind(this);

  this._draw = function () {
    var now = window.performance.now();
    var time_since_last = now - this._lastFrameTime;
    var target_time_between_frames = 1000 / this._targetFrameRate;

    // only draw if we really need to; don't overextend the browser.
    // draw if we're within 5ms of when our next frame should paint
    // (this will prevent us from giving up opportunities to draw
    // again when it's really about time for us to do so). fixes an
    // issue where the frameRate is too low if our refresh loop isn't
    // in sync with the browser. note that we have to draw once even
    // if looping is off, so we bypass the time delay if that
    // is the case.
    var epsilon = 5;
    if (!this.loop ||
        time_since_last >= target_time_between_frames - epsilon) {
      this._setProperty('frameCount', this.frameCount + 1);
      this.redraw();
      this._updatePAccelerations();
      this._updatePMouseCoords();
      this._updatePTouchCoords();
      this._frameRate = 1000.0/(now - this._lastFrameTime);
      this._lastFrameTime = now;
    }

    //mandatory update values for 3d
    if(this._graphics.isP3D){
      this._graphics._update();
    }

    // get notified the next time the browser gives us
    // an opportunity to draw.
    if (this._loop) {
      window.requestAnimationFrame(this._draw);
    }
  }.bind(this);

  this._runFrames = function() {
    if (this._updateInterval) {
      clearInterval(this._updateInterval);
    }
  }.bind(this);

  this._setProperty = function(prop, value) {
    this[prop] = value;
    if (this._isGlobal) {
      window[prop] = value;
    }
  }.bind(this);

  /**
   * Removes the entire p5 sketch. This will remove the canvas and any
   * elements created by p5.js. It will also stop the draw loop and unbind
   * any properties or methods from the window global scope. It will
   * leave a variable p5 in case you wanted to create a new p5 sketch.
   * If you like, you can set p5 = null to erase it.
   * @method remove
   * @example
   *

   * function draw() {
   *   ellipse(50, 50, 10, 10);
   * }
   *
   * function mousePressed() {
   *   remove(); // remove whole sketch on mouse press
   * }
   *

   */
  this.remove = function() {
    if (this._curElement) {

      // stop draw
      this._loop = false;
      if (this._updateInterval) {
        clearTimeout(this._updateInterval);
      }

      // unregister events sketch-wide
      for (var ev in this._events) {
        window.removeEventListener(ev, this._events[ev]);
      }

      // remove DOM elements created by p5, and listeners
      for (var i=0; i<this._elements.length; i++) {
        var e = this._elements[i];
        if (e.elt.parentNode) {
          e.elt.parentNode.removeChild(e.elt);
        }
        for (var elt_ev in e._events) {
          e.elt.removeEventListener(elt_ev, e._events[elt_ev]);
        }
      }

      // call any registered remove functions
      var self = this;
      this._registeredMethods.remove.forEach(function (f) {
        if (typeof(f) !== 'undefined') {
          f.call(self);
        }
      });

      // remove window bound properties and methods
      if (this._isGlobal) {
        for (var p in p5.prototype) {
          try {
            delete window[p];
          } catch (x) {
            window[p] = undefined;
          }
        }
        for (var p2 in this) {
          if (this.hasOwnProperty(p2)) {
            try {
              delete window[p2];
            } catch (x) {
              window[p2] = undefined;
            }
          }
        }
      }
    }
    // window.p5 = undefined;
  }.bind(this);

  // attach constants to p5 instance
  for (var k in constants) {
    p5.prototype[k] = constants[k];
  }

  // If the user has created a global setup or draw function,
  // assume "global" mode and make everything global (i.e. on the window)
  if (!sketch) {
    this._isGlobal = true;
    // Loop through methods on the prototype and attach them to the window
    for (var p in p5.prototype) {
      if(typeof p5.prototype[p] === 'function') {
        var ev = p.substring(2);
        if (!this._events.hasOwnProperty(ev)) {
          window[p] = p5.prototype[p].bind(this);
        }
      } else {
        window[p] = p5.prototype[p];
      }
    }
    // Attach its properties to the window
    for (var p2 in this) {
      if (this.hasOwnProperty(p2)) {
        window[p2] = this[p2];
      }
    }

  } else {
    // Else, the user has passed in a sketch closure that may set
    // user-provided 'setup', 'draw', etc. properties on this instance of p5
    sketch(this);
  }

  // Bind events to window (not using container div bc key events don't work)

  for (var e in this._events) {
    var f = this['_on'+e];
    if (f) {
      var m = f.bind(this);
      window.addEventListener(e, m);
      this._events[e] = m;
    }
  }

  var self = this;
  window.addEventListener('focus', function() {
    self._setProperty('focused', true);
  });

  window.addEventListener('blur', function() {
    self._setProperty('focused', false);
  });

  // TODO: ???

  if (sync) {
    this._start();
  } else {
    if (document.readyState === 'complete') {
      this._start();
    } else {
      window.addEventListener('load', this._start.bind(this), false);
    }
  }
};

// functions that cause preload to wait
// more can be added by using registerPreloadMethod(func)
p5.prototype._preloadMethods = {
  loadJSON: p5.prototype,
  loadImage: p5.prototype,
  loadStrings: p5.prototype,
  loadXML: p5.prototype,
  loadShape: p5.prototype,
  loadTable: p5.prototype,
  loadFont: p5.prototype
};

p5.prototype._registeredMethods = { pre: [], post: [], remove: [] };

p5.prototype.registerPreloadMethod = function(f, o) {
  o = o || p5;
  if (!p5.prototype._preloadMethods.hasOwnProperty(f)) {
    p5.prototype._preloadMethods[f] = o;
  }
};

p5.prototype.registerMethod = function(name, m) {
  if (!p5.prototype._registeredMethods.hasOwnProperty(name)) {
    p5.prototype._registeredMethods[name] = [];
  }
  p5.prototype._registeredMethods[name].push(m);
};

module.exports = p5;
<commit_msg>recover core.js<commit_after>/**
 * @module Structure
 * @submodule Structure
 * @for p5
 * @requires constants
 */

'use strict';

require('./shim');

// Core needs the PVariables object
var constants = require('./constants');

/**
 * This is the p5 instance constructor.
 *
 * A p5 instance holds all the properties and methods related to
 * a p5 sketch.  It expects an incoming sketch closure and it can also
 * take an optional node parameter for attaching the generated p5 canvas
 * to a node.  The sketch closure takes the newly created p5 instance as
 * its sole argument and may optionally set preload(), setup(), and/or
 * draw() properties on it for running a sketch.
 *
 * A p5 sketch can run in "global" or "instance" mode:
 * "global"   - all properties and methods are attached to the window
 * "instance" - all properties and methods are bound to this p5 object
 *
 * @param  {Function}    sketch a closure that can set optional preload(),
 *                              setup(), and/or draw() properties on the
 *                              given p5 instance
 * @param  {HTMLElement|boolean} node element to attach canvas to, if a
 *                                    boolean is passed in use it as sync
 * @param  {boolean}     [sync] start synchronously (optional)
 * @return {p5}                 a p5 instance
 */
var p5 = function(sketch, node, sync) {

  if (arguments.length === 2 && typeof node === 'boolean') {
    sync = node;
    node = undefined;
  }

  //////////////////////////////////////////////
  // PUBLIC p5 PROPERTIES AND METHODS
  //////////////////////////////////////////////

  /**
   * Called directly before setup(), the preload() function is used to handle
   * asynchronous loading of external files. If a preload function is
   * defined, setup() will wait until any load calls within have finished.
   * Nothing besides load calls should be inside preload (loadImage,
   * loadJSON, loadFont, loadStrings, etc).
   *
   * @method preload
   * @example
   *

   * var img;
   * var c;
   * function preload() {  // preload() runs once
   *   img = loadImage('assets/laDefense.jpg');
   * }
   *
   * function setup() {  // setup() waits until preload() is done
   *   img.loadPixels();
   *   // get color of middle pixel
   *   c = img.get(img.width/2, img.height/2);
   * }
   *
   * function draw() {
   *   background(c);
   *   image(img, 25, 25, 50, 50);
   * }
   *

   */

  /**
   * The setup() function is called once when the program starts. It's used to
   * define initial environment properties such as screen size and background
   * color and to load media such as images and fonts as the program starts.
   * There can only be one setup() function for each program and it shouldn't
   * be called again after its initial execution. Note: Variables declared
   * within setup() are not accessible within other functions, including
   * draw().
   *
   * @method setup
   * @example
   *

   * var a = 0;
   *
   * function setup() {
   *   background(0);
   *   noStroke();
   *   fill(102);
   * }
   *
   * function draw() {
   *   rect(a++%width, 10, 2, 80);
   * }
   *

   */

  /**
   * Called directly after setup(), the draw() function continuously executes
   * the lines of code contained inside its block until the program is stopped
   * or noLoop() is called. draw() is called automatically and should never be
   * called explicitly.
   *
   * It should always be controlled with noLoop(), redraw() and loop(). After
   * noLoop() stops the code in draw() from executing, redraw() causes the
   * code inside draw() to execute once, and loop() will cause the code
   * inside draw() to resume executing continuously.
   *
   * The number of times draw() executes in each second may be controlled with
   * the frameRate() function.
   *
   * There can only be one draw() function for each sketch, and draw() must
   * exist if you want the code to run continuously, or to process events such
   * as mousePressed(). Sometimes, you might have an empty call to draw() in
   * your program, as shown in the above example.
   *
   * @method draw
   * @example
   *

   * var yPos = 0;
   * function setup() {  // setup() runs once
   *   frameRate(30);
   * }
   * function draw() {  // draw() loops forever, until stopped
   *   background(204);
   *   yPos = yPos - 1;
   *   if (yPos < 0) {
   *     yPos = height;
   *   }
   *   line(0, yPos, width, yPos);
   * }
   *

   */

  //////////////////////////////////////////////
  // PRIVATE p5 PROPERTIES AND METHODS
  //////////////////////////////////////////////

  this._setupDone = false;
  this.pixelDensity = window.devicePixelRatio || 1; // for handling hidpi
  this._userNode = node;
  this._curElement = null;
  this._elements = [];
  this._preloadCount = 0;
  this._updateInterval = 0;
  this._isGlobal = false;
  this._loop = true;
  this._styles = [];
  this._defaultCanvasSize = {
    width: 100,
    height: 100
  };
  this._events = { // keep track of user-events for unregistering later
    'mousemove': null,
    'mousedown': null,
    'mouseup': null,
    'click': null,
    'mouseover': null,
    'mouseout': null,
    'keydown': null,
    'keyup': null,
    'keypress': null,
    'touchstart': null,
    'touchmove': null,
    'touchend': null,
    'resize': null,
    'blur': null
  };

  if (window.DeviceOrientationEvent) {
    this._events.deviceorientation = null;
  } else if (window.DeviceMotionEvent) {
    this._events.devicemotion = null;
  } else {
    this._events.MozOrientation = null;
  }

  //FF doesn't recognize mousewheel as of FF3.x
  if (/Firefox/i.test(navigator.userAgent)) {
    this._events.DOMMouseScroll = null;
  } else {
    this._events.mousewheel = null;
  }

  this._loadingScreenId = 'p5_loading';

  this._start = function () {
    // Find node if id given
    if (this._userNode) {
      if (typeof this._userNode === 'string') {
        this._userNode = document.getElementById(this._userNode);
      }
    }

    // Always create a default canvas.
    // Later on if the user calls createCanvas, this default one
    // will be replaced
    this.createCanvas(
      this._defaultCanvasSize.width,
      this._defaultCanvasSize.height,
      'p2d',
      true
    );

    var userPreload = this.preload || window.preload; // look for "preload"
    var context = this._isGlobal ? window : this;
    if (userPreload) {

      // Setup loading screen
      // Set loading scfeen into dom if not present
      // Otherwise displays and removes user provided loading screen
      var loadingScreen = document.getElementById(this._loadingScreenId);
      if(!loadingScreen){
        loadingScreen = document.createElement('div');
        loadingScreen.innerHTML = 'Loading...';
        loadingScreen.style.position = 'absolute';
        loadingScreen.id = this._loadingScreenId;
        var node = this._userNode || document.body;
        node.appendChild(loadingScreen);
      }

      var methods = this._preloadMethods;
      Object.keys(methods).forEach(function(f) {
        var methodContext = methods[f];
        var originalFunc = methodContext[f];
        methodContext[f] = function() {
          var argsArray = Array.prototype.slice.call(arguments);
          return context._preload(originalFunc, methodContext, argsArray);
        };
      });

      userPreload();
    } else {
      this._setup();
      this._runFrames();
      this._draw();
    }
  }.bind(this);

  this._preload = function (func, obj, args) {
    var context = this._isGlobal ? window : this;
    context._setProperty('_preloadCount', context._preloadCount + 1);
    var preloadCallback = function (resp) {
      context._setProperty('_preloadCount', context._preloadCount - 1);
      if (context._preloadCount === 0) {
        var loadingScreen = document.getElementById(context._loadingScreenId);
        if (loadingScreen) {
          loadingScreen.parentNode.removeChild(loadingScreen);
        }
        context._setup();
        context._runFrames();
        context._draw();
      }
    };
    args.push(preloadCallback);
    return func.apply(obj, args);
  }.bind(this);

  this._setup = function() {

    // return preload functions to their normal vals if switched by preload
    var context = this._isGlobal ? window : this;
    if (typeof context.preload === 'function') {
      for (var f in this._preloadMethods) {
        //var o = this._preloadMethods[f];
        context[f] = this._preloadMethods[f][f];
      }
    }

    // Short-circuit on this, in case someone used the library in "global"
    // mode earlier
    if (typeof context.setup === 'function') {
      context.setup();
    }

    // // unhide hidden canvas that was created
    // this.canvas.style.visibility = '';
    // this.canvas.className = this.canvas.className.replace('p5_hidden', '');

    // unhide any hidden canvases that were created
    var reg = new RegExp(/(^|\s)p5_hidden(?!\S)/g);
    var canvases = document.getElementsByClassName('p5_hidden');
    for (var i = 0; i < canvases.length; i++) {
      var k = canvases[i];
      k.style.visibility = '';
      k.className = k.className.replace(reg, '');
    }
    this._setupDone = true;

  }.bind(this);

  this._draw = function () {
    var now = window.performance.now();
    var time_since_last = now - this._lastFrameTime;
    var target_time_between_frames = 1000 / this._targetFrameRate;

    // only draw if we really need to; don't overextend the browser.
    // draw if we're within 5ms of when our next frame should paint
    // (this will prevent us from giving up opportunities to draw
    // again when it's really about time for us to do so). fixes an
    // issue where the frameRate is too low if our refresh loop isn't
    // in sync with the browser. note that we have to draw once even
    // if looping is off, so we bypass the time delay if that
    // is the case.
    var epsilon = 5;
    if (!this.loop ||
        time_since_last >= target_time_between_frames - epsilon) {
      this._setProperty('frameCount', this.frameCount + 1);
      this.redraw();
      this._updatePAccelerations();
      this._updatePMouseCoords();
      this._updatePTouchCoords();
      this._frameRate = 1000.0/(now - this._lastFrameTime);
      this._lastFrameTime = now;
    }

    // get notified the next time the browser gives us
    // an opportunity to draw.
    if (this._loop) {
      window.requestAnimationFrame(this._draw);
    }
  }.bind(this);

  this._runFrames = function() {
    if (this._updateInterval) {
      clearInterval(this._updateInterval);
    }
  }.bind(this);

  this._setProperty = function(prop, value) {
    this[prop] = value;
    if (this._isGlobal) {
      window[prop] = value;
    }
  }.bind(this);

  /**
   * Removes the entire p5 sketch. This will remove the canvas and any
   * elements created by p5.js. It will also stop the draw loop and unbind
   * any properties or methods from the window global scope. It will
   * leave a variable p5 in case you wanted to create a new p5 sketch.
   * If you like, you can set p5 = null to erase it.
   * @method remove
   * @example
   *

   * function draw() {
   *   ellipse(50, 50, 10, 10);
   * }
   *
   * function mousePressed() {
   *   remove(); // remove whole sketch on mouse press
   * }
   *

   */
  this.remove = function() {
    if (this._curElement) {

      // stop draw
      this._loop = false;
      if (this._updateInterval) {
        clearTimeout(this._updateInterval);
      }

      // unregister events sketch-wide
      for (var ev in this._events) {
        window.removeEventListener(ev, this._events[ev]);
      }

      // remove DOM elements created by p5, and listeners
      for (var i=0; i<this._elements.length; i++) {
        var e = this._elements[i];
        if (e.elt.parentNode) {
          e.elt.parentNode.removeChild(e.elt);
        }
        for (var elt_ev in e._events) {
          e.elt.removeEventListener(elt_ev, e._events[elt_ev]);
        }
      }

      // call any registered remove functions
      var self = this;
      this._registeredMethods.remove.forEach(function (f) {
        if (typeof(f) !== 'undefined') {
          f.call(self);
        }
      });

      // remove window bound properties and methods
      if (this._isGlobal) {
        for (var p in p5.prototype) {
          try {
            delete window[p];
          } catch (x) {
            window[p] = undefined;
          }
        }
        for (var p2 in this) {
          if (this.hasOwnProperty(p2)) {
            try {
              delete window[p2];
            } catch (x) {
              window[p2] = undefined;
            }
          }
        }
      }
    }
    // window.p5 = undefined;
  }.bind(this);

  // attach constants to p5 instance
  for (var k in constants) {
    p5.prototype[k] = constants[k];
  }

  // If the user has created a global setup or draw function,
  // assume "global" mode and make everything global (i.e. on the window)
  if (!sketch) {
    this._isGlobal = true;
    // Loop through methods on the prototype and attach them to the window
    for (var p in p5.prototype) {
      if(typeof p5.prototype[p] === 'function') {
        var ev = p.substring(2);
        if (!this._events.hasOwnProperty(ev)) {
          window[p] = p5.prototype[p].bind(this);
        }
      } else {
        window[p] = p5.prototype[p];
      }
    }
    // Attach its properties to the window
    for (var p2 in this) {
      if (this.hasOwnProperty(p2)) {
        window[p2] = this[p2];
      }
    }

  } else {
    // Else, the user has passed in a sketch closure that may set
    // user-provided 'setup', 'draw', etc. properties on this instance of p5
    sketch(this);
  }

  // Bind events to window (not using container div bc key events don't work)

  for (var e in this._events) {
    var f = this['_on'+e];
    if (f) {
      var m = f.bind(this);
      window.addEventListener(e, m);
      this._events[e] = m;
    }
  }

  var self = this;
  window.addEventListener('focus', function() {
    self._setProperty('focused', true);
  });

  window.addEventListener('blur', function() {
    self._setProperty('focused', false);
  });

  // TODO: ???

  if (sync) {
    this._start();
  } else {
    if (document.readyState === 'complete') {
      this._start();
    } else {
      window.addEventListener('load', this._start.bind(this), false);
    }
  }
};

// functions that cause preload to wait
// more can be added by using registerPreloadMethod(func)
p5.prototype._preloadMethods = {
  loadJSON: p5.prototype,
  loadImage: p5.prototype,
  loadStrings: p5.prototype,
  loadXML: p5.prototype,
  loadShape: p5.prototype,
  loadTable: p5.prototype,
  loadFont: p5.prototype
};

p5.prototype._registeredMethods = { pre: [], post: [], remove: [] };

p5.prototype.registerPreloadMethod = function(f, o) {
  o = o || p5;
  if (!p5.prototype._preloadMethods.hasOwnProperty(f)) {
    p5.prototype._preloadMethods[f] = o;
  }
};

p5.prototype.registerMethod = function(name, m) {
  if (!p5.prototype._registeredMethods.hasOwnProperty(name)) {
    p5.prototype._registeredMethods[name] = [];
  }
  p5.prototype._registeredMethods[name].push(m);
};

module.exports = p5;
<|endoftext|>


<commit_before>/**
	Linkify a HTML DOM node
*/

import {tokenize, options} from './linkify';

const HTML_NODE = 1, TXT_NODE = 3;

/**
	Given an array of MultiTokens, return an array of Nodes that are either
	(a) Plain Text nodes (node type 3)
	(b) Anchor tag nodes (usually, unless tag name is overriden in the options)

	Takes the same options as linkifyElement and an optional doc element (this should be passed in by linkifyElement)
*/
function tokensToNodes(tokens, opts, doc) {
	let result = [];

	for (let i = 0; i < tokens.length; i++) {
		let token = tokens[i];

		if (token.isLink) {

			let
			href			= token.toHref(opts.defaultProtocol),
			formatted		= options.resolve(opts.format, token.toString(), token.type),
			formattedHref	= options.resolve(opts.formatHref, href, token.type),
			attributesHash	= options.resolve(opts.attributes, href, token.type),
			tagName			= options.resolve(opts.tagName, href, token.type),
			linkClass		= options.resolve(opts.linkClass, href, token.type),
			target			= options.resolve(opts.target, href, token.type),
			events			= options.resolve(opts.events, href, token.type);

			// Build the link
			let link = doc.createElement(tagName);
			link.setAttribute('href', formattedHref);
			link.setAttribute('class', linkClass);
			if (target) {
				link.setAttribute('target', target);
			}

			// Build up additional attributes
			if (attributesHash) {
				for (let attr in attributesHash) {
					link.setAttribute(attr, attributesHash[attr]);
				}
			}

			if (events) {
				for (let event in events) {
					if (link.addEventListener) {
						link.addEventListener(event, events[event]);
					} else if (link.attachEvent)  {
						link.attachEvent('on' + event, events[event]);
					}
				}
			}

			link.appendChild(doc.createTextNode(formatted));
			result.push(link);

		} else if (token.type === 'nl' && opts.nl2br) {
			result.push(doc.createElement('br'));
		} else {
			result.push(doc.createTextNode(token.toString()));
		}
	}

	return result;
}

// Requires document.createElement
function linkifyElementHelper(element, opts, doc) {

	// Can the element be linkified?
	if (!element || typeof element !== 'object' || element.nodeType !== HTML_NODE) {
		throw new Error(`Cannot linkify ${element} - Invalid DOM Node type`);
	}

	// Is this element already a link?
	if (element.tagName.toLowerCase() === 'a' /*|| element.hasClass('linkified')*/) {
		// No need to linkify
		return element;
	}

	let
	children = [],
	childElement = element.firstChild;

	while (childElement) {

		switch (childElement.nodeType) {
		case HTML_NODE:
			children.push(linkifyElementHelper(childElement, opts, doc));
			break;
		case TXT_NODE:

			let
			str = childElement.nodeValue,
			tokens = tokenize(str);
			children.push(...tokensToNodes(tokens, opts, doc));

			break;

		default: children.push(childElement); break;
		}

		childElement = childElement.nextSibling;
	}

	// Clear out the element
	while (element.firstChild) {
		element.removeChild(element.firstChild);
	}

	// Replace with all the new nodes
	for (let i = 0; i < children.length; i++) {
		element.appendChild(children[i]);
	}

	return element;
}

function linkifyElement(element, opts, doc=null) {

	try {
		doc = doc || window && window.document || global && global.document;
	} catch (e) { /* do nothing for now */ }

	if (!doc) {
		throw new Error(
			'Cannot find document implementation. ' +
			'If you are in a non-browser environment like Node.js, ' +
			'pass the document implementation as the third argument to linkifyElement.'
		);
	}

	opts = options.normalize(opts);
	return linkifyElementHelper(element, opts, doc);
}

// Maintain reference to the recursive helper to save some option-normalization
// cycles
linkifyElement.helper = linkifyElementHelper;
linkifyElement.normalize = options.normalize;

export default linkifyElement;
<commit_msg>Initial refactor of linkify-element<commit_after>/**
	Linkify a HTML DOM node
*/

import {tokenize, options} from './linkify';

const HTML_NODE = 1, TXT_NODE = 3;

function replaceChildWithChildren(parent, oldChild, newChildren) {
	let lastNewChild = newChildren[newChildren.length - 1];
	parent.replaceChild(lastNewChild, oldChild);
	debugger;
	for (let i = newChildren.length - 2; i >= 0; i++) {
		parent.insertBefore(newChildren[i], lastNewChild);
	}
}

/**
	Given an array of MultiTokens, return an array of Nodes that are either
	(a) Plain Text nodes (node type 3)
	(b) Anchor tag nodes (usually, unless tag name is overriden in the options)

	Takes the same options as linkifyElement and an optional doc element (this should be passed in by linkifyElement)
*/
function tokensToNodes(tokens, opts, doc) {
	let result = [];

	for (let i = 0; i < tokens.length; i++) {
		let token = tokens[i];

		if (token.isLink) {

			let
			href			= token.toHref(opts.defaultProtocol),
			formatted		= options.resolve(opts.format, token.toString(), token.type),
			formattedHref	= options.resolve(opts.formatHref, href, token.type),
			attributesHash	= options.resolve(opts.attributes, href, token.type),
			tagName			= options.resolve(opts.tagName, href, token.type),
			linkClass		= options.resolve(opts.linkClass, href, token.type),
			target			= options.resolve(opts.target, href, token.type),
			events			= options.resolve(opts.events, href, token.type);

			// Build the link
			let link = doc.createElement(tagName);
			link.setAttribute('href', formattedHref);
			link.setAttribute('class', linkClass);
			if (target) {
				link.setAttribute('target', target);
			}

			// Build up additional attributes
			if (attributesHash) {
				for (let attr in attributesHash) {
					link.setAttribute(attr, attributesHash[attr]);
				}
			}

			if (events) {
				for (let event in events) {
					if (link.addEventListener) {
						link.addEventListener(event, events[event]);
					} else if (link.attachEvent)  {
						link.attachEvent('on' + event, events[event]);
					}
				}
			}

			link.appendChild(doc.createTextNode(formatted));
			result.push(link);

		} else if (token.type === 'nl' && opts.nl2br) {
			result.push(doc.createElement('br'));
		} else {
			result.push(doc.createTextNode(token.toString()));
		}
	}

	return result;
}

// Requires document.createElement
function linkifyElementHelper(element, opts, doc) {

	// Can the element be linkified?
	if (!element || typeof element !== 'object' || element.nodeType !== HTML_NODE) {
		throw new Error(`Cannot linkify ${element} - Invalid DOM Node type`);
	}

	// Is this element already a link?
	if (element.tagName.toLowerCase() === 'a' /*|| element.hasClass('linkified')*/) {
		// No need to linkify
		return element;
	}

	let
	children = [],
	childElement = element.firstChild;

	while (childElement) {

		switch (childElement.nodeType) {
		case HTML_NODE:
			children.push(linkifyElementHelper(childElement, opts, doc));
			break;
		case TXT_NODE:

			let
			str = childElement.nodeValue,
			tokens = tokenize(str),
			nodes = tokensToNodes(tokens, opts, doc);

			replaceChildWithChildren(element, childElement, nodes);

			break;

		default: children.push(childElement); break;
		}

		childElement = childElement.nextSibling;
	}

	return element;
}

function linkifyElement(element, opts, doc=null) {

	try {
		doc = doc || window && window.document || global && global.document;
	} catch (e) { /* do nothing for now */ }

	if (!doc) {
		throw new Error(
			'Cannot find document implementation. ' +
			'If you are in a non-browser environment like Node.js, ' +
			'pass the document implementation as the third argument to linkifyElement.'
		);
	}

	opts = options.normalize(opts);
	return linkifyElementHelper(element, opts, doc);
}

// Maintain reference to the recursive helper to save some option-normalization
// cycles
linkifyElement.helper = linkifyElementHelper;
linkifyElement.normalize = options.normalize;

export default linkifyElement;
<|endoftext|>


<commit_before>({
  showToast : function(title, message, type, duration, mode) {
    var type = (type ? type : "other");
    var duration = (duration ? parseInt(duration) : 5000);
    var mode = (mode ? mode : "dismissible");
    var toastEvent = $A.get("e.force:showToast");

    toastEvent.setParams({
      title: title,
      message: message,
      type: type,
      duration: duration,
      mode: mode
    });
    toastEvent.fire();
  },
})<commit_msg>Deprecate<commit_after>({
  doNothing : function() {

  },
})<|endoftext|>