text:
/**
 * @file embedo.js
 *
 * Embedo is third party content embed plugin with features having events and resizing.
 * It provides a layer above popular social media sites native embed snippets
 * making it easier to hook content without modifying much code.
 *
 * @author Shobhit Sharma <hi@shobh.it>
 * @license MIT
 */

'use strict';

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define(factory);
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory();
  } else if (root) {
    root.Embedo = window.Embedo = factory();
  }
})(this, function () {
  /**
   * @class Embedo Prototype
   *
   * @param {object} options Initialize options.
   */
  function Embedo(options) {
    this.options = options || Embedo.defaults.OPTIONS;
    this.requests = [];
    this.events = [];

    this.init(this.options);

    return this;
  }

  /**
   * @constant
   * Embedo defaults
   *
   * @description Embedo defaults contains basic configuration and values required to build internal engine.
   */
  Object.defineProperty(Embedo, 'defaults', {
    value: {
      OPTIONS: {
        facebook: null,
        twitter: false,
        instagram: false,
        pinterest: false
      },
      SOURCES: {
        facebook: {
          GLOBAL: 'FB',
          SDK: '//connect.facebook.net/${locale}/all.js',
          oEmbed: '//www.facebook.com/plugins/${type}/oembed.json',
          REGEX: /(?:(?:http|https):\/\/)?(?:www.)?facebook.com\/(?:(?:\w)*#!\/)?(?:pages\/)?([\w\-]*)?/g,
          PARAMS: {
            version: 'v3.2',
            cookie: true,
            appId: null,
            xfbml: true
          }
        },
        twitter: {
          GLOBAL: 'twttr',
          SDK: '//platform.twitter.com/widgets.js',
          oEmbed: '//publish.twitter.com/oembed',
          REGEX: /^http[s]*:\/\/[www.]*twitter(\.[a-z]+).*/i,
          PARAMS: {}
        },
        instagram: {
          GLOBAL: 'instgrm',
          SDK: '//www.instagram.com/embed.js',
          oEmbed: '//api.instagram.com/oembed',
          REGEX: /instagram.com\/p\/[a-zA-Z0-9_\/\?\-\=]+/gi,
          PARAMS: {}
        },
        youtube: {
          GLOBAL: null,
          SDK: null,
          oEmbed: '//www.youtube.com/embed/',
          REGEX: /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:m\.)?(?:youtu(?:be)?\.com\/(?:v\/|embed\/|watch(?:\/|\?v=))|youtu\.be\/)((?:\w|-){11})(?:\S+)?$/,
          PARAMS: null
        },
        pinterest: {
          GLOBAL: 'PinUtils',
          SDK: '//assets.pinterest.com/js/pinit.js',
          oEmbed: null,
          REGEX: /(https?:\/\/(ww.)?)?pinterest(\.[a-z]+).*/i,
          PARAMS: {}
        },
        vimeo: {
          GLOBAL: null,
          SDK: null,
          oEmbed: '//vimeo.com/api/oembed.json',
          REGEX: /(http|https)?:\/\/(www\.)?vimeo(\.[a-z]+)\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)/,
          PARAMS: {}
        },
        github: {
          GLOBAL: null,
          SDK: null,
          oEmbed: null,
          REGEX: /(http|https):\/\/gist\.github\.com\/(\w+)\/(\w+)/,
          PARAMS: {}
        },
        soundcloud: {
          GLOBAL: null,
          SDK: null,
          oEmbed: '//soundcloud.com/oembed',
          REGEX: /^(http|https):\/\/soundcloud\.com\/(\w+)\/.*$/,
          PARAMS: {}
        }
      },
      RESTRICTED: ['url', 'strict', 'height', 'width', 'centerize', 'jsonp']
    },
    writable: false,
    enumerable: true,
    configurable: false
  });

  // Logger
  Object.defineProperty(Embedo, 'log', {
    value: function log(type) {
      if (!Embedo.debug) {
        return;
      }
      if (typeof console !== 'undefined' && typeof console[type] !== 'undefined') {
        console[type].apply(console, Array.prototype.slice.call(arguments, 1));
      }
    },
    writable: false,
    enumerable: true,
    configurable: false
  });

  /**
   * Helper utlities
   * @method utils
   *
   * @private
   */
  Object.defineProperty(Embedo, 'utils', {
    value: Object.create({
      /**
       * @function uuid
       */
      uuid: function uuid() {
        var primary = (Math.random() * 0x10000) | 0;
        var secondary = (Math.random() * 0x10000) | 0;
        primary = ('000' + primary.toString(36)).slice(-3);
        secondary = ('000' + secondary.toString(36)).slice(-3);
        return 'embedo__' + primary + secondary;
      },

      /**
       * @function extend
       * @returns {object}
       */
      extend: function extend(obj) {
        obj = obj || {};
        for (var i = 1; i < arguments.length; i++) {
          if (!arguments[i]) {
            continue;
          }
          for (var key in arguments[i]) {
            if (arguments[i].hasOwnProperty(key)) {
              obj[key] = arguments[i][key];
            }
          }
        }
        return obj;
      },

      /**
       * @function merge
       *
       * @param {object} destination
       * @param {object} source
       * @param {array} preserve
       * @returns
       */
      merge: function merge(destination, source, preserve) {
        preserve = preserve || [];

        for (var property in source) {
          if (preserve.indexOf(property) === -1) {
            destination[property] = source[property];
          }
        }

        return destination;
      },

      /**
       * @func sequencer
       * Breaks down array into sequencer
       *
       * @param {Array} array
       * @param {Number} size
       * @returns
       */
      sequencer: function sequencer() {
        var args = arguments;
        return {
          then: function (done) {
            var counter = 0;
            for (var i = 0; i < args.length; i++) {
              args[i](callme);
            }

            function callme() {
              counter++;
              if (counter === args.length) {
                done();
              }
            }
          }
        };
      },

      /**
       * @func replacer
       * Replaces ${entity} with object key/value pair
       *
       * @param {string} str
       * @param {object} obj
       */
      replacer: function replacer(str, obj) {
        if (!str || !obj) {
          return;
        }
        if (obj) {
          for (var key in obj) {
            if (str) {
              str = str.split('${' + key + '}').join(obj[key]);
            }
          }
        }
        return str;
      },

      /**
       * @func observer
       *
       * Deferred Implementation for Object
       */
      observer: (function () {
        function Deferred() {
          this.resolved = [];
          this.rejected = [];
        }
        Deferred.prototype = {
          execute: function (list, args) {
            var i = list.length;
            args = Array.prototype.slice.call(args);
            while (i--) {
              list[i].apply(null, args);
            }
          },
          resolve: function () {
            this.execute(this.resolved, arguments);
          },
          reject: function () {
            this.execute(this.rejected, arguments);
          },
          done: function (callback) {
            this.resolved.push(callback);
            return this;
          },
          fail: function (callback) {
            this.rejected.push(callback);
            return this;
          }
        };
        return Deferred;
      })(),

      camelToSnake: function camelToSnake(str) {
        return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
      },

      /**
       * @function validateURL
       *
       * @param {string} url
       * @returns
       */
      validateURL: function validateURL(url) {
        return /(http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(
          url
        );
      },

      /**
       * @function generateElement
       * Generates DOM element
       *
       * @param {string} source
       * @param {object} attributes
       * @param {string} html
       * @returns HTMLElement
       */
      generateElement: function generateElement(type, attributes, html) {
        var el = document.createElement(type);
        Object.keys(attributes || {}).forEach(function (type) {
          el.setAttribute(type, attributes[type]);
        });
        if (html) {
          el.innerHTML = html;
        }
        return el;
      },

      /**
       * @function generateEmbed
       * Generates Embed Container
       *
       * @param {string} source
       * @param {string} html
       * @returns
       */
      generateEmbed: function generateEmbed(id, source, html) {
        id = id || Embedo.utils.uuid();
        var container = document.createElement('div');

        container.setAttribute('id', id);
        container.setAttribute('data-embedo-id', id);
        container.setAttribute('data-embedo-source', source);

        if (Embedo.utils.validateElement(html)) {
          container.appendChild(html);
        } else {
          container.innerHTML = html || '';
        }

        return container;
      },

      /**
       * @function generateScript
       * Generates script tag element
       *
       * @param {string} source
       * @returns HTMLElement
       */
      generateScript: function generateScript(source) {
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = encodeURI(source);
        script.setAttribute('async', '');
        script.setAttribute('charset', 'utf-8');
        return script;
      },

      /**
       * @function validateElement
       * Validates if passed argument is valid DOM element
       *
       * @param {object} obj
       * @returns HTMLElement
       */
      validateElement: function validateElement(obj) {
        return typeof HTMLElement === 'object' ?
          obj instanceof window.HTMLElement :
          obj &&
          typeof obj === 'object' &&
          obj !== null &&
          obj.nodeType === 1 &&
          typeof obj.nodeName === 'string';
      },

      /**
       * @function sdkReady
       * Checks when SDK global object is ready
       *
       * @param {string} type
       * @param {function} callback
       */
      sdkReady: function sdkReady(type, callback) {
        callback = callback || function () {};
        if (!Embedo.defaults.SOURCES[type]) {
          return callback(new Error('unsupported_sdk_type'));
        }
        var counter = 0;
        (function check() {
          counter++;
          if (counter > 15) {
            return callback(new Error(type + ':sdk_not_available'));
          }
          if (window[Embedo.defaults.SOURCES[type].GLOBAL]) {
            return callback(null, window[Embedo.defaults.SOURCES[type].GLOBAL]);
          }
          setTimeout(check, 10 * counter);
        })(type);
      },

      /**
       * @function querystring
       * Object to Query String
       *
       * @param {object} obj
       * @returns {string}
       */
      querystring: function querystring(obj) {
        var parts = [];

        for (var i in obj) {
          if (obj.hasOwnProperty(i)) {
            parts.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));
          }
        }

        return parts.join('&');
      },

      /**
       * @function fetch
       * JSONP XHR fetch
       *
       * @param {string} url
       * @param {object} options
       * @param {function} callback
       */
      fetch: function fetch(url, options, callback) {
        if (typeof options === 'function') {
          callback = options;
          options = {};
        }
        options = options || {};
        var target = document.head || document.getElementsByTagName('head')[0];
        var script = document.createElement('script');
        var jsonpCallback = 'fetch__' + Embedo.utils.uuid();
        url += (~url.indexOf('?') ? '&' : '?') + 'callback=' + encodeURIComponent(jsonpCallback);
        url = url.replace('?&', '?');

        window[jsonpCallback] = function (data) {
          clear(jsonpCallback, script);
          callback(null, data);
        };

        script.type = 'text/javascript';
        script.defer = true;
        script.charset = 'UTF-8';
        script.onerror = function (err) {
          clear(jsonpCallback, script);
          return callback(err);
        };
        target.appendChild(script);
        script.src = url;

        function clear(jsonpCallback, script) {
          try {
            delete window[jsonpCallback];
          } catch (e) {
            window[jsonpCallback] = undefined;
          }
          if (script) {
            target.removeChild(script);
            script = undefined;
          }
        }
      },

      /**
       * XHR HTTP Requests
       *
       * @param {string} url
       * @param {object} options
       * @param {Function} callback
       */
      ajax: function ajax(url, options, callback) {
        if (typeof options === 'function') {
          callback = options;
          options = {};
        }
        callback = callback || function () {};
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function () {
          if (xhr.readyState === XMLHttpRequest.DONE || xhr.readyState === 4) {
            if (xhr.status >= 400) {
              return callback(new Error(xhr.responseText || xhr.statusText));
            }
            try {
              return callback(null, JSON.parse(xhr.responseText));
            } catch (e) {
              return callback(new Error('invalid_response'));
            }
          }
        };
        xhr.onerror = function (err) {
          return callback(err);
        };
        xhr.open('GET', url);
        xhr.send();
      },

      /**
       * @function transform
       * Cross Browser CSS Transformation
       *
       * @param {HTMLElement} element
       * @param {string} props
       */
      transform: function transform(element, props) {
        if (!Embedo.utils.validateElement(element)) {
          return;
        }
        element.style.webkitTransform = props;
        element.style.MozTransform = props;
        element.style.msTransform = props;
        element.style.OTransform = props;
        element.style.transform = props;
      },

      /**
       * @function compute
       * Computes property value of HTMLElement
       *
       * @param {HTMLElement} el
       * @param {string} prop
       * @param {Boolean} stylesheet
       * @returns {Number}
       */
      compute: function compute(el, prop, is_computed) {
        if (!Embedo.utils.validateElement(el) || !prop) {
          return;
        }

        var bounds = el.getBoundingClientRect();
        var value = bounds[prop];

        if (is_computed || !value) {
          if (document.defaultView && document.defaultView.getComputedStyle) {
            value = document.defaultView.getComputedStyle(el, '').getPropertyValue(prop);
          } else if (el.currentStyle) {
            prop = prop.replace(/\-(\w)/g, function (m, p) {
              return p.toUpperCase();
            });
            value = el.currentStyle[prop];
          }
        }

        if (typeof value === 'string' && !/^\d+(\.\d+)?%$/.test(value)) {
          value = value.replace(/[^\d.-]/g, '');
        }

        return isNaN(Number(value)) ? value : Number(value);
      },

      /**
       * @method convertToPx
       * Calculates approximate pixel value for vw, vh or % values
       *
       * @implements relative_px
       * @implements percent_px
       */
      convertToPx: function convertToPx(el, prop, value) {
        if (!isNaN(Number(value))) {
          return Number(value);
        } else if (/^\d+(\.\d+)?%$/.test(value)) {
          return percent_px(el, prop, value);
        } else if (value.match(/(vh|vw)/)) {
          var dimension = value.replace(/[0-9]/g, '');
          return relative_px(dimension, value);
        }

        // Converts vw or vh to PX
        function relative_px(type, value) {
          var w = window,
            d = document,
            e = d.documentElement,
            g = d.body,
            x = w.innerWidth || e.clientWidth || g.clientWidth,
            y = w.innerHeight || e.clientHeight || g.clientHeight;

          if (type === 'vw') {
            return (x * parseFloat(value)) / 100;
          } else if (type === 'vh') {
            return (y * parseFloat(value)) / 100;
          } else {
            return undefined;
          }
        }

        // Converts % to PX
        function percent_px(el, prop, percent) {
          var parent_width = Embedo.utils.compute(el.parentNode, prop, true);
          percent = parseFloat(percent);
          return parent_width * (percent / 100);
        }
      },

      /**
       * @function watcher
       *
       * @param {string} Identifer
       * @param {Function} Function to Trigger
       * @param {integer} timer
       *
       * @returns {Function}
       */
      watcher: function watcher(id, fn, timer) {
        window.EMBEDO_WATCHER = window.EMBEDO_WATCHER || {};
        window.EMBEDO_WATCHER[id] = window.EMBEDO_WATCHER[id] || {
          id: id,
          count: 0,
          request: null
        };

        if (window.EMBEDO_WATCHER[id].count > 0 && window.EMBEDO_WATCHER[id].request) {
          window.EMBEDO_WATCHER[id].count -= 1;
          clearTimeout(window.EMBEDO_WATCHER[id].request);
        }

        window.EMBEDO_WATCHER[id].count += 1;
        window.EMBEDO_WATCHER[id].request = setTimeout(function () {
          window.EMBEDO_WATCHER[id].count -= 1;
          if (window.EMBEDO_WATCHER[id].count === 0) {
            fn.call();
          }
        }, timer);

        return null;
      },

      /**
       * @function dimensions
       *
       * @param {HTMLElement} el
       * @param {string} width
       * @param {string} height
       *
       * @returns {object{width,height}}
       */
      dimensions: function dimensions(el, width, height) {
        var el_width = Embedo.utils.compute(el, 'width');
        width = width ?
          width :
          el_width > 0 ?
          el_width :
          Embedo.utils.compute(el.parentNode, 'width');
        height = height ?
          height :
          el_width > 0 ?
          el_width / 1.5 :
          Embedo.utils.compute(el.parentNode, 'height');
        return {
          width: width,
          height: height
        };
      },

      /**
       * @function centerize
       * Align an element center in relation to parent div
       *
       * @param {HTMLElement} parent_el
       * @param {HTMLElement} child_el
       * @param {object} options
       * @returns
       */
      centerize: function centerize(parent_el, child_el, options) {
        Embedo.log('info', 'centerize', parent_el, child_el, options);
        if (!Embedo.utils.validateElement(parent_el) || !Embedo.utils.validateElement(child_el)) {
          return;
        }
        options = options || {};

        if (options.width) {
          parent_el.style.width = options.width;
          parent_el.style.maxWidth = options.width;
          parent_el.style.marginLeft = 'auto';
          parent_el.style.marginRight = 'auto';
        }

        if (options.height) {
          parent_el.style.height = options.height;
          parent_el.style.maxHeight = options.height;
        }

        child_el.style.display = '-moz-box';
        child_el.style.display = '-ms-flexbox';
        child_el.style.display = '-webkit-flex';
        child_el.style.display = '-webkit-box';
        child_el.style.display = 'flex';
        child_el.style.textAlign = 'center';
        child_el.style['justify-content'] = 'center';
        child_el.style['align-items'] = 'center';
        child_el.style.margin = '0 auto';
      },

      /**
       * @function handleScriptValidation
       *
       * @param {string} url
       */
      handleScriptValidation: function handleScriptValidation(url) {
        if (!url) {
          return;
        }
        url = url.split('#')[0];
        var scripts = document.getElementsByTagName('script');
        for (var i = scripts.length; i--;) {
          if (scripts[i].src === url) {
            return true;
          }
        }
        return false;
      }
    }),
    writable: false,
    enumerable: true,
    configurable: false
  });

  /**
   * Embedo Event Listeners
   * @private
   *
   * @implements on
   * @implements off
   * @implements emit
   */
  Object.defineProperties(Embedo.prototype, {
    on: {
      value: function (event, listener) {
        if (typeof this.events[event] !== 'object') {
          this.events[event] = [];
        }
        this.events[event].push(listener);
      },
      writable: false,
      configurable: false
    },
    off: {
      value: function (event, listener) {
        var index;
        if (typeof this.events[event] === 'object') {
          index = this.events[event].indexOf(listener);
          if (index > -1) {
            this.events[event].splice(index, 1);
          }
        }
      },
      writable: false,
      configurable: false
    },
    emit: {
      value: function (event) {
        var i,
          listeners,
          length,
          args = [].slice.call(arguments, 1);
        if (typeof this.events[event] === 'object') {
          listeners = this.events[event].slice();
          length = listeners.length;

          for (i = 0; i < length; i++) {
            listeners[i].apply(this, args);
          }
        }
      },
      writable: false,
      configurable: false
    },
    once: {
      value: function (event, listener) {
        this.on(event, function g() {
          this.off(event, g);
          listener.apply(this, arguments);
        });
      },
      writable: false,
      configurable: false
    }
  });

  /**
   * @method init
   * Primary Embedo initialization
   *
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.init = function (options) {
    Embedo.log('info', 'init', this.requests, options);

    // Append enabled SDKs to DOM
    Object.keys(Embedo.defaults.SOURCES).forEach(function (source) {
      if (Embedo.defaults.SOURCES[source].SDK) {
        appendSDK(source, options[source]);
      }
    });

    this.domify();

    /**
     * @func appendSDK
     * Injects SDK's to body
     * @private
     *
     * @param {*} type
     * @param {*} props
     */
    function appendSDK(type, props) {
      if (!type || !props) {
        return;
      }
      var sdk = Embedo.utils.replacer(Embedo.defaults.SOURCES[type.toLowerCase()].SDK, {
        locale: props.locale || window.navigator.language || 'en_US'
      });

      if (!Embedo.utils.handleScriptValidation(sdk)) {
        if (props && typeof props === 'object') {
          sdk += (type === 'facebook' ? '#' : '?') + Embedo.utils.querystring(props);
        }
        document.body.appendChild(Embedo.utils.generateScript(sdk));
      }
    }
  };

  /**
   * @method domify
   * Replaces "data-embedo-*" elements during initialization.
   */
  Embedo.prototype.domify = function domify() {
    var embedos = document.querySelectorAll('[data-embedo-url]');
    [].forEach.call(embedos, function (embedo_el) {
      var options = Object.keys(embedo_el.dataset || {}).reduce(function (acc, cur) {
        if (cur.indexOf('embedo') !== -1) {
          var option = Embedo.utils.camelToSnake(cur).replace('embedo-', '');
          acc[option] = embedo_el.dataset[cur];
        }
        return acc;
      }, {});

      this.render(embedo_el, options.url, options);
    }.bind(this));
  };

  /**
   * @method facebook
   * Facebook embed prototype
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.facebook = function facebook(id, element, url, options, callback) {
    var type, fb_html_class;

    if (/^([^\/?].+\/)?post|photo(s|\.php)[\/?].*$/gm.test(url)) {
      type = url.match(/comment_id|reply_comment_id/) ? 'comment' : 'post';
    } else if (/^([^\/?].+\/)?video(s|\.php)[\/?].*$/gm.test(url)) {
      type = 'video';
    }

    if (type && type.match(/post|video/)) {
      var embed_uri = Embedo.utils.replacer(Embedo.defaults.SOURCES.facebook.oEmbed, {
        type: type
      });
      var query = Embedo.utils.merge({
          url: encodeURI(url),
          omitscript: true
        },
        options,
        Embedo.defaults.RESTRICTED
      );

      if ('width' in options || 'maxwidth' in options) {
        query.maxwidth = options.maxwidth || options.width;
      }

      embed_uri += '?' + Embedo.utils.querystring(query);

      Embedo.utils.fetch(embed_uri, function (error, content) {
        if (error) {
          Embedo.log('error', 'facebook', error);
          return callback(error);
        }
        handleFacebookEmbed(content.html);
      });
    } else {
      if (type === 'comment' || url.match(/comment_id|reply_comment_id/)) {
        fb_html_class = 'fb-comment-embed';
        options['data-numposts'] = options['data-numposts'] || 5;
      } else if (url.match(/plugins\/comments/)) {
        fb_html_class = 'fb-comments';
      } else {
        fb_html_class = 'fb-page';
        options['data-height'] =
          options['data-height'] || options.maxheight || options.height || 500;
      }

      var fb_html = Embedo.utils.generateElement(
        'div',
        Embedo.utils.merge({
            class: fb_html_class,
            'data-href': url,
            'data-width': options['data-width'] || options.maxwidth || options.width || 350
          },
          options
        )
      );

      fb_html.removeAttribute('width');
      fb_html.removeAttribute('height');
      handleFacebookEmbed(fb_html);
    }

    function handleFacebookEmbed(html) {
      var container = Embedo.utils.generateEmbed(id, 'facebook', html);
      element.appendChild(container);

      facebookify(
        element,
        container, {
          id: id,
          url: url,
          strict: options.strict,
          width: options.width,
          height: options.height,
          centerize: options.centerize
        },
        function (err, result) {
          if (err) {
            return callback(err);
          }
          callback(null, {
            id: id,
            el: element,
            width: result.width,
            height: result.height
          });
        }
      );
    }
  };

  /**
   * Twitter embed prototype
   * @method twitter
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.twitter = function twitter(id, element, url, options, callback) {
    var embed_uri = Embedo.defaults.SOURCES.twitter.oEmbed;
    var query = Embedo.utils.merge({
        url: encodeURI(url),
        omit_script: 1
      },
      options,
      Embedo.defaults.RESTRICTED
    );

    if ('width' in options || 'maxwidth' in options) {
      query.maxwidth = options.maxwidth || options.width;
    }

    if ('height' in options || 'maxheight' in options) {
      query.maxheight = options.maxheight || options.height;
    }

    embed_uri += '?' + Embedo.utils.querystring(query);

    Embedo.utils.fetch(embed_uri, function (error, content) {
      if (error) {
        Embedo.log('error', 'twitter', error);
        return callback(error);
      }
      var container = Embedo.utils.generateEmbed(id, 'twitter', content.html);
      element.appendChild(container);

      twitterify(
        element,
        container, {
          id: id,
          url: url,
          strict: options.strict,
          width: options.width,
          height: options.height,
          centerize: options.centerize
        },
        function (err, result) {
          if (err) {
            return callback(err);
          }
          callback(null, {
            id: id,
            el: element,
            width: result.width,
            height: result.height
          });
        }
      );
    });
  };

  /**
   * @method instagram
   * Instagram embed prototype
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.instagram = function (id, element, url, options, callback) {
    var embed_uri = Embedo.defaults.SOURCES.instagram.oEmbed;
    var query = Embedo.utils.merge({
        url: encodeURI(url),
        omitscript: true,
        hidecaption: true
      },
      options,
      Embedo.defaults.RESTRICTED
    );

    if ('width' in options || 'maxwidth' in options) {
      options.width = options.maxwidth ? options.maxwidth : options.width;
      if (options.width > 320) {
        query.maxwidth = options.width;
      }
    }

    embed_uri += '?' + Embedo.utils.querystring(query);

    var method = options.jsonp ? 'jsonp' : 'ajax';

    Embedo.utils[method](embed_uri, function (error, content) {
      if (error) {
        Embedo.log('error', 'instagram', error);
        return callback(error);
      }

      var container = Embedo.utils.generateEmbed(id, 'instagram', content.html);
      element.appendChild(container);

      instagramify(
        element,
        container, {
          id: id,
          url: url,
          strict: options.strict,
          width: options.width,
          height: options.height,
          centerize: options.centerize
        },
        function (err, result) {
          if (err) {
            return callback(err);
          }
          callback(null, {
            id: id,
            el: element,
            width: result.width,
            height: result.height
          });
        }
      );
    });
  };

  /**
   * @method youtube
   * YouTube embed prototype
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.youtube = function (id, element, url, options, callback) {
    if (!getYTVideoID(url)) {
      Embedo.log('error', 'youtube', 'Unable to detect Youtube video id.');
      return callback('Unable to detect Youtube video id.');
    }

    var youtube_uri = Embedo.defaults.SOURCES.youtube.oEmbed + getYTVideoID(url);
    youtube_uri += '?' + Embedo.utils.querystring(
      Embedo.utils.merge({
          modestbranding: 1,
          autohide: 1,
          showinfo: 0
        },
        options,
        Embedo.defaults.RESTRICTED
      )
    );

    this.iframe(id, element, youtube_uri, options, callback);

    /**
     * @func getYTVideoID
     * @private
     *
     * @param {string} url
     * @returns {String|Boolean}
     */
    function getYTVideoID(url) {
      var regexp = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/ ]{11})/i;
      var match = url.match(regexp);
      return match && match.length === 2 ? match[1] : false;
    }
  };

  /**
   * @method vimeo
   * Vimeo embed prototype
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.vimeo = function (id, element, url, options, callback) {
    var size = Embedo.utils.dimensions(element, options.width, options.height);
    var embed_options = Embedo.utils.merge({
        url: url,
        width: size.width,
        height: size.height,
        autohide: 1
      },
      options,
      Embedo.defaults.RESTRICTED
    );
    var embed_uri =
      Embedo.defaults.SOURCES.vimeo.oEmbed + '?' + Embedo.utils.querystring(embed_options);

    Embedo.utils.fetch(embed_uri, function (error, content) {
      if (error) {
        Embedo.log('error', 'vimeo', error);
        return callback(error);
      }
      var container = Embedo.utils.generateEmbed(id, 'vimeo', content.html);
      element.appendChild(container);

      callback(null, {
        id: id,
        el: element,
        width: size.width,
        height: size.height
      });
    });
  };

  /**
   * @method pinterest
   * Pinterest Embed
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.pinterest = function (id, element, url, options, callback) {
    var size = Embedo.utils.dimensions(element, options.width, options.height);
    var pin_size = size.width > 600 ? 'large' : size.width < 345 ? 'small' : 'medium';
    var pin_el = Embedo.utils.generateElement(
      'a',
      Embedo.utils.merge({
          href: url,
          'data-pin-do': options['data-pin-do'] || 'embedPin',
          'data-pin-lang': options['data-pin-lang'] || 'en',
          'data-pin-width': pin_size
        },
        options
      )
    );
    var container = Embedo.utils.generateEmbed(id, 'pinterest', pin_el);

    element.appendChild(container);

    pinterestify(
      element,
      container, {
        id: id,
        url: url,
        strict: options.strict,
        width: options.width,
        height: options.height,
        centerize: options.centerize
      },
      function (err, result) {
        if (err) {
          Embedo.log('error', 'pinterest', err);
          return callback(err);
        }
        callback(null, {
          id: id,
          el: element,
          width: result.width,
          height: result.height
        });
      }
    );
  };

  /**
   * @method github
   * Embed github URLs (gist) to DOM
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.github = function github(id, element, url, options, callback) {
    var size = Embedo.utils.dimensions(element, options.width, options.height);
    var iframe = Embedo.utils.generateElement('iframe',
      Embedo.utils.merge({
          width: size.width,
          height: size.height
        },
        options,
        Embedo.defaults.RESTRICTED
      )
    );
    var container = Embedo.utils.generateEmbed(id, 'github', iframe);

    element.appendChild(container);
    iframe.contentWindow.document.open();
    iframe.contentWindow.document.write(
      '<body><style type="text/css">body,html{margin:0;padding:0;border-radius:3px;}' +
      '.gist .gist-file{margin:0 !important;padding:0;}</style>' +
      '<script src="' + url + '"></script>' +
      '</body>'
    );
    iframe.contentWindow.document.close();
    iframe.onerror = function (err) {
      callback(err);
    };
    iframe.onload = function () {
      callback(null, {
        id: id,
        el: element,
        width: Embedo.utils.compute(container, 'width'),
        height: Embedo.utils.compute(container, 'height')
      });
    };
  };

  /**
   * @method soundcloud
   * SoundCloud Embed Player (api-web) prototype
   *
   * @see https://developers.soundcloud.com/docs/oembed
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.soundcloud = function (id, element, url, options, callback) {
    if (options.hasOwnProperty('width') && options.width) {
      options.maxwidth = options.maxwidth || options.width || '100%';
    }
    if (options.hasOwnProperty('height') && options.height) {
      options.maxheight = options.maxheight || options.height;
    }
    var size = Embedo.utils.dimensions(element, options.maxwidth, options.maxheight);
    var embed_options = Embedo.utils.merge({
        url: encodeURI(url),
        format: 'js' // Defaults JSONP
      },
      options,
      Embedo.defaults.RESTRICTED
    );
    var embed_uri =
      Embedo.defaults.SOURCES.soundcloud.oEmbed + '?' + Embedo.utils.querystring(embed_options);

    Embedo.utils.fetch(embed_uri, function (error, content) {
      if (error) {
        Embedo.log('error', 'soundcloud', error);
        return callback(error);
      }
      var container = Embedo.utils.generateEmbed(id, 'soundcloud', content.html);
      element.appendChild(container);

      callback(null, {
        id: id,
        el: element,
        width: size.width,
        height: size.height
      });
    });
  };

  /**
   * @method iframe
   * Embed URLs to HTML5 frame prototype
   *
   * @param {number} id
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.iframe = function (id, element, url, options, callback) {
    var fragment = document.createDocumentFragment();
    var size = Embedo.utils.dimensions(element, options.width, options.height);
    var extension = (url.substr(url.lastIndexOf('.')) || '').replace('.', '').toLowerCase();
    var mimes = {
      csv: 'text/csv',
      pdf: 'application/pdf',
      gif: 'image/gif',
      js: 'application/javascript',
      json: 'application/json',
      xhtml: 'application/xhtml+xml',
      pps: 'application/vnd.ms-powerpoint',
      ppsx: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
      xml: 'application/xml',
      ogg: 'video/ogg',
      mp4: 'video/mp4',
      webm: 'video/webm',
      html: 'text/html'
    };
    var mimetype = mimes[extension] || mimes.html;
    var has_video = extension.match(/(mp4|ogg|webm|ogv|ogm)/);
    var el_type = has_video ? 'video' : options.tagName || 'embed';
    var override = Embedo.utils.merge({}, options, Embedo.defaults.RESTRICTED);
    var embed_el = Embedo.utils.generateElement(el_type,
      Embedo.utils.merge({
          type: mimetype,
          src: url,
          width: size.width,
          height: size.height
        },
        override
      )
    );

    fragment.appendChild(Embedo.utils.generateEmbed(id, 'iframe', embed_el));
    element.appendChild(fragment);

    if (el_type === 'video') {
      setTimeout(function () {
        callback(null, {
          id: id,
          el: element,
          width: Embedo.utils.compute(embed_el, 'width'),
          height: Embedo.utils.compute(embed_el, 'height')
        });
      }, 250);
    } else {
      embed_el.onerror = function (err) {
        callback(err);
      };
      embed_el.onload = function () {
        callback(null, {
          id: id,
          el: element,
          width: Embedo.utils.compute(embed_el, 'width'),
          height: Embedo.utils.compute(embed_el, 'height')
        });
      };
    }
  };

  /**
   * @method render
   * Renders an embedo instance
   *
   * @name load
   * @param {HTMLElement} element
   * @param {string} url
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.render = function (element, url, options, callback) {
    Embedo.log('info', 'render', element, url, options);
    options = options || {};
    callback = callback || function () {};

    if (!element || !Embedo.utils.validateElement(element)) {
      Embedo.log('info', 'render', '`element` is either missing or invalid');
      return this.emit('error', new Error('element_is_missing'));
    }

    if (typeof url !== 'string') {
      return this.emit('error', new Error('invalid_url_string'));
    }

    if (!url || !Embedo.utils.validateURL(url)) {
      Embedo.log('info', 'render', '`url` is either missing or invalid');
      return this.emit('error', new Error('invalid_or_missing_url'));
    }

    var source = getURLSource(url);

    if (!source) {
      Embedo.log('info', 'render', new Error('Invalid or Unsupported URL'));
      return this.emit('error', new Error('url_not_supported'));
    }

    if (!this[source]) {
      Embedo.log('info', 'render', new Error('Requested source is not implemented or missing.'));
      return this.emit('error', new Error('unrecognised_url'));
    }

    if ('width' in options && options.width) {
      options.width = Embedo.utils.convertToPx(element, 'width', options.width);
    }

    if ('height' in options && options.height) {
      options.height = Embedo.utils.convertToPx(element, 'height', options.height);
    }

    var id = Embedo.utils.uuid();
    var request = {
      id: id,
      el: element,
      source: source,
      url: url,
      attributes: options
    };

    this.requests.push(request);

    this.emit('watch', 'load', request);

    this[source](id, element, url, options,
      function (err, data) {
        if (err) {
          this.emit('error', err);
          return callback(err);
        }
        data.url = request.url;
        data.source = request.source;
        data.options = request.attributes;

        this.emit('watch', 'loaded', data);
        callback(null, data);
      }.bind(this)
    );

    /**
     * @function getURLSource
     * Checks Source from URI
     *
     * @param {string} url
     * @returns {string}
     */
    function getURLSource(url) {
      var urlRegExp = /(http|https):\/\/(\w+:{0,1}\w*)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%!\-\/]))?/;
      var sources = Object.keys(Embedo.defaults.SOURCES) || [];

      if (!urlRegExp.test(url)) {
        return null;
      }

      var matched_source = sources
        .filter(function (source) {
          if (Embedo.defaults.SOURCES[source] && url.match(Embedo.defaults.SOURCES[source].REGEX)) {
            return source;
          }
        })
        .filter(Boolean);

      return matched_source && matched_source.length ? matched_source[0] : 'iframe';
    }
  };

  /**
   * @method load
   * Loads single or multiple embedo instances
   *
   * @name load
   * @param {HTMLElement} element
   * @param {String|Array} urls
   * @param {object} options Optional parameters.
   * @return callback
   */
  Embedo.prototype.load = function (element, urls, options) {
    Embedo.log('info', 'load', element, urls, options);
    options = options || {};
    var observer = new Embedo.utils.observer();

    if (!element || !Embedo.utils.validateElement(element)) {
      Embedo.log('info', 'load', '`element` is either missing or invalid');
      this.emit('error', new Error('element_is_missing'));
    } else {
      if (urls instanceof Array) {
        var reqs = {
          failed: [],
          finished: []
        };
        var jobs = urls.map(
          function (url) {
            return function (done) {
              this.render(element, url, options, function (err, data) {
                if (err) {
                  reqs.failed.push(err);
                  return done(err);
                }
                reqs.finished.push(data);
                done(null, data);
              });
            }.bind(this);
          }.bind(this)
        );

        Embedo.utils.sequencer.apply(this, jobs).then(function () {
          if (reqs.failed.length > 0) {
            return observer.reject(reqs.failed);
          }
          observer.resolve(reqs.finished);
        });
      } else if (typeof urls === 'string') {
        this.render(element, urls, options, function (err, data) {
          if (err) {
            return observer.reject(err);
          }
          observer.resolve(data);
        });
      } else {
        this.emit('error', new Error('invalid_url_string'));
      }
    }

    return observer;
  };

  /**
   * @method refresh
   * Refresh single or all embedo instances
   *
   * @param {object} element
   */
  Embedo.prototype.refresh = function (element) {
    Embedo.log('info', 'refresh', this.requests, element);
    if (this.requests.length === 0) {
      return;
    }
    this.requests.forEach(
      function (request) {
        if (!request.el) {
          return;
        }

        if (request.source === 'iframe') {
          return this.emit('refresh', request, {
            width: Embedo.utils.compute(request.el, 'width'),
            height: Embedo.utils.compute(request.el, 'height')
          });
        }

        if (element) {
          if (!Embedo.utils.validateElement(element)) {
            return;
          }
          if (element === request.el) {
            automagic(
              request.el,
              document.getElementById(request.id),
              request.attributes,
              function (err, data) {
                if (data) {
                  this.emit('refresh', request, data);
                }
              }.bind(this)
            );
          }
        } else {
          automagic(
            request.el,
            document.getElementById(request.id),
            request.attributes,
            function (err, data) {
              if (data) {
                this.emit('refresh', request, data);
              }
            }.bind(this)
          );
        }
      }.bind(this)
    );

    return this;
  };

  /**
   * @method destroy
   * Destroy an/all instance(s) of embedo
   *
   * @param {object} element
   */
  Embedo.prototype.destroy = function (element) {
    Embedo.log('warn', 'destroy', this.requests, element);
    if (this.requests.length === 0) {
      return;
    }
    var removed = [];

    this.requests.forEach(
      function (request) {
        if (!request.el || !Embedo.utils.validateElement(request.el)) {
          return;
        }
        if (element) {
          if (!Embedo.utils.validateElement(element)) {
            return;
          }
          if (element === request.el) {
            if (document.getElementById(request.id)) {
              document.getElementById(request.id).remove();
            }
            removed.push(request.id);
            this.emit('destroy', request);
          }
        } else {
          if (document.getElementById(request.id)) {
            document.getElementById(request.id).remove();
          }
          removed.push(request.id);
          this.emit('destroy', request);
        }
      }.bind(this)
    );

    this.requests = this.requests.filter(function (request) {
      return removed.indexOf(request.id) < 0;
    });

    return this;
  };

  /**
   * @function facebookify
   * Parses Facebook SDK
   *
   * @param {HTMLElement} parentNode
   * @param {HTMLElement} childNode
   * @param {object} options
   */
  function facebookify(parentNode, childNode, options, callback) {
    Embedo.utils.sdkReady('facebook', function (err) {
      if (err) {
        return callback(err);
      }
      window.FB.XFBML.parse(parentNode);
      window.FB.Event.subscribe('xfbml.render', function () {
        // First state will be `parsed` and then `rendered` to acknowledge embed.
        if (childNode.firstChild) {
          if (options.centerize !== false) {
            Embedo.utils.centerize(parentNode, childNode, options);
          }
          if (childNode.firstChild.getAttribute('fb-xfbml-state') === 'rendered') {
            automagic(parentNode, childNode, options, callback);
          }
        }
      });
    });
  }

  /**
   * @function twitterify
   * Parses Twitter SDK
   *
   * @param {HTMLElement} parentNode
   * @param {HTMLElement} childNode
   * @param {object} options
   */
  function twitterify(parentNode, childNode, options, callback) {
    Embedo.utils.sdkReady('twitter', function (err) {
      if (err) {
        return callback(err);
      }
      window.twttr.widgets.load(childNode);
      window.twttr.events.bind('rendered', function (event) {
        if (
          childNode.firstChild &&
          childNode.firstChild.getAttribute('id') === event.target.getAttribute('id')
        ) {
          if (options.centerize !== false) {
            Embedo.utils.centerize(parentNode, childNode, options);
          }
          automagic(parentNode, childNode, options, callback);
        }
      });
    });
  }

  /**
   * @function instagramify
   * Parses Instagram SDK
   *
   * @param {HTMLElement} parentNode
   * @param {HTMLElement} childNode
   * @param {object} options
   */
  function instagramify(parentNode, childNode, options, callback) {
    Embedo.utils.sdkReady('instagram', function (err) {
      if (err) {
        return callback(err);
      }
      if (!window.instgrm.Embeds || !window.instgrm.Embeds) {
        return callback(new Error('instagram_sdk_missing'));
      }

      window.instgrm.Embeds.process(childNode);
      var instagram_embed_timer = setInterval(handleInstagramRendered, 250);

      function handleInstagramRendered() {
        if (
          childNode.firstChild &&
          childNode.firstChild.className.match(/instagram-media-rendered/)
        ) {
          clearInterval(instagram_embed_timer);
          if (options.centerize !== false) {
            Embedo.utils.centerize(parentNode, childNode, options);
          }
          return automagic(parentNode, childNode, options, callback);
        }
      }
    });
  }

  /**
   * @function pinterestify
   * Parses Pinterest SDK
   *
   * @param {HTMLElement} parentNode
   * @param {HTMLElement} childNode
   * @param {object} options
   */
  function pinterestify(parentNode, childNode, options, callback) {
    Embedo.utils.sdkReady('pinterest', function (err) {
      if (err) {
        return callback(err);
      }
      if (!window.PinUtils || !window.PinUtils || !childNode || !childNode.firstChild) {
        return callback(new Error('pinterest_sdk_missing'));
      }

      setTimeout(function () {
        if (!childNode.querySelector('[data-pin-href]')) {
          window.PinUtils.build(childNode);
        }

        var pinterest_embed_timer_count = 0;
        var pinterest_embed_timer = setInterval(function () {
          pinterest_embed_timer_count += 1;
          if (childNode.querySelector('[data-pin-href]')) {
            clearInterval(pinterest_embed_timer);
            if (options.centerize !== false) {
              Embedo.utils.centerize(parentNode, childNode, options);
            }
            return automagic(parentNode, childNode, options, callback);
          } else if (pinterest_embed_timer_count >= 20) {
            clearInterval(pinterest_embed_timer);
            return callback(new Error('pinterest_embed_failed'));
          }
        }, 250);
      }, 750);
    });
  }

  /**
   * @function automagic
   * Automagic - Scales and resizes embed container
   *
   * @param {HTMLElement} parentNode
   * @param {HTMLElement} childNode
   * @param {object} options
   */
  function automagic(parentNode, childNode, options, callback) {
    Embedo.log('info', 'automagic', parentNode, childNode, options);
    options = options || {};
    callback = callback || function () {};

    if (!Embedo.utils.validateElement(parentNode) || !Embedo.utils.validateElement(childNode)) {
      return callback(new Error('HTMLElement does not exist in DOM.'));
    }

    Embedo.utils.watcher(
      options.id || Embedo.utils.uuid(),
      function () {
        var parent = {
          width: options.width || Embedo.utils.compute(parentNode, 'width'),
          height: options.height || Embedo.utils.compute(parentNode, 'height')
        };
        var child = {
          width: Embedo.utils.compute(childNode, 'width'),
          height: Embedo.utils.compute(childNode, 'height')
        };

        if (options.strict) {
          return callback(null, {
            width: parent.width,
            height: parent.height
          });
        }

        // Odd case when requested height is beyond limit of third party
        // Only apply when fixed width and heights are provided
        if (options.width && options.height) {
          var isOverflowing = child.width > parent.width || child.height > parent.height;

          if (options.width) {
            childNode.style.width = options.width + 'px';
          }

          if (options.height) {
            childNode.style.height = options.height + 'px';
          }

          if (isOverflowing) {
            var scale = Math.min(parent.width / child.width, parent.height / child.height);
            Embedo.utils.transform(childNode, 'scale(' + scale + ')');
          }
        }

        callback(null, {
          width: parent.width,
          height: parent.height
        });
      },
      500
    );
  }

  return Embedo;
});


