<commit_before>;(function() {

  var config = [

    // Stream Pins
    {
      placement: 'pinterest-stream-pins',
      selector: '.Pin.Module:not(.buffer-inserted, .hideHoverUI)',
      $button: $([
        '
',
          '',
            '',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted');

        $actions
          .find('.pinImageActionButtonWrapper')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $img = $(el).find('.pinImg');
        var image = $img.attr('src');
        // Grab text from image alt attribute
        var text = $img.attr('alt');

        var $source = $(el).find('.pinNavLink');
        if($source.length === 0){
          $source = $(el).find('.pinDomain');
        }
        console.error($source);
        var source = $source.attr('href') || $source.text();
        return {
          text: text,
          url: source,
          picture: image,
          placement: this.placement
        };
      }
    },
    // Single Pins
    {
      placement: 'pinterest-single-pin',
      selector: '.PinActionBar:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $img = $(el).parents('.closeupActionBarContainer')
                        .siblings('.closeupContainer')
                        .find('.pinImage');

        var image = $img.attr('src');
        // Grab text from image alt attribute
        var text = $img.attr('alt');

        var $source = $(el).children('.repinLike').find('.NavigateButton.website');
        var source = $source.attr('href');

        return {
          text: text,
          url: source,
          picture: image,
          placement: this.placement
        };
      }
    },
    // Pin Action Popup Pin
    {
      placement: 'pinterest-pin-popup',
      selector: '.SelectList li.item .BoardLabel:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $img = $(el).parents('.boardsWrapper').siblings('.pinContainer').find('.pinImg');

        var image = $img.attr('src');
        // Grab text from image alt attribute
        var text = $img.attr('alt');

        var $source = $(el).parents('.boardsWrapper').siblings('.pinContainer').find('.pinDomain');
        var source = $source.text();

        if(!source){
          if(window.location.search){
            var params = window.location.search.split('&');

            var urlIndex = -1;
            for(var i = 0; i < params.length; i++){
              if(params[i].indexOf('url=') > -1){
                urlIndex = i;
                break;
              }
            }

            if(urlIndex > -1){
              source = decodeURIComponent(params[urlIndex].split("=")[1]);
            }
          }

        }

        return {
          text: text,
          url: source,
          picture: image,
          placement: this.placement
        };
      }
    }
  ];

  var insertButton = function(target) {
    $(target.selector).each(function(i, el) {
      var $button = target.insert(el);
      $button.on('click', function(e) {
        e.preventDefault();
        e.stopImmediatePropagation();
        xt.port.emit('buffer_click', target.getData(el));
      })
    });
  };

  var insertButtons = function() {
    config.forEach(insertButton);
  };

  var pinterestLoop = function() {
    insertButtons();
    setTimeout(pinterestLoop, 500);
  };

  var start = function() {

    // Add class for css scoping
    document.body.classList.add('buffer-pinterest');

    // Start the loop that will watch for new DOM elements
    pinterestLoop();
  }

  // Wait for xt.options to be set
  ;(function check() {
    // If Pinterest is switched on, start the main loop
    if (!xt.options) {
      setTimeout(check, 0);
    } else if (xt.options['buffer.op.pinterest'] === 'pinterest') {
      start();
    } else {
      setTimeout(check, 2000);
    }
  }());

}());
<commit_msg>Remove logging<commit_after>;(function() {

  var config = [

    // Stream Pins
    {
      placement: 'pinterest-stream-pins',
      selector: '.Pin.Module:not(.buffer-inserted, .hideHoverUI)',
      $button: $([
        '
',
          '',
            '',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted');

        $actions
          .find('.pinImageActionButtonWrapper')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $img = $(el).find('.pinImg');
        var image = $img.attr('src');
        // Grab text from image alt attribute
        var text = $img.attr('alt');

        var $source = $(el).find('.pinNavLink');
        if($source.length === 0){
          $source = $(el).find('.pinDomain');
        }

        var source = $source.attr('href') || $source.text();
        return {
          text: text,
          url: source,
          picture: image,
          placement: this.placement
        };
      }
    },
    // Single Pins
    {
      placement: 'pinterest-single-pin',
      selector: '.PinActionBar:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $img = $(el).parents('.closeupActionBarContainer')
                        .siblings('.closeupContainer')
                        .find('.pinImage');

        var image = $img.attr('src');
        // Grab text from image alt attribute
        var text = $img.attr('alt');

        var $source = $(el).children('.repinLike').find('.NavigateButton.website');
        var source = $source.attr('href');

        return {
          text: text,
          url: source,
          picture: image,
          placement: this.placement
        };
      }
    },
    // Pin Action Popup Pin
    {
      placement: 'pinterest-pin-popup',
      selector: '.SelectList li.item .BoardLabel:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $img = $(el).parents('.boardsWrapper').siblings('.pinContainer').find('.pinImg');

        var image = $img.attr('src');
        // Grab text from image alt attribute
        var text = $img.attr('alt');

        var $source = $(el).parents('.boardsWrapper').siblings('.pinContainer').find('.pinDomain');
        var source = $source.text();

        if(!source){
          if(window.location.search){
            var params = window.location.search.split('&');

            var urlIndex = -1;
            for(var i = 0; i < params.length; i++){
              if(params[i].indexOf('url=') > -1){
                urlIndex = i;
                break;
              }
            }

            if(urlIndex > -1){
              source = decodeURIComponent(params[urlIndex].split("=")[1]);
            }
          }

        }

        return {
          text: text,
          url: source,
          picture: image,
          placement: this.placement
        };
      }
    }
  ];

  var insertButton = function(target) {
    $(target.selector).each(function(i, el) {
      var $button = target.insert(el);
      $button.on('click', function(e) {
        e.preventDefault();
        e.stopImmediatePropagation();
        xt.port.emit('buffer_click', target.getData(el));
      })
    });
  };

  var insertButtons = function() {
    config.forEach(insertButton);
  };

  var pinterestLoop = function() {
    insertButtons();
    setTimeout(pinterestLoop, 500);
  };

  var start = function() {

    // Add class for css scoping
    document.body.classList.add('buffer-pinterest');

    // Start the loop that will watch for new DOM elements
    pinterestLoop();
  }

  // Wait for xt.options to be set
  ;(function check() {
    // If Pinterest is switched on, start the main loop
    if (!xt.options) {
      setTimeout(check, 0);
    } else if (xt.options['buffer.op.pinterest'] === 'pinterest') {
      start();
    } else {
      setTimeout(check, 2000);
    }
  }());

}());
<|endoftext|>