text:
@interface BBBSecureDataStore : NSObject <BBBDataStoring>

@end

redpajama_set_name: RedPajamaGithub


text:
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

/** @suppress {duplicate} */
var remoting = remoting || {};

/**
 * HostInstallDialog prompts the user to accept Google ToS and install host
 * components. It has (up to) three stages:
 *
 * 1. When show() is called, it prompts the user to accept Google's ToS. If
 *    the user declines, it calls the onError callback with reason CANCELED.
 * 2. If the user accepts, it starts downloading the host installer and shows
 *    a prompt asking the user to continue. If the user cancels at this point,
 *    if calls the onError callback with reason CANCELED.
 * 3. If the user clicks OK, it calls the onDone callback. This callback should
 *    check whether or not the host is actually installed, and call tryAgain()
 *    if it is not. This will show a reminder to the user that they need to run
 *    the downloaded installer.
 *
 * @constructor
 */
remoting.HostInstallDialog = function() {
  /** @private */
  this.acceptTosButton_ = document.getElementById('host-install-tos-accept');
  /** @private */
  this.rejectTosButton_ = document.getElementById('host-install-tos-reject');
  /** @private */
  this.continueInstallButton_ = document.getElementById(
      'host-install-continue');
  /** @private */
  this.cancelInstallButton_ = document.getElementById('host-install-dismiss');
  /** @private */
  this.retryInstallButton_ = document.getElementById('host-install-retry');
  /** @private */
  this.tosMessage_ = document.getElementById('tos-message');

  /** @private {base.Disposables} */
  this.handlers_ = null;

  /** @private {function():void} */
  this.onDoneHandler_ = function() {};

  /** @private {function(!remoting.Error):void} */
  this.onErrorHandler_ = function(error) {};

  /** @private {remoting.HostInstaller} */
  this.hostInstaller_ = new remoting.HostInstaller();

  /** @private */
  this.termsOfServiceAccepted_ = false;
};

/**
 * Starts downloading host components and shows installation prompt.
 *
 * @param {function():void} onDone Callback called when user clicks Ok,
 * presumably after installing the host. The handler must verify that the host
 * has been installed and call tryAgain() otherwise.
 * @param {function(!remoting.Error):void} onError Callback called when user
 *    clicks Cancel button or there is some other unexpected error.
 * @return {void}
 */
remoting.HostInstallDialog.prototype.show = function(onDone, onError) {
  console.assert(this.handlers_ == null, 'Event handlers already exist.');
  this.handlers_ = new base.Disposables(
      new base.DomEventHook(this.acceptTosButton_,
                            'click',
                            this.onAcceptClicked_.bind(this),
                            false),
      new base.DomEventHook(this.rejectTosButton_,
                            'click',
                            this.onCancelClicked_.bind(this),
                            false));

  this.onDoneHandler_ = onDone;
  this.onErrorHandler_ = onError;

  var url = 'http://www.google.com/accounts/TOS?hl=' + l10n.getLocale();
  var substitutions = [
    '',
    ''
  ];
  l10n.localizeElementFromTag(this.tosMessage_,
                              'HOST_SETUP_TERMS_OF_SERVICE',
                              substitutions,
                              true);

  remoting.setMode(remoting.AppMode.HOST_INSTALL_TOS);
};