<commit_before>;(function() {

  // Only run this script on tweetdeck:
  if ( window.location.host.indexOf('tweetdeck.twitter.com') !== 0 ) return;

  var conifg = [

    // Streams
    {
      placement: 'tweekdeck-stream',
      selector: '.js-stream-item .js-tweet-actions:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
            'Buffer ',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $streamItem = $(el).closest('.js-stream-item');

        var $text = $streamItem.find('.js-tweet-text');
        var $screenname = $streamItem.find('.username').first();
        var screenname = $screenname.text().trim().replace(/^@/, '');
        var text = getFullTweetText($text, screenname);
        var displayName = $streamItem.find('.fullname').first().text().trim();
        var tweetKey = $streamItem.attr('data-key');
        var $tweetAction;
        var retweeted_tweet_id;

        // Retweet, mention, favorite: non-regular tweet whose data-key attr
        // is formatted differently, and for which we need to get the tweet_id
        // from somewhere else
        if (/^(?:quoted_tweet|favorite|mention)/.test(tweetKey)) {
          $tweetAction = $streamItem.find('.tweet-action[data-user-id]');
          retweeted_tweet_id = $tweetAction.attr('data-chirp-id');
        // Regular tweet
        } else {
          retweeted_tweet_id = tweetKey;
        }

        return {
          text: text,
          placement: this.placement,
          retweeted_tweet_id: retweeted_tweet_id,
          retweeted_user_name: screenname,
          retweeted_user_display_name: displayName
        };
      }
    },

    // Tweet Detail
    {
      placement: 'tweetdeck-detail',
      selector: '.js-tweet-detail .tweet-detail-actions:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
            'Buffer ',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $streamItem = $(el).closest('.js-stream-item');

        var $text = $streamItem.find('.js-tweet-text');
        var $screenname = $streamItem.find('.username').first();
        var screenname = $screenname.text().trim().replace(/^@/, '');
        var text = getFullTweetText($text, screenname);
        var displayName = $streamItem.find('.fullname').first().text().trim();

        return {
          text: text,
          placement: this.placement,
          retweeted_tweet_id: $streamItem.attr('data-key'),
          retweeted_user_name: screenname,
          retweeted_user_display_name: displayName
        };
      }
    },

    // Composer slide-out
    {
      placement: 'tweetdeck-composer',
      selector: '.js-docked-compose .js-send-button-container:not(.buffer-inserted)',
      $button: $([
        '<button class="js-buffer-button btn-buffer is-disabled btn btn-extra-height">',
          'Buffer',
        '</button>'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        var $container = $(el).closest('.js-docked-compose');
        this.$newActionItem = $newActionItem;
        this.$textarea = $container.find('.js-compose-text');
        this.$charCount = $container.find('.js-character-count');

        // Listen to changes in the textarea
        this.$textarea.on('keyup focus blur change paste cut', this.onKeyup.bind(this));

        return $newActionItem;
      },
      onKeyup: function(e) {
        // NOTE - We check the live character count b/c the URL will be shortened
        var count = parseInt(this.$charCount.val(), 10);
        if (count < 140 && count >= 0) {
          this.$newActionItem.removeClass('is-disabled');
        } else {
          this.$newActionItem.addClass('is-disabled');
        }
      },
      getData: function(el) {
        var $container = $(el).closest('.js-docked-compose');
        var $quotedTweet = $container.find('.quoted-tweet');
        var isRTwcomment = !!$quotedTweet.length;
        var composerText = this.$textarea.val().trim();
        var $text;
        var username;

        if (isRTwcomment) {
          $text = $quotedTweet.find('.js-quoted-tweet-text');
          username = $quotedTweet.find('.username').text().trim().replace(/^@/, '');

          return {
            text: getFullTweetText($text, username),
            placement: this.placement,
            retweeted_tweet_id: $quotedTweet.attr('data-tweet-id'),
            retweeted_user_name: username,
            retweeted_user_display_name: $quotedTweet.find('.fullname').text().trim(),
            retweet_comment: composerText
          };
        } else {
          return {
            text: composerText,
            placement: this.placement
          };
        }
      }
    }

  ];

  // RT modal - This does not work on the loop
  var RTConfig = {
    placement: 'tweetdeck-rt-modal',
    selector: '#actions-modal .js-retweet-button:not(.buffer-inserted)',
    $button: $([
      '<button data-action="buffer" class="js-buffer-button btn btn-buffer">',
        'Buffer Retweet',
      '</button>'
    ].join('')),
    insert: function(el) {
      var $rtButton = $(el);
      var $newActionItem = this.$button.clone();

      $rtButton
        .addClass('buffer-inserted')
        .before($newActionItem)
        .before(' ');

      return $newActionItem;
    },
    getData: function(el) {
      var $tweet = $('#actions-modal .js-tweet');

      var $text = $tweet.find('.js-tweet-text');
      var $screenname = $tweet.find('.username');
      var screenname = $screenname.text().trim().replace(/^@/, '');
      var text = getFullTweetText($text, screenname);
      var displayName = $tweet.find('.fullname').text().trim();
      var tweetURL = $tweet.find('.js-timestamp a').attr('href');

      // NOTE - this is kind of a strict regex, may need to change if URLs change
      var tweetId = tweetURL.replace(/https:\/\/twitter.com\/\w+\/status\//, '');

      return {
        text: text,
        placement: this.placement,
        retweeted_tweet_id: tweetId,
        retweeted_user_name: screenname,
        retweeted_user_display_name: displayName
      };
    }
  };

  var getFullTweetText = function($text, screenname) {
    var $clone = $text.clone();

    // Replace any shortened URL with the full url
    $clone.find('a[data-full-url]').each(function(i, el) {
      el.textContent = el.getAttribute('data-full-url');
    });

    // Replace emotes with their unicode representation
    $clone.find('img.emoji').each(function(i, el) {
      $(el).replaceWith(el.getAttribute('alt'));
    });

    return 'RT @' + screenname + ': ' + $clone.text().trim() + '';
  };

  var insertButton = function(target) {
    $(target.selector).each(function(i, el) {
      var $button = target.insert(el);
      $button.on('click', function(e) {
        e.preventDefault();
        xt.port.emit('buffer_click', target.getData(el));
      })
    });
  };

  var insertButtons = function() {
    conifg.forEach(insertButton);
  };

  var tweetdeckLoop = function() {
    insertButtons();
    setTimeout(tweetdeckLoop, 500);
  };

  var currentStyleIndicator = document.head.querySelector('meta[http-equiv="Default-Style"]');

  var checkDarkTheme = function() {
    var isDarkThemeSelected = currentStyleIndicator.getAttribute('content') == 'dark';
    document.body.classList.toggle('buffer-tweetdeck-dark', isDarkThemeSelected);
  };

  var start = function() {

    // Add class for css scoping
    document.body.classList.add('buffer-tweetdeck');

    $(document).ready(function() {
      checkDarkTheme();

      // Observe future changes to the 'disabled' attribute of link[title=dark]
      // Useful if the app hasn't initialized, or if settings are changed later on
      if (window.MutationObserver) {
        var observer = new MutationObserver(checkDarkTheme);
        observer.observe(currentStyleIndicator, { attributes: true, attributeFilter: ['content'] });

        // Stop observing when add-on is disabled/removed (the detach event is Firefox-specific)
        xt.port.on('detach', function() {
          observer.disconnect();
        });
      }
    });

    // Start the loop that will watch for new DOM elements
    tweetdeckLoop();

    // Listen for a RT click so we can inject into the modal instantly
    // This addresses the potentially 500ms delay
    $('body').on('click',
      '.js-stream-item .js-tweet-actions .tweet-action[rel="retweet"],' +
      '.js-tweet-detail .tweet-detail-actions .tweet-detail-action[rel="retweet"]',
      function() {
        // 10ms delay for modal to open
        setTimeout(function() {
          insertButton(RTConfig);
        }, 10);
      });
  }

  // Wait for xt.options to be set
  ;(function check() {
    // If tweetdeck is switched on, start the main loop
    if (!xt.options) {
      setTimeout(check, 0);
    } else if (xt.options['buffer.op.tweetdeck'] === 'tweetdeck') {
      start();
    } else {
      setTimeout(check, 2000);
    }
  }());

}());
<commit_msg>Include link to tweet's image when Buffering from Tweetdeck<commit_after>;(function() {

  // Only run this script on tweetdeck:
  if ( window.location.host.indexOf('tweetdeck.twitter.com') !== 0 ) return;

  var conifg = [

    // Streams
    {
      placement: 'tweekdeck-stream',
      selector: '.js-stream-item .js-tweet-actions:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
            'Buffer ',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $streamItem = $(el).closest('.js-stream-item');

        var $text = $streamItem.find('.js-tweet-text');
        var $screenname = $streamItem.find('.username').first();
        var screenname = $screenname.text().trim().replace(/^@/, '');
        var text = getFullTweetText($text, screenname);
        var displayName = $streamItem.find('.fullname').first().text().trim();
        var tweetKey = $streamItem.attr('data-key');
        var $tweetAction;
        var retweeted_tweet_id;

        // Retweet, mention, favorite: non-regular tweet whose data-key attr
        // is formatted differently, and for which we need to get the tweet_id
        // from somewhere else
        if (/^(?:quoted_tweet|favorite|mention)/.test(tweetKey)) {
          $tweetAction = $streamItem.find('.tweet-action[data-user-id]');
          retweeted_tweet_id = $tweetAction.attr('data-chirp-id');
        // Regular tweet
        } else {
          retweeted_tweet_id = tweetKey;
        }

        return {
          text: text,
          placement: this.placement,
          retweeted_tweet_id: retweeted_tweet_id,
          retweeted_user_name: screenname,
          retweeted_user_display_name: displayName
        };
      }
    },

    // Tweet Detail
    {
      placement: 'tweetdeck-detail',
      selector: '.js-tweet-detail .tweet-detail-actions:not(.buffer-inserted)',
      $button: $([
        '
',
          '',
            '',
            'Buffer ',
          '',
        '
'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        return $newActionItem;
      },
      getData: function(el) {
        var $streamItem = $(el).closest('.js-stream-item');

        var $text = $streamItem.find('.js-tweet-text');
        var $screenname = $streamItem.find('.username').first();
        var screenname = $screenname.text().trim().replace(/^@/, '');
        var text = getFullTweetText($text, screenname);
        var displayName = $streamItem.find('.fullname').first().text().trim();

        return {
          text: text,
          placement: this.placement,
          retweeted_tweet_id: $streamItem.attr('data-key'),
          retweeted_user_name: screenname,
          retweeted_user_display_name: displayName
        };
      }
    },

    // Composer slide-out
    {
      placement: 'tweetdeck-composer',
      selector: '.js-docked-compose .js-send-button-container:not(.buffer-inserted)',
      $button: $([
        '<button class="js-buffer-button btn-buffer is-disabled btn btn-extra-height">',
          'Buffer',
        '</button>'
      ].join('')),
      insert: function(el) {
        var $actions = $(el);
        var $newActionItem = this.$button.clone();

        $actions
          .addClass('buffer-inserted')
          .prepend($newActionItem);

        var $container = $(el).closest('.js-docked-compose');
        this.$newActionItem = $newActionItem;
        this.$textarea = $container.find('.js-compose-text');
        this.$charCount = $container.find('.js-character-count');

        // Listen to changes in the textarea
        this.$textarea.on('keyup focus blur change paste cut', this.onKeyup.bind(this));

        return $newActionItem;
      },
      onKeyup: function(e) {
        // NOTE - We check the live character count b/c the URL will be shortened
        var count = parseInt(this.$charCount.val(), 10);
        if (count < 140 && count >= 0) {
          this.$newActionItem.removeClass('is-disabled');
        } else {
          this.$newActionItem.addClass('is-disabled');
        }
      },
      getData: function(el) {
        var $container = $(el).closest('.js-docked-compose');
        var $quotedTweet = $container.find('.quoted-tweet');
        var isRTwcomment = !!$quotedTweet.length;
        var composerText = this.$textarea.val().trim();
        var $text;
        var username;

        if (isRTwcomment) {
          $text = $quotedTweet.find('.js-quoted-tweet-text');
          username = $quotedTweet.find('.username').text().trim().replace(/^@/, '');

          return {
            text: getFullTweetText($text, username),
            placement: this.placement,
            retweeted_tweet_id: $quotedTweet.attr('data-tweet-id'),
            retweeted_user_name: username,
            retweeted_user_display_name: $quotedTweet.find('.fullname').text().trim(),
            retweet_comment: composerText
          };
        } else {
          return {
            text: composerText,
            placement: this.placement
          };
        }
      }
    }

  ];

  // RT modal - This does not work on the loop
  var RTConfig = {
    placement: 'tweetdeck-rt-modal',
    selector: '#actions-modal .js-retweet-button:not(.buffer-inserted)',
    $button: $([
      '<button data-action="buffer" class="js-buffer-button btn btn-buffer">',
        'Buffer Retweet',
      '</button>'
    ].join('')),
    insert: function(el) {
      var $rtButton = $(el);
      var $newActionItem = this.$button.clone();

      $rtButton
        .addClass('buffer-inserted')
        .before($newActionItem)
        .before(' ');

      return $newActionItem;
    },
    getData: function(el) {
      var $tweet = $('#actions-modal .js-tweet');

      var $text = $tweet.find('.js-tweet-text');
      var $screenname = $tweet.find('.username');
      var screenname = $screenname.text().trim().replace(/^@/, '');
      var text = getFullTweetText($text, screenname);
      var displayName = $tweet.find('.fullname').text().trim();
      var tweetURL = $tweet.find('.js-timestamp a').attr('href');

      // NOTE - this is kind of a strict regex, may need to change if URLs change
      var tweetId = tweetURL.replace(/https:\/\/twitter.com\/\w+\/status\//, '');

      return {
        text: text,
        placement: this.placement,
        retweeted_tweet_id: tweetId,
        retweeted_user_name: screenname,
        retweeted_user_display_name: displayName
      };
    }
  };

  var getFullTweetText = function($text, screenname) {
    var $clone = $text.clone();

    // Replace any shortened URL with the full url
    $clone.find('a[data-full-url]').each(function(i, el) {
      el.textContent = el.getAttribute('data-full-url');
    });

    // Replace emotes with their unicode representation
    $clone.find('img.emoji').each(function(i, el) {
      $(el).replaceWith(el.getAttribute('alt'));
    });

    var text = $clone.text().trim();

    // Append link to the tweet's image if there's an image displayed below
    var $imageBelow = $text.siblings('.js-media, .js-tweet-media').first();
    if ($imageBelow.length > 0) {
      var $link = $imageBelow.find('.js-media-image-link').first();
      if ($link.length > 0) text += ' ' + $link.attr('href');
    }

    return 'RT @' + screenname + ': ' + text + '';
  };

  var insertButton = function(target) {
    $(target.selector).each(function(i, el) {
      var $button = target.insert(el);
      $button.on('click', function(e) {
        e.preventDefault();
        xt.port.emit('buffer_click', target.getData(el));
      })
    });
  };

  var insertButtons = function() {
    conifg.forEach(insertButton);
  };

  var tweetdeckLoop = function() {
    insertButtons();
    setTimeout(tweetdeckLoop, 500);
  };

  var currentStyleIndicator = document.head.querySelector('meta[http-equiv="Default-Style"]');

  var checkDarkTheme = function() {
    var isDarkThemeSelected = currentStyleIndicator.getAttribute('content') == 'dark';
    document.body.classList.toggle('buffer-tweetdeck-dark', isDarkThemeSelected);
  };

  var start = function() {

    // Add class for css scoping
    document.body.classList.add('buffer-tweetdeck');

    $(document).ready(function() {
      checkDarkTheme();

      // Observe future changes to the 'disabled' attribute of link[title=dark]
      // Useful if the app hasn't initialized, or if settings are changed later on
      if (window.MutationObserver) {
        var observer = new MutationObserver(checkDarkTheme);
        observer.observe(currentStyleIndicator, { attributes: true, attributeFilter: ['content'] });

        // Stop observing when add-on is disabled/removed (the detach event is Firefox-specific)
        xt.port.on('detach', function() {
          observer.disconnect();
        });
      }
    });

    // Start the loop that will watch for new DOM elements
    tweetdeckLoop();

    // Listen for a RT click so we can inject into the modal instantly
    // This addresses the potentially 500ms delay
    $('body').on('click',
      '.js-stream-item .js-tweet-actions .tweet-action[rel="retweet"],' +
      '.js-tweet-detail .tweet-detail-actions .tweet-detail-action[rel="retweet"]',
      function() {
        // 10ms delay for modal to open
        setTimeout(function() {
          insertButton(RTConfig);
        }, 10);
      });
  }

  // Wait for xt.options to be set
  ;(function check() {
    // If tweetdeck is switched on, start the main loop
    if (!xt.options) {
      setTimeout(check, 0);
    } else if (xt.options['buffer.op.tweetdeck'] === 'tweetdeck') {
      start();
    } else {
      setTimeout(check, 2000);
    }
  }());

}());
<|endoftext|>