/**
 * In manual host installation, onDone handler must call this method if it
 * detects that the host components are still unavailable. The same onDone
 * and onError callbacks will be used when user clicks Ok or Cancel.
 */
remoting.HostInstallDialog.prototype.tryAgain = function() {
  console.assert(this.handlers_ == null, 'Event handlers already exist.');
  this.handlers_ = new base.Disposables(
      new base.DomEventHook(this.retryInstallButton_,
                            'click',
                            this.onRetryClicked_.bind(this),
                            false));

  remoting.setMode(remoting.AppMode.HOST_INSTALL_PENDING);
};

/** @private */
remoting.HostInstallDialog.prototype.onAcceptClicked_ = function() {
  console.assert(this.handlers_ != null, 'No event handlers registered.');
  base.dispose(this.handlers_);
  this.handlers_ = new base.Disposables(
      new base.DomEventHook(this.continueInstallButton_,
                            'click',
                            this.onOkClicked_.bind(this),
                            false),
      new base.DomEventHook(this.cancelInstallButton_,
                            'click',
                            this.onCancelClicked_.bind(this),
                            false));

  /** @type {remoting.HostInstaller} */
  var hostInstaller = new remoting.HostInstaller();

  /** @type {remoting.HostInstallDialog} */
  var that = this;

  this.hostInstaller_.downloadAndWaitForInstall().then(function() {
    that.continueInstallButton_.click();
    that.hostInstaller_.cancel();
  }, function(){
    that.onErrorHandler_(new remoting.Error(remoting.Error.Tag.CANCELLED));
    that.hostInstaller_.cancel();
  });

  remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
};