<commit_before>describe('the fahrenheit2celsius canary test', () => {
  it('shows the infrastructure works', () => {
    true.should.equal(true);
  });
});

describe('fahrenheit2celsius should', () => {
  it('give 0°C for 32°F');
  it('give 10°C for 50°F');
});
<commit_msg>fail fahrenheit2celsius test<commit_after>describe('the fahrenheit2celsius canary test', () => {
  it('shows the infrastructure works', () => {
    true.should.equal(true);
  });
});

describe('fahrenheit2celsius should', () => {
  it('give 0°C for 32°F', () => {
    const expected = fahrenheit2celsius(32);
    expected.should.equal(0);
  });
  it('give 10°C for 50°F');
});
<|endoftext|>


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

/*
 * The object graph files under /test/data are extracted using
 * web-apis (https://github.com/mdittmer/web-apis). The test
 * specifications are defined in the API Result test cases sheet.
 **/

describe('WebAPI and api extractor', function() {
  let webCatalog;
  beforeEach(function(done) {
    let chrome56 = global.DATA.chrome56;
    let edge14 = global.DATA.edge14;
    let safari602 = global.DATA.safari602;
    let og = global.ObjectGraph;
    let extractor = org.chromium.apis.web.apiExtractor.create({});
    let apiImporter = org.chromium.apis.web.ApiImporter.create();
    let apiMatrix = org.chromium.apis.web.ApiMatrix
      .create({browserAPIs: apiImporter.browserAPIs});

    apiImporter.import('Chrome', '56', 'Windows', '10',
      extractor.extractWebCatalog(og.fromJSON(chrome56)));
    apiImporter.import('Edge', '14', 'Windows', '10',
        extractor.extractWebCatalog(og.fromJSON(edge14)));
    apiImporter.import('Safari', '602', 'OSX', '10',
        extractor.extractWebCatalog(og.fromJSON(safari602)));
    apiMatrix.toMatrix(['Chrome_56_Windows_10', 'Edge_14_Windows_10',
    'Safari_602_OSX_10']).then((webCatalogMatrix) =>{
      webCatalog = webCatalogMatrix;
      done();
    });
  });

  it('filters out constant primitive properties', function() {
    expect(webCatalog.CSSRule.CHARSET_RULE).toBeUndefined();
    expect(webCatalog.Math.PI).toBeUndefined();
  });
  it('contains capital non-constant properties', function() {
    // Constant value are not identified as all-capital value.
    expect(webCatalog.Document.URL).toEqual({
      Chrome_56_Windows_10: true,
      Edge_14_Windows_10: true,
      Safari_602_OSX_10: true,
    });
    expect(webCatalog.BiquadFilterNode.Q).toEqual({
      Chrome_56_Windows_10: true,
      Edge_14_Windows_10: true,
      Safari_602_OSX_10: true,
    });
  });
  describe('Window interface', function() {
    it(`contains first level objects`, function() {
      expect(webCatalog.Window.alert).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.Boolean).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.ApplePaySession).toEqual({
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.Math).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.MouseEvent).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
    });
    it(`contains first level objects that references to the same object
      as separate interfaecs`, function() {
        // In chrome, MediaStream is the same function as webkitMediaStream.
        expect(webCatalog.Window.MediaStream).toBeDefined();
        expect(webCatalog.Window.webkitMediaStream).toBeDefined();
      });
    it('does not contain non-interface objects', function() {
      // Chrome does not expose FontFace as global interface.
      expect(webCatalog.Window.FontFaceSet.
        Chrome_56_Windows_10).toBeUndefined;
    });
  });
  it('ignores built-in function properties on function instances',
    function() {
      expect(webCatalog.MouseEvent.name).toBeUndefined();
      expect(webCatalog.MouseEvent.caller).toBeUndefined();
      expect(webCatalog.MouseEvent.bind).toBeUndefined();
      expect(webCatalog.AnalyserNode.caller).toBeUndefined();
      expect(webCatalog.AnalyserNode.name).toBeUndefined();
      expect(webCatalog.AnalyserNode.bind).toBeUndefined();
    });
  it('captures built-in properties for Function and Object',
    function() {
      expect(webCatalog.Function.bind).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.apply).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.call).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.length).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.name).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.__defineGetter__).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.hasOwnProperty).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.toString).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.constructor).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
    });
});
<commit_msg>Change documentation: test data are extracted using object-graph-js<commit_after>/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

/*
 * The object graph files under /test/data are extracted using
 * object-graph-js (https://github.com/mdittmer/object-graph-js). The test
 * specifications are defined in the API Result test cases sheet.
 **/

describe('WebAPI and api extractor', function() {
  let webCatalog;
  beforeEach(function(done) {
    let chrome56 = global.DATA.chrome56;
    let edge14 = global.DATA.edge14;
    let safari602 = global.DATA.safari602;
    let og = global.ObjectGraph;
    let extractor = org.chromium.apis.web.apiExtractor.create({});
    let apiImporter = org.chromium.apis.web.ApiImporter.create();
    let apiMatrix = org.chromium.apis.web.ApiMatrix
      .create({browserAPIs: apiImporter.browserAPIs});

    apiImporter.import('Chrome', '56', 'Windows', '10',
      extractor.extractWebCatalog(og.fromJSON(chrome56)));
    apiImporter.import('Edge', '14', 'Windows', '10',
        extractor.extractWebCatalog(og.fromJSON(edge14)));
    apiImporter.import('Safari', '602', 'OSX', '10',
        extractor.extractWebCatalog(og.fromJSON(safari602)));
    apiMatrix.toMatrix(['Chrome_56_Windows_10', 'Edge_14_Windows_10',
    'Safari_602_OSX_10']).then((webCatalogMatrix) =>{
      webCatalog = webCatalogMatrix;
      done();
    });
  });

  it('filters out constant primitive properties', function() {
    expect(webCatalog.CSSRule.CHARSET_RULE).toBeUndefined();
    expect(webCatalog.Math.PI).toBeUndefined();
  });
  it('contains capital non-constant properties', function() {
    // Constant value are not identified as all-capital value.
    expect(webCatalog.Document.URL).toEqual({
      Chrome_56_Windows_10: true,
      Edge_14_Windows_10: true,
      Safari_602_OSX_10: true,
    });
    expect(webCatalog.BiquadFilterNode.Q).toEqual({
      Chrome_56_Windows_10: true,
      Edge_14_Windows_10: true,
      Safari_602_OSX_10: true,
    });
  });
  describe('Window interface', function() {
    it(`contains first level objects`, function() {
      expect(webCatalog.Window.alert).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.Boolean).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.ApplePaySession).toEqual({
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.Math).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Window.MouseEvent).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
    });
    it(`contains first level objects that references to the same object
      as separate interfaecs`, function() {
        // In chrome, MediaStream is the same function as webkitMediaStream.
        expect(webCatalog.Window.MediaStream).toBeDefined();
        expect(webCatalog.Window.webkitMediaStream).toBeDefined();
      });
    it('does not contain non-interface objects', function() {
      // Chrome does not expose FontFace as global interface.
      expect(webCatalog.Window.FontFaceSet.
        Chrome_56_Windows_10).toBeUndefined;
    });
  });
  it('ignores built-in function properties on function instances',
    function() {
      expect(webCatalog.MouseEvent.name).toBeUndefined();
      expect(webCatalog.MouseEvent.caller).toBeUndefined();
      expect(webCatalog.MouseEvent.bind).toBeUndefined();
      expect(webCatalog.AnalyserNode.caller).toBeUndefined();
      expect(webCatalog.AnalyserNode.name).toBeUndefined();
      expect(webCatalog.AnalyserNode.bind).toBeUndefined();
    });
  it('captures built-in properties for Function and Object',
    function() {
      expect(webCatalog.Function.bind).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.apply).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.call).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.length).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Function.name).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.__defineGetter__).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.hasOwnProperty).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.toString).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
      expect(webCatalog.Object.constructor).toEqual({
        Chrome_56_Windows_10: true,
        Edge_14_Windows_10: true,
        Safari_602_OSX_10: true,
      });
    });
});
<|endoftext|>


<commit_before>const db = require('../../../database/schemas.js');

module.exports = {
  getOne: (req, res) => {
    db.Post.findOne({
      where: {
        id: req.params.listId,
      },
    })
    .then((listing) => {
      res.send(listing);
    });
  },

  getAll: (req, res) => {
    db.Post.findAll({})
    .then((listings) => {
      res.send(listings);
    });
  },

  createOne: (req, res) => {
    db.Post.create({
      body: req.body.body,
      tags: req.body.tags,
      user_id: req.params.id,
    })
    .then((listing) => {
      res.send(listing);
    });
  },

  patchOne: (req, res) => {

  },

  deleteOne: (req, res) => {

  },

  getAllPhotos: (req, res) => {

  },

  createOnePhoto: (req, res) => {

  },

  patchOnePhoto: (req, res) => {

  },

  deleteOnePhoto: (req, res) => {

  },
};
<commit_msg>Implement the patchOne callback for listingController. Close #50<commit_after>const db = require('../../../database/schemas.js');

module.exports = {
  getOne: (req, res) => {
    db.Post.findOne({
      where: {
        id: req.params.listId,
      },
    })
    .then((listing) => {
      res.send(listing);
    });
  },

  getAll: (req, res) => {
    db.Post.findAll({})
    .then((listings) => {
      res.send(listings);
    });
  },

  createOne: (req, res) => {
    db.Post.create({
      body: req.body.body,
      tags: req.body.tags,
      user_id: req.params.id,
    })
    .then((listing) => {
      res.send(listing);
    });
  },

  patchOne: (req, res) => {
    db.Post.update(req.body, {
      where: {
        id: req.params.listId,
      },
    }).then((status) => {
      res.send(status);
    });
  },

  deleteOne: (req, res) => {

  },

  getAllPhotos: (req, res) => {

  },

  createOnePhoto: (req, res) => {

  },

  patchOnePhoto: (req, res) => {

  },

  deleteOnePhoto: (req, res) => {

  },
};
<|endoftext|>


<commit_before>const DrawCard = require('../../drawcard.js');

class BentensTouch extends DrawCard {
    setupCardAbilities(ability) {
    }
}

BentensTouch.id = 'benten-s-touch'; // This is a guess at what the id might be - please check it!!!

module.exports = BentensTouch;
<commit_msg>Added Benten's Touch<commit_after>const DrawCard = require('../../drawcard.js');

class BentensTouch extends DrawCard {
    setupCardAbilities(ability) {
        this.action({
            title: 'Bow and Honor a character',
            condition: () => this.game.currentConflict,
            cost: ability.costs.bow(card => card.location === 'play area' && card.hasTrait('shugenja')),
            target: {
                cardType: 'character',
                activePromptTitle: 'Choose a character to honor',
                cardCondition: card => card.isParticipating() && !card.isHonored && card.controller === this.controller
            },
            handler: context => {
                this.game.addMessage('{0} uses {1} to bow {2} and honor {3}', this.controller, this, context.costs.bow, context.target);
                this.controller.honorCard(context.target, this);
            }
        });
    }
}

BentensTouch.id = 'benten-s-touch';

module.exports = BentensTouch;
<|endoftext|>


<commit_before>const DrawCard = require('../../drawcard.js');

class BeggingBrother extends DrawCard {
    setupCardAbilities(ability) {
        this.interrupt({
            canCancel: true,
            when: {
                onCardAbilityInitiated: event => event.source.getType() === 'character' && event.player !== this.controller
            },
            cost: ability.costs.discardGold(),
            handler: context => {
                context.event.cancel();
                this.game.addMessage('{0} discards 1 gold from {1} to cancel {2}', this.controller, this, context.event.source);
            }
        });
    }
}

BeggingBrother.code = '06097';