/** @private */
remoting.HostInstallDialog.prototype.onOkClicked_ = function() {
  console.assert(this.handlers_ != null, 'No event handlers registered.');
  base.dispose(this.handlers_);
  this.handlers_ = null;

  this.onDoneHandler_();
};

/** @private */
remoting.HostInstallDialog.prototype.onCancelClicked_ = function() {
  console.assert(this.handlers_ != null, 'No event handlers registered.');
  base.dispose(this.handlers_);
  this.handlers_ = null;
  this.hostInstaller_.cancel();
  this.onErrorHandler_(new remoting.Error(remoting.Error.Tag.CANCELLED));
};

/** @private */
remoting.HostInstallDialog.prototype.onRetryClicked_ = function() {
  console.assert(this.handlers_ != null, 'No event handlers registered.');
  base.dispose(this.handlers_);
  this.handlers_ = new base.Disposables(
      new base.DomEventHook(this.continueInstallButton_,
                            'click',
                            this.onOkClicked_.bind(this),
                            false),
      new base.DomEventHook(this.cancelInstallButton_,
                            'click',
                            this.onCancelClicked_.bind(this),
                            false));

  remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
};


text:
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core;
using umbraco.interfaces;

namespace Umbraco.Web.Trees
{
    [CollectionDataContract(Name = "menuItems", Namespace = "")]
    public class MenuItemCollection : IEnumerable<MenuItem>
    {
        public MenuItemCollection()
        {

        }