module.exports = BeggingBrother;
<commit_msg>Allow Begging Brothers to cancel own abilities (#1479)<commit_after>const DrawCard = require('../../drawcard.js');

class BeggingBrother extends DrawCard {
    setupCardAbilities(ability) {
        this.interrupt({
            canCancel: true,
            when: {
                //Nested cancels can get quite disorienting as this can cancel itself too, so limiting it to opponent's Begging Brother only
                onCardAbilityInitiated: event => event.source.getType() === 'character' && (event.source.name !== this.name || event.source.controller !== this.controller)
            },
            cost: ability.costs.discardGold(),
            handler: context => {
                context.event.cancel();
                this.game.addMessage('{0} discards 1 gold from {1} to cancel {2}', this.controller, this, context.event.source);
            }
        });
    }
}

BeggingBrother.code = '06097';

module.exports = BeggingBrother;
<|endoftext|>


<commit_before>/* global $, Strophe */

import { getLogger } from 'jitsi-meet-logger';
const logger = getLogger(__filename);

import RandomUtil from '../util/RandomUtil';
import * as JitsiConnectionErrors from '../../JitsiConnectionErrors';
import * as JitsiConnectionEvents from '../../JitsiConnectionEvents';
import RTCBrowserType from '../RTC/RTCBrowserType';
import initEmuc from './strophe.emuc';
import initJingle from './strophe.jingle';
import initStropheUtil from './strophe.util';
import initPing from './strophe.ping';
import initRayo from './strophe.rayo';
import initStropheLogger from './strophe.logger';
import Listenable from '../util/Listenable';
import Caps from './Caps';

/**
 *
 * @param token
 * @param bosh
 */
function createConnection(token, bosh = '/http-bind') {
    // Append token as URL param
    if (token) {
        // eslint-disable-next-line no-param-reassign
        bosh += `${bosh.indexOf('?') === -1 ? '?' : '&'}token=${token}`;
    }

    return new Strophe.Connection(bosh);
}

/**
 *
 */
export default class XMPP extends Listenable {
    /**
     * FIXME describe all options
     * @param {Object} options
     * @param {Array<Object>} options.p2pStunServers see
     * {@link JingleConnectionPlugin} for more details.
     * @param token
     */
    constructor(options, token) {
        super();
        this.connection = null;
        this.disconnectInProgress = false;
        this.connectionTimes = {};
        this.forceMuted = false;
        this.options = options;
        this.connectParams = {};
        this.token = token;
        this.authenticatedUser = false;
        this._initStrophePlugins(this);

        this.connection = createConnection(token, options.bosh);

        this.caps = new Caps(this.connection, this.options.clientNode);

        // Initialize features advertised in disco-info
        this.initFeaturesList();

        // Setup a disconnect on unload as a way to facilitate API consumers. It
        // sounds like they would want that. A problem for them though may be if
        // they wanted to utilize the connected connection in an unload handler
        // of their own. However, it should be fairly easy for them to do that
        // by registering their unload handler before us.
        $(window).on('beforeunload unload', this.disconnect.bind(this));
    }

    /**
     * Initializes the list of feature advertised through the disco-info
     * mechanism.
     */
    initFeaturesList() {
        // http://xmpp.org/extensions/xep-0167.html#support
        // http://xmpp.org/extensions/xep-0176.html#support
        this.caps.addFeature('urn:xmpp:jingle:1');
        this.caps.addFeature('urn:xmpp:jingle:apps:rtp:1');
        this.caps.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
        this.caps.addFeature('urn:xmpp:jingle:apps:dtls:0');
        this.caps.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
        this.caps.addFeature('urn:xmpp:jingle:apps:rtp:audio');
        this.caps.addFeature('urn:xmpp:jingle:apps:rtp:video');

        if (!this.options.disableRtx && RTCBrowserType.supportsRtx()) {
            this.caps.addFeature('urn:ietf:rfc:4588');
        }

        // this is dealt with by SDP O/A so we don't need to announce this
        // XEP-0293
        // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0');
        // XEP-0294
        // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0');

        this.caps.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
        this.caps.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle

        // this.caps.addFeature('urn:ietf:rfc:5576'); // a=ssrc

        // Enable Lipsync ?
        if (RTCBrowserType.isChrome() && this.options.enableLipSync !== false) {
            logger.info('Lip-sync enabled !');
            this.caps.addFeature('http://jitsi.org/meet/lipsync');
        }

        if (this.connection.rayo) {
            this.caps.addFeature('urn:xmpp:rayo:client:1');
        }
    }

    /**
     *
     */
    getConnection() {
        return this.connection;
    }

    /**
     * Receive connection status changes and handles them.
     * @password {string} the password passed in connect method
     * @status the connection status
     * @msg message
     */
    connectionHandler(password, status, msg) {
        const now = window.performance.now();
        const statusStr = Strophe.getStatusString(status).toLowerCase();

        this.connectionTimes[statusStr] = now;
        logger.log(
            `(TIME) Strophe ${statusStr}${msg ? `[${msg}]` : ''}:\t`,
            now);
        if (status === Strophe.Status.CONNECTED
            || status === Strophe.Status.ATTACHED) {
            if (this.options.useStunTurn
                || (this.options.p2p && this.options.p2p.useStunTurn)) {
                this.connection.jingle.getStunAndTurnCredentials();
            }

            logger.info(`My Jabber ID: ${this.connection.jid}`);

            // Schedule ping ?
            const pingJid = this.connection.domain;

            this.connection.ping.hasPingSupport(
                pingJid,
                hasPing => {
                    if (hasPing) {
                        this.connection.ping.startInterval(pingJid);
                    } else {
                        logger.warn(`Ping NOT supported by ${pingJid}`);
                    }
                });

            if (password) {
                this.authenticatedUser = true;
            }
            if (this.connection && this.connection.connected
                && Strophe.getResourceFromJid(this.connection.jid)) {
                // .connected is true while connecting?
    //                this.connection.send($pres());
                this.eventEmitter.emit(
                        JitsiConnectionEvents.CONNECTION_ESTABLISHED,
                        Strophe.getResourceFromJid(this.connection.jid));
            }
        } else if (status === Strophe.Status.CONNFAIL) {
            if (msg === 'x-strophe-bad-non-anon-jid') {
                this.anonymousConnectionFailed = true;
            } else {
                this.connectionFailed = true;
            }
            this.lastErrorMsg = msg;
        } else if (status === Strophe.Status.DISCONNECTED) {
            // Stop ping interval
            this.connection.ping.stopInterval();
            const wasIntentionalDisconnect = this.disconnectInProgress;
            const errMsg = msg ? msg : this.lastErrorMsg;

            this.disconnectInProgress = false;
            if (this.anonymousConnectionFailed) {
                // prompt user for username and password
                this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_FAILED,
                    JitsiConnectionErrors.PASSWORD_REQUIRED);
            } else if (this.connectionFailed) {
                this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_FAILED,
                    JitsiConnectionErrors.OTHER_ERROR, errMsg);
            } else if (wasIntentionalDisconnect) {
                this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_DISCONNECTED, errMsg);
            } else {
                // XXX if Strophe drops the connection while not being asked to,
                // it means that most likely some serious error has occurred.
                // One currently known case is when a BOSH request fails for
                // more than 4 times. The connection is dropped without
                // supplying a reason(error message/event) through the API.
                logger.error('XMPP connection dropped!');

                // XXX if the last request error is within 5xx range it means it
                // was a server failure
                const lastErrorStatus = Strophe.getLastErrorStatus();

                if (lastErrorStatus >= 500 && lastErrorStatus < 600) {
                    this.eventEmitter.emit(
                        JitsiConnectionEvents.CONNECTION_FAILED,
                        JitsiConnectionErrors.SERVER_ERROR,
                        errMsg ? errMsg : 'server-error');
                } else {
                    this.eventEmitter.emit(
                        JitsiConnectionEvents.CONNECTION_FAILED,
                        JitsiConnectionErrors.CONNECTION_DROPPED_ERROR,
                        errMsg ? errMsg : 'connection-dropped-error');
                }
            }
        } else if (status === Strophe.Status.AUTHFAIL) {
            // wrong password or username, prompt user
            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
                JitsiConnectionErrors.PASSWORD_REQUIRED);

        }
    }

    /**
     *
     * @param jid
     * @param password
     */
    _connect(jid, password) {
        // connection.connect() starts the connection process.
        //
        // As the connection process proceeds, the user supplied callback will
        // be triggered multiple times with status updates. The callback should
        // take two arguments - the status code and the error condition.
        //
        // The status code will be one of the values in the Strophe.Status
        // constants. The error condition will be one of the conditions defined
        // in RFC 3920 or the condition ‘strophe-parsererror’.
        //
        // The Parameters wait, hold and route are optional and only relevant
        // for BOSH connections. Please see XEP 124 for a more detailed
        // explanation of the optional parameters.
        //
        // Connection status constants for use by the connection handler
        // callback.
        //
        //  Status.ERROR - An error has occurred (websockets specific)
        //  Status.CONNECTING - The connection is currently being made
        //  Status.CONNFAIL - The connection attempt failed
        //  Status.AUTHENTICATING - The connection is authenticating
        //  Status.AUTHFAIL - The authentication attempt failed
        //  Status.CONNECTED - The connection has succeeded
        //  Status.DISCONNECTED - The connection has been terminated
        //  Status.DISCONNECTING - The connection is currently being terminated
        //  Status.ATTACHED - The connection has been attached

        this.anonymousConnectionFailed = false;
        this.connectionFailed = false;
        this.lastErrorMsg = undefined;
        this.connection.connect(jid, password,
            this.connectionHandler.bind(this, password));
    }

    /**
     * Attach to existing connection. Can be used for optimizations. For
     * example: if the connection is created on the server we can attach to it
     * and start using it.
     *
     * @param options {object} connecting options - rid, sid, jid and password.
     */
    attach(options) {
        const now = this.connectionTimes.attaching = window.performance.now();

        logger.log(`(TIME) Strophe Attaching\t:${now}`);
        this.connection.attach(options.jid, options.sid,
            parseInt(options.rid, 10) + 1,
            this.connectionHandler.bind(this, options.password));
    }

    /**
     *
     * @param jid
     * @param password
     */
    connect(jid, password) {
        this.connectParams = {
            jid,
            password
        };
        if (!jid) {
            let configDomain
                = this.options.hosts.anonymousdomain
                    || this.options.hosts.domain;

            // Force authenticated domain if room is appended with '?login=true'
            // or if we're joining with the token

            if (this.options.hosts.anonymousdomain
                    && (window.location.search.indexOf('login=true') !== -1
                        || this.token)) {
                configDomain = this.options.hosts.domain;
            }

            // eslint-disable-next-line no-param-reassign
            jid = configDomain || window.location.hostname;
        }

        return this._connect(jid, password);
    }

    /**
     *
     * @param roomName
     * @param options
     */
    createRoom(roomName, options) {
        // By default MUC nickname is the resource part of the JID
        let mucNickname = Strophe.getNodeFromJid(this.connection.jid);
        let roomjid = `${roomName}@${this.options.hosts.muc}/`;
        const cfgNickname
            = options.useNicks && options.nick ? options.nick : null;

        if (cfgNickname) {
            // Use nick if it's defined
            mucNickname = options.nick;
        } else if (!this.authenticatedUser) {
            // node of the anonymous JID is very long - here we trim it a bit
            mucNickname = mucNickname.substr(0, 8);
        }

        // Constant JIDs need some random part to be appended in order to be
        // able to join the MUC more than once.
        if (this.authenticatedUser || cfgNickname !== null) {
            mucNickname += `-${RandomUtil.randomHexString(6)}`;
        }

        roomjid += mucNickname;

        return this.connection.emuc.createRoom(roomjid, null, options);
    }

    /**
     * Returns the logs from strophe.jingle.
     * @returns {Object}
     */
    getJingleLog() {
        const jingle = this.connection.jingle;

        return jingle ? jingle.getLog() : {};
    }

    /**
     * Returns the logs from strophe.
     */
    getXmppLog() {
        return (this.connection.logger || {}).log || null;
    }

    /**
     *
     */
    dial(...args) {
        this.connection.rayo.dial(...args);
    }

    /**
     *
     * @param jid
     * @param mute
     */
    setMute(jid, mute) {
        this.connection.moderate.setMute(jid, mute);
    }

    /**
     *
     * @param jid
     */
    eject(jid) {
        this.connection.moderate.eject(jid);
    }

    /**
     *
     */
    getSessions() {
        return this.connection.jingle.sessions;
    }

    /**
     * Disconnects this from the XMPP server (if this is connected).
     *
     * @param ev optionally, the event which triggered the necessity to
     * disconnect from the XMPP server (e.g. beforeunload, unload).
     */
    disconnect(ev) {
        if (this.disconnectInProgress
                || !this.connection
                || !this.connection.connected) {
            this.eventEmitter.emit(JitsiConnectionEvents.WRONG_STATE);

            return;
        }

        this.disconnectInProgress = true;

        // XXX Strophe is asynchronously sending by default. Unfortunately, that
        // means that there may not be enough time to send an unavailable
        // presence or disconnect at all. Switching Strophe to synchronous
        // sending is not much of an option because it may lead to a noticeable
        // delay in navigating away from the current location. As a compromise,
        // we will try to increase the chances of sending an unavailable
        // presence and/or disconecting within the short time span that we have
        // upon unloading by invoking flush() on the connection. We flush() once
        // before disconnect() in order to attemtp to have its unavailable
        // presence at the top of the send queue. We flush() once more after
        // disconnect() in order to attempt to have its unavailable presence
        // sent as soon as possible.
        this.connection.flush();

        if (ev !== null && typeof ev !== 'undefined') {
            const evType = ev.type;

            if (evType === 'beforeunload' || evType === 'unload') {
                // XXX Whatever we said above, synchronous sending is the best
                // (known) way to properly disconnect from the XMPP server.
                // Consequently, it may be fine to have the source code and
                // comment it in or out depending on whether we want to run with
                // it for some time.
                this.connection.options.sync = true;
            }
        }

        this.connection.disconnect();

        if (this.connection.options.sync !== true) {
            this.connection.flush();
        }
    }

    /**
     *
     */
    _initStrophePlugins() {
        // FIXME: remove once we have a default config template. -saghul
        const defaultStunServers = [
            { urls: 'stun:stun.l.google.com:19302' },
            { urls: 'stun:stun1.l.google.com:19302' },
            { urls: 'stun:stun2.l.google.com:19302' }
        ];
        const p2pStunServers = (this.options.p2p
            && this.options.p2p.stunServers) || defaultStunServers;

        initEmuc(this);
        initJingle(this, this.eventEmitter, p2pStunServers);
        initStropheUtil();
        initPing(this);
        initRayo();
        initStropheLogger();
    }
}
<commit_msg>[RN] Do not mandate window.location<commit_after>/* global $, Strophe */

import { getLogger } from 'jitsi-meet-logger';
const logger = getLogger(__filename);

import RandomUtil from '../util/RandomUtil';
import * as JitsiConnectionErrors from '../../JitsiConnectionErrors';
import * as JitsiConnectionEvents from '../../JitsiConnectionEvents';
import RTCBrowserType from '../RTC/RTCBrowserType';
import initEmuc from './strophe.emuc';
import initJingle from './strophe.jingle';
import initStropheUtil from './strophe.util';
import initPing from './strophe.ping';
import initRayo from './strophe.rayo';
import initStropheLogger from './strophe.logger';
import Listenable from '../util/Listenable';
import Caps from './Caps';

/**
 *
 * @param token
 * @param bosh
 */
function createConnection(token, bosh = '/http-bind') {
    // Append token as URL param
    if (token) {
        // eslint-disable-next-line no-param-reassign
        bosh += `${bosh.indexOf('?') === -1 ? '?' : '&'}token=${token}`;
    }

    return new Strophe.Connection(bosh);
}

/**
 *
 */
export default class XMPP extends Listenable {
    /**
     * FIXME describe all options
     * @param {Object} options
     * @param {Array<Object>} options.p2pStunServers see
     * {@link JingleConnectionPlugin} for more details.
     * @param token
     */
    constructor(options, token) {
        super();
        this.connection = null;
        this.disconnectInProgress = false;
        this.connectionTimes = {};
        this.forceMuted = false;
        this.options = options;
        this.connectParams = {};
        this.token = token;
        this.authenticatedUser = false;
        this._initStrophePlugins(this);

        this.connection = createConnection(token, options.bosh);

        this.caps = new Caps(this.connection, this.options.clientNode);

        // Initialize features advertised in disco-info
        this.initFeaturesList();

        // Setup a disconnect on unload as a way to facilitate API consumers. It
        // sounds like they would want that. A problem for them though may be if
        // they wanted to utilize the connected connection in an unload handler
        // of their own. However, it should be fairly easy for them to do that
        // by registering their unload handler before us.
        $(window).on('beforeunload unload', this.disconnect.bind(this));
    }

    /**
     * Initializes the list of feature advertised through the disco-info
     * mechanism.
     */
    initFeaturesList() {
        // http://xmpp.org/extensions/xep-0167.html#support
        // http://xmpp.org/extensions/xep-0176.html#support
        this.caps.addFeature('urn:xmpp:jingle:1');
        this.caps.addFeature('urn:xmpp:jingle:apps:rtp:1');
        this.caps.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
        this.caps.addFeature('urn:xmpp:jingle:apps:dtls:0');
        this.caps.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
        this.caps.addFeature('urn:xmpp:jingle:apps:rtp:audio');
        this.caps.addFeature('urn:xmpp:jingle:apps:rtp:video');

        if (!this.options.disableRtx && RTCBrowserType.supportsRtx()) {
            this.caps.addFeature('urn:ietf:rfc:4588');
        }

        // this is dealt with by SDP O/A so we don't need to announce this
        // XEP-0293
        // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0');
        // XEP-0294
        // this.caps.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0');

        this.caps.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
        this.caps.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle

        // this.caps.addFeature('urn:ietf:rfc:5576'); // a=ssrc

        // Enable Lipsync ?
        if (RTCBrowserType.isChrome() && this.options.enableLipSync !== false) {
            logger.info('Lip-sync enabled !');
            this.caps.addFeature('http://jitsi.org/meet/lipsync');
        }

        if (this.connection.rayo) {
            this.caps.addFeature('urn:xmpp:rayo:client:1');
        }
    }

    /**
     *
     */
    getConnection() {
        return this.connection;
    }