        private readonly List<MenuItem> _menuItems = new List<MenuItem>();

        /// <summary>
        /// Adds a menu item
        /// </summary>
        internal MenuItem AddMenuItem(IAction action)
        {
            var item = new MenuItem(action);
            _menuItems.Add(item);
            return item;
        }

        /// <summary>
        /// Adds a menu item
        /// </summary>
        public void AddMenuItem(MenuItem item)
        {
            _menuItems.Add(item);
        }

        //TODO: Implement more overloads for MenuItem with dictionary vals

        /// <summary>
        /// Adds a menu item
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public void AddMenuItem<T>()
            where T : IAction
        {
            AddMenuItem<T>(null);
        }

        /// <summary>
        /// Adds a menu item with a key value pair which is merged to the AdditionalData bag
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void AddMenuItem<T>(string key, string value)
            where T : IAction
        {
            AddMenuItem<T>(new Dictionary<string, object> { { key, value } });
        }

        /// <summary>
        /// Adds a menu item with a dictionary which is merged to the AdditionalData bag
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="additionalData"></param>
        public void AddMenuItem<T>(IDictionary<string, object> additionalData)
            where T : IAction
        {
            var item = ActionsResolver.Current.GetAction<T>();
            if (item != null)
            {
                var menuItem = new MenuItem(item);

                if (additionalData != null)
                {
                    foreach (var i in additionalData)
                    {
                        menuItem.AdditionalData[i.Key] = i.Value;
                    }
                }

                _menuItems.Add(menuItem);

                //TODO: Once we implement 'real' menu items, not just IActions we can implement this since
                // people may need to pass specific data to their menu items

                ////validate the data in the meta data bag
                //item.ValidateRequiredData(AdditionalData);
            }
        }

        public IEnumerator<MenuItem> GetEnumerator()
        {
            return _menuItems.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}


text:
package org.spongepowered.common.mixin.api.minecraft.client.server;

import net.minecraft.client.Minecraft;
import net.minecraft.client.server.IntegratedServer;
import org.spongepowered.api.Client;
import org.spongepowered.api.client.LocalServer;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

@Mixin(IntegratedServer.class)
public abstract class IntegratedServerMixin_API implements LocalServer {

    // @formatter:off
    @Shadow @Final private Minecraft minecraft;
    // @formatter:on

    @Override
    public Client client() {
        return (Client) this.minecraft;
    }
}


text:
class Grid extends GameObjectContainer {

    constructor(public width: number, public height: number, public squareSize: number, public player:Player, public goal:Goal)
    {
        super();
    }

    init():void {
        for (var x = 0; x < this.width; x++) {
          for (var y = 0; y < this.height; y++) {
              super.pushObject(new Square(x, y, this.squareSize));
          }
        }

        this.player = new Player();
        this.placePlayer(this.player);
        super.pushObject(this.player);

        this.goal = new Goal();
        this.placeGoal(this.goal);
        super.pushObject(this.goal);

        super.init();
    }

    private placePlayer(player:Player):void {
        player.x = Math.floor(Math.random() * 2);
        player.y = Math.floor(Math.random() * this.height);
    }

    private placeGoal(goal:Goal):void {
        goal.x = this.width - 1 - Math.floor(Math.random() * 2);
        goal.y = Math.floor(Math.random() * this.height);
    }
}


text:
import { merge } from 'lodash'
import { mount } from '@vue/test-utils'
import { useI18nGlobally } from '../../__utils__/i18n'
import { WalletDelegates } from '@/components/Wallet'

const i18n = useI18nGlobally()

describe('WalletDelegates', () => {
  let showExplanation
  let walletVote = {}

  const activeDelegatesMock = count => {
    return {
      mocks: {
        session_network: {
          constants: {
            activeDelegates: count
          }
        }
      }
    }
  }

  const mountWrapper = config => {
    return mount(WalletDelegates, merge({
      i18n,
      provide: {
        walletVote
      },
      mocks: {
        session_network: {
          constants: {
            activeDelegates: 51
          }
        },
        $store: {
          getters: {
            'app/showVotingExplanation': showExplanation
          }
        },
        $logger: {
          error: () => {}
        },
        $error: () => {}
      },
      stubs: {
        TableWrapper: true
      }
    }, config))
  }

  it('should render', () => {
    const wrapper = mountWrapper()
    expect(wrapper.isVueInstance()).toBeTrue()
  })

  it('should dynamically calculate the per page options', () => {
    let wrapper = mountWrapper(activeDelegatesMock(25))
    expect(wrapper.vm.perPageOptions).toEqual([25])

    wrapper = mountWrapper(activeDelegatesMock(53))
    expect(wrapper.vm.perPageOptions).toEqual([25, 53])

    wrapper = mountWrapper(activeDelegatesMock(101))
    expect(wrapper.vm.perPageOptions).toEqual([25, 50, 75, 100])
  })

  it('should cap the query limit at 100', () => {
    const wrapper = mountWrapper(activeDelegatesMock(101))
    expect(wrapper.vm.queryParams.limit).toBe(100)
  })

  describe('when the wallet is voting', () => {
    beforeEach(() => {
      walletVote = { username: 'key' }
      showExplanation = true
    })

    it('should not display the voting explanation', () => {
      const wrapper = mountWrapper()
      expect(wrapper.find('.WalletDelegates__explanation').exists()).toBeFalse()
    })
  })

  describe('when the wallet is not voting', () => {
    beforeEach(() => {
      walletVote = {}
    })

    describe('when the voting explanation has not been closed', () => {
      beforeEach(() => {
        showExplanation = true
      })

      it('should display it', () => {
        const wrapper = mountWrapper()
        expect(wrapper.find('.WalletDelegates__explanation').exists()).toBeTrue()
      })
    })

    describe('when the voting explanation has been closed', () => {
      beforeEach(() => {
        showExplanation = false
      })

      it('should not display it', () => {
        const wrapper = mountWrapper()
        expect(wrapper.find('.WalletDelegates__explanation').exists()).toBeFalse()
      })
    })
  })
})


text:
package org.wso2.carbon.identity.entitlement.filter.exception;

import javax.servlet.ServletException;

public class EntitlementFilterException extends ServletException {

    public EntitlementFilterException(String message) {
        super(message);
    }

    public EntitlementFilterException(String message, Throwable cause) {
        super(message, cause);
    }

}


text:
PupaFoundation
=============

Pupa is a custom foundation framework for Cocoa


text:
Rails.application.routes.draw do
  # Serve websocket cable requests in-process
  mount ActionCable.server => '/cable'

	resources :businesses
  get 'orders/history', to: 'orders#history', as: 'history'
  get '/orders/shippers', to: 'orders#shippers', as: 'all_shippers'
	get 'orders/update_status', to: 'orders#update_status', as: 'order_update_status'

  resources :orders

  resources :charges, only: [:new, :create]

  resources :boxes

  # Admins with devise and custom routes
  devise_for :admins
  # , :controllers => {:registrations => 'registrations'}
  get '/admins/new', to: 'admins#admin_new', as: 'new_admin'
  post '/admins/new', to: 'admins#admin_create', as: 'create_admin'
  put '/admin/:id/update', to: 'admins#admin_update', as: 'update_admin'

# Shippers routes
 devise_for :shippers
# , :controllers => {:registrations => 'registrations'}
  get '/shippers/new', to: 'shippers#shipper_new', as: 'new_shipper'
  post '/shippers/new', to: 'shippers#shipper_create', as: 'create_shipper'
  get '/shippers/:id/edit', to: 'shippers#shipper_edit', as: 'edit_shipper'
  put '/shipper/:id/edit', to: 'shippers#shipper_update', as: 'update_shipper'

  devise_for :clients, :controllers => {:registrations => 'registrations'}
  post '/clients/:id/add_bag', to: 'clients#add_bag', as: 'add_bag'
  get '/clients/:id/edit_bag', to: 'clients#edit_bag', as: 'edit_bag'
  patch '/clients/:id/edit_bag', to: 'clients#update_bag', as: 'client'
  get "/admins/dash" => "static_pages#admins_dash"
  root "application#home"
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end


text:
package me.aksalj.usiuboard.data;

import android.content.Context;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Set;

import me.aksalj.usiuboard.R;
import me.aksalj.usiuboard.data.iface.ICallback;
import me.aksalj.usiuboard.data.iface.IResultCallback;
import me.aksalj.usiuboard.data.worker.FeedItemsFetcher;
import me.aksalj.usiuboard.data.worker.FeedsFetcher;

public class Manager {
    private static Manager sInstance;

    public static final int DEFAULT_FEED_ID = -1;
    private static BoardFeed DEFAULT_FEED;

    private Context mCxt;

    private ArrayList<BoardFeed> mFeeds = new ArrayList<>();
    private HashMap<Integer, ArrayList<BoardItem>> mFeedsItems = new HashMap<>();

    public static Manager getInstance() {
        if (sInstance == null) {
            throw new IllegalStateException("Manager needs to be init!");
        }

        return sInstance;
    }

    public static boolean init(Context cxt) {
        sInstance = new Manager(cxt);
        DEFAULT_FEED = new BoardFeed(DEFAULT_FEED_ID,
                cxt.getString(R.string.latest_posts),
                cxt.getString(R.string.latest_board_posts), null);
        return true;
    }

    private Manager(Context cxt) {
        mCxt = cxt;
    }

    /**
     * Get a feed by position
     * @param position
     * @return
     */
    public BoardFeed getFeed(int position) {
        return mFeeds.get(position);
    }

    /**
     *
     * @param feedId
     * @return
     */
    public BoardFeed getFeedById(int feedId) {
//        if(feedId == DEFAULT_FEED_ID) {
//            return DEFAULT_FEED;
//        }

        for (BoardFeed feed:mFeeds) {
            if(feed.id == feedId) return feed;
        }
        return null;
    }

    /**
     *
     * @return
     */
    public int countFeeds() {
        return mFeeds.size();
    }

    /**
     *
     * @param feedId
     * @return
     */
    public int countItems(int feedId) {
        if (mFeedsItems.containsKey(feedId)) {
            return mFeedsItems.get(feedId).size();
        }
        return 0;
    }

    /**
     *
     * @param feed
     * @param positionInFeed
     * @return
     */
    public BoardItem getItem(int feed, int positionInFeed) {
        if (mFeedsItems.containsKey(feed)) {
            return mFeedsItems.get(feed).get(positionInFeed); // HUH!!!
        }
        return null;
    }

    /**
     *
     * @param id
     * @return
     */
    public BoardItem getItem(String id) {
        for (Integer integer : mFeedsItems.keySet()) {
            ArrayList<BoardItem> next = mFeedsItems.get(integer);
            for (BoardItem item : next) {
                if (item.id.contentEquals(id)) return item;
            }
        }
        return null;
    }

    /**
     * Fetch feed items
     * @param feedId
     * @param callback
     */
    public void fetchItems(final int feedId, final ICallback callback) {

        if(feedId == DEFAULT_FEED_ID) {
            fetchItems(callback);
        } else {

            BoardFeed feed = getFeedById(feedId);
            if (feed != null) {

                new FeedItemsFetcher(feed, new IResultCallback<ArrayList<BoardItem>>() {
                    @Override
                    public void onResult(ArrayList<BoardItem> result) {
                        mFeedsItems.put(feedId, result);
                        callback.onSuccess();
                    }

                    @Override
                    public void onError(String message) {
                        callback.onError(message);
                    }
                }).execute();
            } else {
                callback.onError("Feed not found");
            }
        }
    }

    /**
     * Fetch all feeds' items
     * @param callback
     */
    private void fetchItems(final ICallback callback) {
        for(int i = 0; i < mFeeds.size(); i++) {

            final boolean isLastFeed = (i >= (mFeeds.size() - 1));
            final BoardFeed feed = mFeeds.get(i);

            if(feed.id == DEFAULT_FEED_ID) // Default feed has no URL
                continue;

            new FeedItemsFetcher(feed, new IResultCallback<ArrayList<BoardItem>>() {
                @Override
                public void onResult(ArrayList<BoardItem> result) {
                    mFeedsItems.put(feed.id, result); // Overwrite if already there!

                    if(isLastFeed) {
                        // TODO: Pick every first of every feed to make default feed

                        ArrayList<BoardItem> defaultItems = new ArrayList<BoardItem>();
                        Set<Integer> keys = mFeedsItems.keySet();
                        for (Integer key:keys) {

                            if(key == DEFAULT_FEED_ID) // Already added
                                continue;

                            ArrayList<BoardItem> items = mFeedsItems.get(key);
                            if(items.size() > 0) {
                                defaultItems.add(items.get(0));
                            }
                        }

                        Collections.sort(defaultItems, new Comparator<BoardItem>() { // Sort by date
                            @Override
                            public int compare(BoardItem lhs, BoardItem rhs) {
                                if (lhs._date_ms < rhs._date_ms) {
                                    return 1;
                                } else if (lhs._date_ms > rhs._date_ms) {
                                    return -1;
                                } else {
                                    return 0;
                                }
                            }
                        });

                        mFeedsItems.put(DEFAULT_FEED_ID, defaultItems);

                        callback.onSuccess();

                    }
                }

                @Override
                public void onError(String message) {
                    if(isLastFeed) { callback.onError(message); }
                }
            }).execute();
        }
    }

    /**
     * Fetch board feeds
     * @param callback
     */
    public void fetchFeeds(final ICallback callback) {
        new FeedsFetcher(new IResultCallback<ArrayList<BoardFeed>>() {
            @Override
            public void onResult(ArrayList<BoardFeed> result) {
                result.add(0, DEFAULT_FEED);
                mFeeds = result;
                callback.onSuccess();
            }

            @Override
            public void onError(String message) {
                callback.onError(message);
            }
        }).execute();
    }
}


text:
import { CourseService } from './course.service';
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-course',
  templateUrl: './course.component.html',
  styleUrls: ['./course.component.css']
})
export class CourseComponent implements OnInit {

title = 'List of Courses';
   courses: any;
  isActive = false;
  private newCourse = 'Add new Course';

  onDelete(course) {
    console.log(course);
    this.courses.slice(this.courses.indexOf(course), 1);

  }
  onKeyUp() {
    this.courses.push(this.newCourse);

  }

  constructor(courseService: CourseService) {
    this.courses = courseService.getCourse();

  }

  ngOnInit() {
  }

}


text:
layout: post
title:  "ADB access to remote server from local usb"
date:   2014-10-06 23:51:57
permalink: /blog/32/
---

I have a VPS server where I'm compiling CyanogenMod and some android kernels. To compile CyanogenMod we need to pull some vendor specific "prebuilts" i.e. binaries. Connecting the server to the device connected to my laptop is actually quite trivial.

First we put adb in TCP/IP mode

    adb tcpip 8001

Then, let's forward the that same port to the laptop

    adb forward tcp:8001 tcp:8001

Connect to the phone

    adb connect localhost:8001

Then finally, we forward the local port to the server

    ssh -R localhost:8001:localhost:8001 <server_host>

This command simply does a reverse tunnel. Back on the server we simply connect to the port on localhost again

    adb connect localhost:8001

And viola! Pretty simple :D


text:
import LocalParser from '../src/LocalParser';
import Block from '../src/Block';
import { Properties } from '../src/types';
import { createBlock } from './helpers';
import {
  SYNTAX_FALLBACKS,
  SYNTAX_PROPERTIES,
  SYNTAX_SELECTOR_ATTRIBUTES,
  SYNTAX_SELECTOR_PSEUDOS,
  SYNTAX_SELECTORS_SPECIFICITY,
  SYNTAX_SELECTORS_COMBINATORS,
  SYNTAX_SELECTORS_MULTIPLE,
  SYNTAX_SUPPORTS,
  SYNTAX_MEDIA,
  SYNTAX_MEDIA_NESTED,
  SYNTAX_LOCAL_BLOCK,
  SYNTAX_VARIABLES,
} from './__mocks__/local';