    /**
     * Receive connection status changes and handles them.
     * @password {string} the password passed in connect method
     * @status the connection status
     * @msg message
     */
    connectionHandler(password, status, msg) {
        const now = window.performance.now();
        const statusStr = Strophe.getStatusString(status).toLowerCase();

        this.connectionTimes[statusStr] = now;
        logger.log(
            `(TIME) Strophe ${statusStr}${msg ? `[${msg}]` : ''}:\t`,
            now);
        if (status === Strophe.Status.CONNECTED
            || status === Strophe.Status.ATTACHED) {
            if (this.options.useStunTurn
                || (this.options.p2p && this.options.p2p.useStunTurn)) {
                this.connection.jingle.getStunAndTurnCredentials();
            }

            logger.info(`My Jabber ID: ${this.connection.jid}`);

            // Schedule ping ?
            const pingJid = this.connection.domain;

            this.connection.ping.hasPingSupport(
                pingJid,
                hasPing => {
                    if (hasPing) {
                        this.connection.ping.startInterval(pingJid);
                    } else {
                        logger.warn(`Ping NOT supported by ${pingJid}`);
                    }
                });

            if (password) {
                this.authenticatedUser = true;
            }
            if (this.connection && this.connection.connected
                && Strophe.getResourceFromJid(this.connection.jid)) {
                // .connected is true while connecting?
    //                this.connection.send($pres());
                this.eventEmitter.emit(
                        JitsiConnectionEvents.CONNECTION_ESTABLISHED,
                        Strophe.getResourceFromJid(this.connection.jid));
            }
        } else if (status === Strophe.Status.CONNFAIL) {
            if (msg === 'x-strophe-bad-non-anon-jid') {
                this.anonymousConnectionFailed = true;
            } else {
                this.connectionFailed = true;
            }
            this.lastErrorMsg = msg;
        } else if (status === Strophe.Status.DISCONNECTED) {
            // Stop ping interval
            this.connection.ping.stopInterval();
            const wasIntentionalDisconnect = this.disconnectInProgress;
            const errMsg = msg ? msg : this.lastErrorMsg;

            this.disconnectInProgress = false;
            if (this.anonymousConnectionFailed) {
                // prompt user for username and password
                this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_FAILED,
                    JitsiConnectionErrors.PASSWORD_REQUIRED);
            } else if (this.connectionFailed) {
                this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_FAILED,
                    JitsiConnectionErrors.OTHER_ERROR, errMsg);
            } else if (wasIntentionalDisconnect) {
                this.eventEmitter.emit(
                    JitsiConnectionEvents.CONNECTION_DISCONNECTED, errMsg);
            } else {
                // XXX if Strophe drops the connection while not being asked to,
                // it means that most likely some serious error has occurred.
                // One currently known case is when a BOSH request fails for
                // more than 4 times. The connection is dropped without
                // supplying a reason(error message/event) through the API.
                logger.error('XMPP connection dropped!');

                // XXX if the last request error is within 5xx range it means it
                // was a server failure
                const lastErrorStatus = Strophe.getLastErrorStatus();

                if (lastErrorStatus >= 500 && lastErrorStatus < 600) {
                    this.eventEmitter.emit(
                        JitsiConnectionEvents.CONNECTION_FAILED,
                        JitsiConnectionErrors.SERVER_ERROR,
                        errMsg ? errMsg : 'server-error');
                } else {
                    this.eventEmitter.emit(
                        JitsiConnectionEvents.CONNECTION_FAILED,
                        JitsiConnectionErrors.CONNECTION_DROPPED_ERROR,
                        errMsg ? errMsg : 'connection-dropped-error');
                }
            }
        } else if (status === Strophe.Status.AUTHFAIL) {
            // wrong password or username, prompt user
            this.eventEmitter.emit(JitsiConnectionEvents.CONNECTION_FAILED,
                JitsiConnectionErrors.PASSWORD_REQUIRED);

        }
    }

    /**
     *
     * @param jid
     * @param password
     */
    _connect(jid, password) {
        // connection.connect() starts the connection process.
        //
        // As the connection process proceeds, the user supplied callback will
        // be triggered multiple times with status updates. The callback should
        // take two arguments - the status code and the error condition.
        //
        // The status code will be one of the values in the Strophe.Status
        // constants. The error condition will be one of the conditions defined
        // in RFC 3920 or the condition ‘strophe-parsererror’.
        //
        // The Parameters wait, hold and route are optional and only relevant
        // for BOSH connections. Please see XEP 124 for a more detailed
        // explanation of the optional parameters.
        //
        // Connection status constants for use by the connection handler
        // callback.
        //
        //  Status.ERROR - An error has occurred (websockets specific)
        //  Status.CONNECTING - The connection is currently being made
        //  Status.CONNFAIL - The connection attempt failed
        //  Status.AUTHENTICATING - The connection is authenticating
        //  Status.AUTHFAIL - The authentication attempt failed
        //  Status.CONNECTED - The connection has succeeded
        //  Status.DISCONNECTED - The connection has been terminated
        //  Status.DISCONNECTING - The connection is currently being terminated
        //  Status.ATTACHED - The connection has been attached

        this.anonymousConnectionFailed = false;
        this.connectionFailed = false;
        this.lastErrorMsg = undefined;
        this.connection.connect(jid, password,
            this.connectionHandler.bind(this, password));
    }

    /**
     * Attach to existing connection. Can be used for optimizations. For
     * example: if the connection is created on the server we can attach to it
     * and start using it.
     *
     * @param options {object} connecting options - rid, sid, jid and password.
     */
    attach(options) {
        const now = this.connectionTimes.attaching = window.performance.now();

        logger.log(`(TIME) Strophe Attaching\t:${now}`);
        this.connection.attach(options.jid, options.sid,
            parseInt(options.rid, 10) + 1,
            this.connectionHandler.bind(this, options.password));
    }

    /**
     *
     * @param jid
     * @param password
     */
    connect(jid, password) {
        this.connectParams = {
            jid,
            password
        };
        if (!jid) {
            const { anonymousdomain, domain } = this.options.hosts;
            let configDomain = anonymousdomain || domain;

            // Force authenticated domain if room is appended with '?login=true'
            // or if we're joining with the token

            // FIXME Do not rely on window.location because (1) React Native
            // does not have a window.location by default and (2) here we cannot
            // know for sure that query/search has not be stripped from
            // window.location by the time the following executes.
            const { location } = window;

            if (anonymousdomain) {
                const search = location && location.search;

                if ((search && search.indexOf('login=true') !== -1)
                        || this.token) {
                    configDomain = domain;
                }
            }

            // eslint-disable-next-line no-param-reassign
            jid = configDomain || (location && location.hostname);
        }

        return this._connect(jid, password);
    }

    /**
     *
     * @param roomName
     * @param options
     */
    createRoom(roomName, options) {
        // By default MUC nickname is the resource part of the JID
        let mucNickname = Strophe.getNodeFromJid(this.connection.jid);
        let roomjid = `${roomName}@${this.options.hosts.muc}/`;
        const cfgNickname
            = options.useNicks && options.nick ? options.nick : null;

        if (cfgNickname) {
            // Use nick if it's defined
            mucNickname = options.nick;
        } else if (!this.authenticatedUser) {
            // node of the anonymous JID is very long - here we trim it a bit
            mucNickname = mucNickname.substr(0, 8);
        }

        // Constant JIDs need some random part to be appended in order to be
        // able to join the MUC more than once.
        if (this.authenticatedUser || cfgNickname !== null) {
            mucNickname += `-${RandomUtil.randomHexString(6)}`;
        }

        roomjid += mucNickname;

        return this.connection.emuc.createRoom(roomjid, null, options);
    }

    /**
     * Returns the logs from strophe.jingle.
     * @returns {Object}
     */
    getJingleLog() {
        const jingle = this.connection.jingle;

        return jingle ? jingle.getLog() : {};
    }

    /**
     * Returns the logs from strophe.
     */
    getXmppLog() {
        return (this.connection.logger || {}).log || null;
    }

    /**
     *
     */
    dial(...args) {
        this.connection.rayo.dial(...args);
    }

    /**
     *
     * @param jid
     * @param mute
     */
    setMute(jid, mute) {
        this.connection.moderate.setMute(jid, mute);
    }

    /**
     *
     * @param jid
     */
    eject(jid) {
        this.connection.moderate.eject(jid);
    }

    /**
     *
     */
    getSessions() {
        return this.connection.jingle.sessions;
    }

    /**
     * Disconnects this from the XMPP server (if this is connected).
     *
     * @param ev optionally, the event which triggered the necessity to
     * disconnect from the XMPP server (e.g. beforeunload, unload).
     */
    disconnect(ev) {
        if (this.disconnectInProgress
                || !this.connection
                || !this.connection.connected) {
            this.eventEmitter.emit(JitsiConnectionEvents.WRONG_STATE);

            return;
        }

        this.disconnectInProgress = true;

        // XXX Strophe is asynchronously sending by default. Unfortunately, that
        // means that there may not be enough time to send an unavailable
        // presence or disconnect at all. Switching Strophe to synchronous
        // sending is not much of an option because it may lead to a noticeable
        // delay in navigating away from the current location. As a compromise,
        // we will try to increase the chances of sending an unavailable
        // presence and/or disconecting within the short time span that we have
        // upon unloading by invoking flush() on the connection. We flush() once
        // before disconnect() in order to attemtp to have its unavailable
        // presence at the top of the send queue. We flush() once more after
        // disconnect() in order to attempt to have its unavailable presence
        // sent as soon as possible.
        this.connection.flush();

        if (ev !== null && typeof ev !== 'undefined') {
            const evType = ev.type;

            if (evType === 'beforeunload' || evType === 'unload') {
                // XXX Whatever we said above, synchronous sending is the best
                // (known) way to properly disconnect from the XMPP server.
                // Consequently, it may be fine to have the source code and
                // comment it in or out depending on whether we want to run with
                // it for some time.
                this.connection.options.sync = true;
            }
        }

        this.connection.disconnect();

        if (this.connection.options.sync !== true) {
            this.connection.flush();
        }
    }

    /**
     *
     */
    _initStrophePlugins() {
        // FIXME: remove once we have a default config template. -saghul
        const defaultStunServers = [
            { urls: 'stun:stun.l.google.com:19302' },
            { urls: 'stun:stun1.l.google.com:19302' },
            { urls: 'stun:stun2.l.google.com:19302' }
        ];
        const p2pStunServers = (this.options.p2p
            && this.options.p2p.stunServers) || defaultStunServers;

        initEmuc(this);
        initJingle(this, this.eventEmitter, p2pStunServers);
        initStropheUtil();
        initPing(this);
        initRayo();
        initStropheLogger();
    }
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import BaseEditFormController from 'ember-flexberry/controllers/edit-form';
import EditFormControllerOperationsIndicationMixin from 'ember-flexberry/mixins/edit-form-controller-operations-indication';

export default BaseEditFormController.extend(EditFormControllerOperationsIndicationMixin, {
  /**
    Route name for transition after close edit form.

    @property parentRoute
    @type String
    @default 'ember-flexberry-dummy-suggestion-list'
   */
  parentRoute: 'components-examples/flexberry-objectlistview/return-with-query-params/ember-flexberry-dummy-suggestion-return-with-query-params-list',

  /**
    Name of model.comments edit route.

    @property commentsEditRoute
    @type String
    @default 'ember-flexberry-dummy-comment-edit'
   */
  commentsEditRoute: 'ember-flexberry-dummy-comment-edit',

  /**
    Method to get type and attributes of a component,
    which will be embeded in object-list-view cell.

    @method getCellComponent.
    @param {Object} attr Attribute of projection property related to current table cell.
    @param {String} bindingPath Path to model property related to current table cell.
    @param {DS.Model} modelClass Model class of data record related to current table row.
    @return {Object} Object containing name & properties of component, which will be used to render current table cell.
    { componentName: 'my-component',  componentProperties: { ... } }.
   */
  getCellComponent(attr, bindingPath, model) {
    let cellComponent = this._super(...arguments);
    if (attr.kind === 'belongsTo') {
      switch (`${model.modelName}+${bindingPath}`) {
        case 'ember-flexberry-dummy-vote+author':
          cellComponent.componentProperties = {
            choose: 'showLookupDialog',
            remove: 'removeLookupValue',
            displayAttributeName: 'name',
            required: true,
            relationName: 'author',
            projection: 'ApplicationUserL',
            autocomplete: true,
          };
          break;

        case 'ember-flexberry-dummy-comment+author':
          cellComponent.componentProperties = {
            choose: 'showLookupDialog',
            remove: 'removeLookupValue',
            displayAttributeName: 'name',
            required: true,
            relationName: 'author',
            projection: 'ApplicationUserL',
            autocomplete: true,
          };
          break;

      }
    }

    return cellComponent;
  },

  actions: {
    /**
      This method set controller.limitFunction for olv window.
      @method onButtonClick
     */
    onButtonClick() {
      this._router = Ember.getOwner(this).lookup('router:main');
      this._router.router.transitionTo(this.parentRoute, { queryParams: { perPage: 1 } });
    }
  }
});
<commit_msg>Fix test exsample<commit_after>import BaseEditFormController from 'ember-flexberry/controllers/edit-form';
import EditFormControllerOperationsIndicationMixin from 'ember-flexberry/mixins/edit-form-controller-operations-indication';

export default BaseEditFormController.extend(EditFormControllerOperationsIndicationMixin, {
  /**
    Route name for transition after close edit form.

    @property parentRoute
    @type String
    @default 'ember-flexberry-dummy-suggestion-list'
   */
  parentRoute: 'components-examples/flexberry-objectlistview/return-with-query-params/ember-flexberry-dummy-suggestion-return-with-query-params-list',

  /**
    Name of model.comments edit route.

    @property commentsEditRoute
    @type String
    @default 'ember-flexberry-dummy-comment-edit'
   */
  commentsEditRoute: 'ember-flexberry-dummy-comment-edit',

  /**
    Method to get type and attributes of a component,
    which will be embeded in object-list-view cell.

    @method getCellComponent.
    @param {Object} attr Attribute of projection property related to current table cell.
    @param {String} bindingPath Path to model property related to current table cell.
    @param {DS.Model} modelClass Model class of data record related to current table row.
    @return {Object} Object containing name & properties of component, which will be used to render current table cell.
    { componentName: 'my-component',  componentProperties: { ... } }.
   */
  getCellComponent(attr, bindingPath, model) {
    let cellComponent = this._super(...arguments);
    if (attr.kind === 'belongsTo') {
      switch (`${model.modelName}+${bindingPath}`) {
        case 'ember-flexberry-dummy-vote+author':
          cellComponent.componentProperties = {
            choose: 'showLookupDialog',
            remove: 'removeLookupValue',
            displayAttributeName: 'name',
            required: true,
            relationName: 'author',
            projection: 'ApplicationUserL',
            autocomplete: true,
          };
          break;

        case 'ember-flexberry-dummy-comment+author':
          cellComponent.componentProperties = {
            choose: 'showLookupDialog',
            remove: 'removeLookupValue',
            displayAttributeName: 'name',
            required: true,
            relationName: 'author',
            projection: 'ApplicationUserL',
            autocomplete: true,
          };
          break;

      }
    }

    return cellComponent;
  },

  actions: {
    /**
      This method set controller.limitFunction for olv window.
      @method onButtonClick
     */
    onButtonClick() {
      this.transitionToRoute(this.parentRoute, { queryParams: { perPage: 1 } });
    }
  }
});
<|endoftext|>