describe('LocalParser', () => {
  let parser: LocalParser<Properties>;
  let spy: jest.Mock;

  beforeEach(() => {
    parser = new LocalParser();
    spy = jest.fn();
  });

  it('errors for an at-rule', () => {
    expect(() => {
      parser.parse({
        '@rule': {},
      });
    }).toThrow('At-rules may not be defined at the root of a local block, found "@rule".');
  });

  it('errors for invalid value type', () => {
    expect(() => {
      parser.parse({
        // @ts-expect-error
        el: 123,
      });
    }).toThrow(
      'Invalid declaration for "el". Must be an object (style declaration) or string (class name).',
    );
  });

  it('renders a full block', () => {
    parser.on('rule', spy);
    parser.parse({
      selector: SYNTAX_LOCAL_BLOCK,
    });

    expect(spy).toHaveBeenCalledWith('selector', createBlock('selector', SYNTAX_LOCAL_BLOCK));
  });

  describe('class names', () => {
    beforeEach(() => {
      parser.on('class', spy);
    });

    it('emits for each class name', () => {
      parser.parse({
        foo: 'foo',
        bar: {},
        baz: 'baz',
      });

      expect(spy).toHaveBeenCalledTimes(2);
      expect(spy).toHaveBeenCalledWith('foo', 'foo');
      expect(spy).toHaveBeenCalledWith('baz', 'baz');
    });
  });

  describe('properties', () => {
    beforeEach(() => {
      parser.on('block:property', spy);
    });

    it('emits each property and value', () => {
      parser.parse({
        props: SYNTAX_PROPERTIES,
      });

      expect(spy).toHaveBeenCalledTimes(4);
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'color', 'black');
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'display', 'inline');
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'marginRight', 10);
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'padding', 0);
    });

    it('doesnt emit for undefined values', () => {
      parser.parse({
        props: {
          color: undefined,
        },
      });

      expect(spy).toHaveBeenCalledTimes(0);
    });
  });

  describe('attributes', () => {
    beforeEach(() => {
      parser.on('block:attribute', spy);
    });

    it('emits each attribute with value and params', () => {
      parser.parse({
        attrs: SYNTAX_SELECTOR_ATTRIBUTES,
      });

      expect(spy).toHaveBeenCalledTimes(2);

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '[disabled]',
        createBlock('[disabled]', SYNTAX_SELECTOR_ATTRIBUTES['[disabled]']),
        { specificity: 0 },
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '[href]',
        createBlock('[href]', SYNTAX_SELECTOR_ATTRIBUTES['[href]']),
        { specificity: 0 },
      );
    });
  });

  describe('pseudos', () => {
    beforeEach(() => {
      parser.on('block:pseudo', spy);
    });

    it('emits each pseudo (class and element) with value and params', () => {
      parser.parse({
        attrs: SYNTAX_SELECTOR_PSEUDOS,
      });

      expect(spy).toHaveBeenCalledTimes(2);

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        ':hover',
        createBlock(':hover', SYNTAX_SELECTOR_PSEUDOS[':hover']),
        { specificity: 0 },
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '::before',
        createBlock('::before', SYNTAX_SELECTOR_PSEUDOS['::before']),
        { specificity: 0 },
      );
    });
  });

  describe('selectors', () => {
    beforeEach(() => {
      parser.on('block:selector', spy);
    });

    it('errors if selector is not an object', () => {
      expect(() => {
        parser.parse({
          selector: {
            // @ts-expect-error
            ':hover': 123,
          },
        });
      }).toThrow('":hover" must be a declaration object of CSS properties.');
    });

    it('errors if a comma separated list is passed', () => {
      expect(() => {
        parser.parse({
          selector: {
            // @ts-expect-error
            ':hover, :focus': {},
          },
        });
      }).toThrow('Advanced selector ":hover, :focus" must be nested within a @selectors block.');
    });
  });

  describe('@fallbacks', () => {
    beforeEach(() => {
      parser.on('block:fallback', spy);
    });

    it('errors if fallbacks are not an object', () => {
      expect(() => {
        parser.parse({
          fb: {
            // @ts-expect-error
            '@fallbacks': 123,
          },
        });
      }).toThrow('"@fallbacks" must be a declaration object of CSS properties.');
    });

    it('does not emit if no fallbacks', () => {
      parser.parse({
        fb: {},
      });

      expect(spy).not.toHaveBeenCalled();
    });

    it('emits for each fallback declaration', () => {
      parser.parse({
        fb: SYNTAX_FALLBACKS,
      });

      expect(spy).toHaveBeenCalledTimes(3);
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'background', ['red']);
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'display', ['block', 'inline-block']);
      expect(spy).toHaveBeenCalledWith(expect.any(Block), 'color', ['blue']);
    });
  });

  describe('@media', () => {
    beforeEach(() => {
      parser.on('block:media', spy);
    });

    it('errors if media is not an object', () => {
      expect(() => {
        parser.parse({
          fb: {
            // @ts-expect-error
            '@media': 123,
          },
        });
      }).toThrow('@media must be a mapping of CSS declarations.');
    });

    it('does not emit if no media', () => {
      parser.parse({
        media: {},
      });

      expect(spy).not.toHaveBeenCalled();
    });

    it('emits for each condition', () => {
      parser.parse({
        media: SYNTAX_MEDIA,
      });

      expect(spy).toHaveBeenCalledTimes(2);

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '(min-width: 300px)',
        createBlock('@media (min-width: 300px)', {
          color: 'blue',
          paddingLeft: 15,
        }),
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '(max-width: 1000px)',
        createBlock('@media (max-width: 1000px)', {
          color: 'green',
          paddingLeft: 20,
        }),
      );
    });

    it('supports nested media conditions', () => {
      parser.parse({
        media: SYNTAX_MEDIA_NESTED,
      });

      expect(spy).toHaveBeenCalledTimes(2);

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '(min-width: 300px)',
        createBlock('@media (min-width: 300px)', {
          color: 'blue',
          '@media (max-width: 1000px)': {
            color: 'green',
          },
        }),
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '(max-width: 1000px)',
        createBlock('@media (max-width: 1000px)', {
          color: 'green',
        }),
      );
    });
  });

  describe('@selectors', () => {
    beforeEach(() => {
      parser.on('block:selector', spy);
    });

    it('errors if selectors are not an object', () => {
      expect(() => {
        parser.parse({
          fb: {
            // @ts-expect-error
            '@selectors': 123,
          },
        });
      }).toThrow('@selectors must be a mapping of CSS declarations.');
    });

    it('does not emit if no selectors', () => {
      parser.parse({
        selectors: {},
      });

      expect(spy).not.toHaveBeenCalled();
    });

    it('emits selector with defined specificity', () => {
      const attrSpy = jest.fn();
      const pseudoSpy = jest.fn();

      parser.on('block:attribute', attrSpy);
      parser.on('block:pseudo', pseudoSpy);
      parser.parse({
        selectors: SYNTAX_SELECTORS_SPECIFICITY,
      });

      expect(pseudoSpy).toHaveBeenCalledTimes(2);

      expect(pseudoSpy).toHaveBeenCalledWith(
        expect.any(Block),
        ':hover',
        createBlock(':hover', { position: 'static' }),
        { specificity: 2 },
      );

      expect(pseudoSpy).toHaveBeenCalledWith(
        expect.any(Block),
        ':active',
        createBlock(':active', { position: 'absolute' }),
        { specificity: 1 },
      );

      expect(attrSpy).toHaveBeenCalledTimes(1);

      expect(attrSpy).toHaveBeenCalledWith(
        expect.any(Block),
        '[hidden]',
        createBlock('[hidden]', { position: 'relative' }),
        { specificity: 3 },
      );
    });

    it('emits universal and combinator selectors', () => {
      parser.parse({
        selectors: SYNTAX_SELECTORS_COMBINATORS,
      });

      expect(spy).toHaveBeenCalledTimes(4);

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '> li',
        createBlock('> li', { listStyle: 'bullet' }),
        { specificity: 0 },
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '+ div',
        createBlock('+ div', { display: 'none' }),
        { specificity: 0 },
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '~ span',
        createBlock('~ span', { color: 'black' }),
        { specificity: 0 },
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '*',
        createBlock('*', { backgroundColor: 'inherit' }),
        { specificity: 0 },
      );
    });

    it('emits each selector in a comma separated list', () => {
      const attrSpy = jest.fn();
      const pseudoSpy = jest.fn();

      parser.on('block:attribute', attrSpy);
      parser.on('block:pseudo', pseudoSpy);
      parser.parse({
        selectors: SYNTAX_SELECTORS_MULTIPLE,
      });

      expect(pseudoSpy).toHaveBeenCalledWith(
        expect.any(Block),
        ':disabled',
        createBlock(':disabled', { cursor: 'default' }),
        { specificity: 0 },
      );

      expect(attrSpy).toHaveBeenCalledWith(
        expect.any(Block),
        '[disabled]',
        createBlock('[disabled]', { cursor: 'default' }),
        { specificity: 2 },
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '> span',
        createBlock('> span', { cursor: 'default' }),
        { specificity: 0 },
      );
    });
  });

  describe('@supports', () => {
    beforeEach(() => {
      parser.on('block:supports', spy);
    });

    it('errors if supports are not an object', () => {
      expect(() => {
        parser.parse({
          fb: {
            // @ts-expect-error
            '@supports': 123,
          },
        });
      }).toThrow('@supports must be a mapping of CSS declarations.');
    });

    it('does not emit if no supports', () => {
      parser.parse({
        supports: {},
      });

      expect(spy).not.toHaveBeenCalled();
    });

    it('emits for each condition', () => {
      parser.parse({
        supports: SYNTAX_SUPPORTS,
      });

      expect(spy).toHaveBeenCalledTimes(2);

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        '(display: flex)',
        createBlock('@supports (display: flex)', { display: 'flex' }),
      );

      expect(spy).toHaveBeenCalledWith(
        expect.any(Block),
        'not (display: flex)',
        createBlock('@supports not (display: flex)', { float: 'left' }),
      );
    });
  });

  describe('@variables', () => {
    beforeEach(() => {
      parser.on('block:variable', spy);
    });

    it('errors if variables are not an object', () => {
      expect(() => {
        parser.parse({
          vars: {
            // @ts-expect-error
            '@variables': 123,
          },
        });
      }).toThrow('@variables must be a mapping of CSS variables.');
    });

    it('does not emit if no variables', () => {
      parser.parse({
        vars: {},
      });

      expect(spy).not.toHaveBeenCalled();
    });

    it('does not emit `variable` listener', () => {
      const varSpy = jest.fn();

      parser.on('variable', varSpy);
      parser.parse({
        vars: SYNTAX_VARIABLES,
      });

      expect(varSpy).not.toHaveBeenCalled();
    });

    it('emits for each variable', () => {
      parser.parse({
        vars: SYNTAX_VARIABLES,
      });

      expect(spy).toHaveBeenCalledTimes(3);
      expect(spy).toHaveBeenCalledWith(expect.any(Block), '--font-size', '14px');
      expect(spy).toHaveBeenCalledWith(expect.any(Block), '--color', 'red');
      expect(spy).toHaveBeenCalledWith(expect.any(Block), '--line-height', 1.5);
    });
  });
});