<commit_before>"use strict";
import {separate as separate} from "../separate/separate.js";
import {deSeparate as deSeparate} from "../separate/separate.js";
import identify from "./identify.js";
import url2tag from "./url2tag.js";

export default function(str,options){
	var arr = separate(str);
	arr = identify(arr,options).map((fragment)=>{
		if(typeof fragment === "string") return fragment;
		url2tag(fragment.url,options);
		return url2tag(fragment.protocol+fragment.url,options);
	});
	return deSeparate(arr);
}

function url2tag (fragment,options){
	var href = fragment.protocol + removeNotationEnds(fragment.encoded);
	var original = fragment.raw;
	original = (options.truncate > 0 && original.length > options.truncate) ?  original.substring(0,options.truncate) + "..." : original;
	return `{
		if(typeof attribute === 'function') {
			var name = attribute(fragment).name;
			var value = attribute(fragment).value;
			if(name && !value) return " name ";
			if(name && value) return ` ${name}="${value}" `;
		}
		else return ` ${attribute.name}="${attribute.value}" `;
	}).join("")}>${original}`;
}<commit_msg>attributes and options can now be functions<commit_after>"use strict";
import {separate as separate} from "../separate/separate.js";
import {deSeparate as deSeparate} from "../separate/separate.js";
import {removeNotationEnds as removeNotationEnds} from "../util.js";
import identify from "./identify.js";

export default function(str,options){

	var arr = separate(str);
	arr = identify(arr,options);

	// return the current list (with words being filtered out)
	if(options.list) return arr.filter((fragment)=>typeof fragment !== "string");

	// transform objects to HTML tags
	arr = arr.map((fragment)=>{
		if(typeof fragment === "string") return fragment;
		return url2tag(fragment,options);
	});

	// join and return
	return deSeparate(arr);
}

function url2tag (fragment,options){
	var href = fragment.protocol + removeNotationEnds(fragment.encoded);
	var original = fragment.raw;
	original = (options.truncate > 0 && original.length > options.truncate) ?  original.substring(0,options.truncate) + "..." : original;
	return `{
		if(typeof attribute === 'function') {
			var name = attribute(fragment).name;
			var value = attribute(fragment).value;
			if(name && !value) return " name ";
			if(name && value) return ` ${name}="${value}" `;
		}
		else return ` ${attribute.name}="${attribute.value}" `;
	}).join("")}>${original}`;
}<|endoftext|>


<commit_before>import React from 'react';
import * as R from 'ramda';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import moment from 'moment';
import Environment from '../EnvironmentTeaser';
import { bp, color, fontSize } from '../../variables';
import giturlparse from 'git-url-parse';

class Project extends React.Component {
  constructor(props) {
    super(props);

    const gitUrlParsed = giturlparse(this.props.project.gitUrl);
    const gitLink = `${gitUrlParsed.resource}/${gitUrlParsed.full_name}`;
    const environmentCount = R.countBy(R.prop('environmentType'))(this.props.project.environments)
    const developEnvironmentCount = environmentCount.development ? environmentCount.development: 0;

    this.state = {
      project: [],
      gitUrl: this.props.project.gitUrl,
      copied: false,
      gitLinkWithScheme: `https://${gitLink}`,
      gitLink: gitLink,
      developEnvironmentCount: developEnvironmentCount,
    };
  }

  render() {
    return (

              Created

{moment.utc(this.props.project.created).local().format('DD MMM YYYY, HH:mm:ss')}

              Origin

{this.state.gitLink}

              Git URL

                {this.props.project.gitUrl}

              Copied
              <CopyToClipboard text={this.state.gitUrl} onCopy={() => {
                  this.setState({copied: true});
                  setTimeout(function(){
                     this.setState({copied:false});
                  }.bind(this),750);
                }
              }>

              </CopyToClipboard>

              Branches enabled

{this.props.project.branches}

              Pull requests enabled

{this.props.project.pullrequests}

              Non-Production environments in use

{this.state.developEnvironmentCount} of {R.defaultTo('unlimited', this.props.project.developmentEnvironmentsLimit)}

Environments

            {!this.props.project.environments.length && `No Environments`}
            {this.props.project.environments.map(environment =>
              <Environment
                key={environment.id}
                environment={environment}
                project={this.props.project.name}
              />)
              .sort((a, b) => {
                const environmentTypes = {
                  production: 1,
                  development: 2,
                };
                const deployTypes = {
                  branch: 1,
                  pullrequest: 2,
                };
                return environmentTypes[a.props.environment.environmentType] - environmentTypes[b.props.environment.environmentType] ||
                  deployTypes[a.props.environment.deployType] - deployTypes[b.props.environment.deployType];
              })
            }

    );
  }
}
export default Project;
<commit_msg>Update services/ui/src/components/Project/index.js<commit_after>import React from 'react';
import * as R from 'ramda';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import moment from 'moment';
import Environment from '../EnvironmentTeaser';
import { bp, color, fontSize } from '../../variables';
import giturlparse from 'git-url-parse';

class Project extends React.Component {
  constructor(props) {
    super(props);

    const gitUrlParsed = giturlparse(this.props.project.gitUrl);
    const gitLink = `${gitUrlParsed.resource}/${gitUrlParsed.full_name}`;
    const environmentCount = R.countBy(R.prop('environmentType'))(this.props.project.environments)
    const developEnvironmentCount = environmentCount.development ? environmentCount.development: 0;

    this.state = {
      project: [],
      gitUrl: this.props.project.gitUrl,
      copied: false,
      gitLinkWithScheme: `https://${gitLink}`,
      gitLink: gitLink,
      developEnvironmentCount: developEnvironmentCount,
    };
  }

  render() {
    return (

              Created

{moment.utc(this.props.project.created).local().format('DD MMM YYYY, HH:mm:ss')}

              Origin

{this.state.gitLink}

              Git URL

                {this.props.project.gitUrl}

              Copied
              <CopyToClipboard text={this.state.gitUrl} onCopy={() => {
                  this.setState({copied: true});
                  setTimeout(function(){
                     this.setState({copied:false});
                  }.bind(this),750);
                }
              }>

              </CopyToClipboard>

              Branches enabled

{this.props.project.branches}

              Pull requests enabled

{this.props.project.pullrequests}

              Development environments in use

{this.state.developEnvironmentCount} of {R.defaultTo('unlimited', this.props.project.developmentEnvironmentsLimit)}

Environments

            {!this.props.project.environments.length && `No Environments`}
            {this.props.project.environments.map(environment =>
              <Environment
                key={environment.id}
                environment={environment}
                project={this.props.project.name}
              />)
              .sort((a, b) => {
                const environmentTypes = {
                  production: 1,
                  development: 2,
                };
                const deployTypes = {
                  branch: 1,
                  pullrequest: 2,
                };
                return environmentTypes[a.props.environment.environmentType] - environmentTypes[b.props.environment.environmentType] ||
                  deployTypes[a.props.environment.deployType] - deployTypes[b.props.environment.deployType];
              })
            }

    );
  }
}
export default Project;
<|endoftext|>


<commit_before>
django.jQuery(function($) {
	'use strict';

	django.cascade.ShopLinkPlugin = ring.create(eval(django.cascade.ring_plugin_bases.ShopLinkPlugin), {
		constructor: function() {
			this.$super();

			// be more intuitive, reorganize layout by moving 'Link Target'
			$('.glossary-widget .glossary_target').before($('.form-row.field-link_type'));
		},
		toggleLinkTypes: function(linkType) {
			var $field_cmspage = $(".form-row .field-box.field-cms_page, .form-row .field-box.field-section"),
				$field_product = $(".form-row .field-box.field-product"),
				$field_exturl = $(".form-row .field-box.field-ext_url"),
				$field_mailto = $(".form-row .field-box.field-mail_to"),
				$link_target = $(".glossary-widget.glossary_target");

			switch(linkType) {
			case 'cmspage':
				$field_cmspage.show();
				$field_product.hide();
				$field_exturl.hide();
				$field_mailto.hide();
				$link_target.hide();
				break;
			case 'product':
				$field_cmspage.hide();
				$field_product.show();
				$field_exturl.hide();
				$field_mailto.hide();
				$link_target.show();
				break;
			case 'exturl':
				$field_cmspage.hide();
				$field_product.hide();
				$field_exturl.show();
				$field_mailto.hide();
				$link_target.show();
				break;
			case 'email':
				$field_cmspage.hide();
				$field_product.hide();
				$field_exturl.hide();
				$field_mailto.show();
				$link_target.hide();
				break;
			default:
				$field_cmspage.hide();
				$field_product.hide();
				$field_exturl.hide();
				$field_mailto.hide();
				$link_target.hide();
				break;
			}
		}
	});
});
<commit_msg>use improved link type toggeling from djangocms-cascade 0.16<commit_after>
django.jQuery(function($) {
	'use strict';

	django.cascade.ShopLinkPlugin = ring.create(eval(django.cascade.ring_plugin_bases.ShopLinkPlugin), {
		constructor: function() {
			this.$super();

			// be more intuitive, reorganize layout by moving 'Link Target'
			$('.glossary-widget .glossary_target').before($('.form-row.field-link_type'));
		},
		initializeLinkTypes: function() {
			this.$super();
			this.linkTypes['product'] = new this.LinkType('.form-row .field-box.field-product', true);
		}
	});
});
<|endoftext|>