<commit_before>// @flow
/**
 * CommonFashionista: provides utilities to help manage Fashions
 * (which are * object-representations of CSS styles).
 *
 * @module internal/fashionistas/common
 */

import Debug from 'debug'
import chroma from 'chroma-js'

import type {
  ColorFashion,
  CompositeFashion,
  Fashion,
  NumberFashion,
  StaticFashion,
  UnitFashion,
} from '../flow-types'

import {
  BETWEEN_PAREN_REGEX,
  ALL_COMMAS_REGEX,
  NUMBER_REGEX,
  NON_NUMER_REGEX,
  makeError,
} from '../utils'

import {
  BOX_SHADOW,
  TRANSFORM,
} from '../constants'

const EMPTY_UNIT: string = '0px';

const debug = Debug('react-animatronics:fashionistas:common');

export const isColorString = (str: string): boolean => {
  let color;
  try {
    color = chroma(str);
  } catch (e) {
    color = false;
  }
  return !!color;
}

// NOTE: replace decimals and negative signs otherwise it'll get caught by the
// NON_NUMBER_REGEX
// TODO: why is the NON_NUMBER_REGEX check in here?
export const isNumberString = (str: string): boolean => (
  !isNaN(parseFloat(str))
  && !NON_NUMER_REGEX.test(str.replace('.', '').replace('-', ''))
);

// FIXME: can have false positives
export const isUnitString = (str: string): boolean => (
  NUMBER_REGEX.test(str)
);

export const createColorFashion = (raw: string): ColorFashion => ({
  isBasicType: true,
  isColorType: true,
  value: chroma(raw).hex(),
});

export const createStaticFashion = (raw: string): StaticFashion => ({
  isBasicType: true,
  isStaticType: true,
  value: raw,
});

export const createNumberFashion = (raw: string|number): NumberFashion => ({
  isBasicType: true,
  isNumberType: true,
  value: parseFloat(raw),
});

// EXPERIMENT: Perhaps departing from implicit returns since they make
// debugging more difficult.
export const createUnitFashion = (raw: string): UnitFashion => {
  debug('creating unit fashion for "%s"', raw);
  return {
    isBasicType: true,
    isUnitType: true,
    value: parseFloat(NUMBER_REGEX.exec(raw)[0]),
    unit: raw.slice(NUMBER_REGEX.exec(raw)[0].length),
  }
};

export const parseTransformName = (transform: string): string =>
  transform.slice(0, transform.indexOf('('));

const parseTransformStyle = (transform: string): Fashion => {
  return parseStyle(BETWEEN_PAREN_REGEX.exec(transform)[1]);
}

const normalizeRawTransform = (raw: string): string[] => raw
  .replace(ALL_COMMAS_REGEX, ',')
  .split(' ');

export const createTransformFashion = (raw: string): CompositeFashion => ({
  isCompositeType: true,
  names: normalizeRawTransform(raw).map(parseTransformName),
  styles: normalizeRawTransform(raw).map(parseTransformStyle),
});

/**
 * @param raw - e.g. "6px 12px" (margin/padding shorthand)
 * @returns CompositeFashion
 */
export const createSpacingFashion = (raw: string, name: string): CompositeFashion => {
  const segments = raw.split(' ');
  const numSegments = segments.filter(s => !!s).length;
  if (numSegments < 1 || numSegments > 4) {
    throw makeError(
      `Received an invalid style for ${ name || "margin/padding" }: "${ raw }".`,
      `Margins/paddings should have between 1 to 4 values (no less than 1 and`,
      `no more than 4).`
    );
  }
  const parsed = segments.map(createUnitFashion);
  return {
    isCompositeType: true,
    styles: numSegments === 1 ?
      [parsed[0], parsed[0], parsed[0], parsed[0]]
    : numSegments === 2 ?
      [parsed[0], parsed[1], parsed[0], parsed[1]]
    : numSegments === 3 ?
      [parsed[0], parsed[1], parsed[2], parsed[1]]
    : // numSegments === 4
      [parsed[0], parsed[1], parsed[2], parsed[3]]
  };
};

export const createCommaFashion = (raw: string, name: ?string): CompositeFashion => ({
  isCompositeType: true,
  isCommaType: true,
  styles: raw
    .split(',')
    .map(s => s.trim())
    .map(style => parseStyle(style, name)),
});

export const createBoxShadowFashion = (raw: string): CompositeFashion => {
  const segments = raw.split(' ');
  const numSegments = segments.filter(s => !!s).length;
  if (numSegments < 3 || numSegments > 5) {
    throw makeError(
      `Received an invalid style for box-shadow: "${ raw }".`,
      `Box-shadows should have between 3 to 5 values:`,
      `https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow`
    );
  }

  // Possible formats:
    // offset-x | offset-y | color
    // offset-x | offset-y | blur-radius | color
    // offset-x | offset-y | blur-radius | spread-radius | color
    // inset | offset-x | offset-y | color
  // Target format:
    // offset-x | offset-y | blur-radius | spread-radius | color

  return {
    isCompositeType: true,
    styles: numSegments === 3 ?
      [
        createUnitFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(EMPTY_UNIT),
        createUnitFashion(EMPTY_UNIT),
        createColorFashion(segments[2])
      ]
    : (numSegments === 4 && segments[0] === 'inset') ?
      [
        createStaticFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(segments[2]),
        createColorFashion(segments[3])
      ]
    : (numSegments === 4) ?
      [
        createUnitFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(segments[2]),
        createUnitFashion(EMPTY_UNIT),
        createColorFashion(segments[3])
      ]
    : // numSegments === 5
      [
        createUnitFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(segments[2]),
        createUnitFashion(segments[3]),
        createColorFashion(segments[4])
      ]
  };
}

export const parseStyle = (raw: string|number, name: ?string): Fashion => {
  return typeof raw === 'number' ?
    createNumberFashion(raw)
  : isNumberString(raw) ?
    createNumberFashion(raw)
  : isColorString(raw) ?
    createColorFashion(raw)
  : typeof raw === 'string' && name === TRANSFORM ?
    createTransformFashion(raw)
  : typeof raw === 'string' && raw.includes(',') ?
    createCommaFashion(raw, name)
  : name === BOX_SHADOW ?
    createBoxShadowFashion(raw)
  : typeof name === 'string' && (name.includes('margin') || name.includes('padding')) ?
    createSpacingFashion(raw, name)
  : isUnitString(raw) ?
    createUnitFashion(raw)
  :
    createStaticFashion(raw)
};

export const stringifyColor = (color: ColorFashion) => `${ chroma(color.value).hex() }`;

export const stringifyNumber = (number: NumberFashion) => `${ number.value }`;

export const stringifyUnit = (style: UnitFashion) => `${ style.value }${ style.unit }`;

export const stringifyComposite = (composite: CompositeFashion) => composite.styles
  .reduce((arr: string[], style: Fashion, index: number) => {
    const name: ?string = composite.names && composite.names[index];
    return arr.concat(
      name
        ? `${ name }(${ stringifyFashion(style) })`
        : stringifyFashion(style)
    );
  }, [])
  .join(composite.isCommaType ? ', ' : ' ');

const stringifyBasic = (style: Fashion): string => (
  style.isColorType ?
    stringifyColor(style)
  : style.isNumberType ?
    stringifyNumber(style)
  : style.isUnitType ?
    stringifyUnit(style)
  :
    ''
);

export const stringifyFashion = (style: Fashion): string => (
  style.isCompositeType ?
    stringifyComposite(style)
  : // default: unknown style
    stringifyBasic(style)
);

// IMPROVE: By default, all "transforms" will be converted, even if they have
// the same units. We can be more efficient about this, but it's unclear if
// that's worth it right now.
export const haveConvertibleUnits = (rawA: string, rawB: string, styleName: string): boolean => {
  if (styleName === TRANSFORM) return true;
  const fashionA = parseStyle(rawA, styleName);
  const fashionB = parseStyle(rawB, styleName);
  return (!fashionA.unit || !fashionB.unit)
    ? false
    : fashionA.unit !== fashionB.unit;
};
<commit_msg>:bug: [fashionistas/common] Make parseStyle more accurate in detecting comma vs color styles<commit_after>// @flow
/**
 * CommonFashionista: provides utilities to help manage Fashions
 * (which are * object-representations of CSS styles).
 *
 * @module internal/fashionistas/common
 */

import Debug from 'debug'
import chroma from 'chroma-js'

import type {
  ColorFashion,
  CompositeFashion,
  Fashion,
  NumberFashion,
  StaticFashion,
  UnitFashion,
} from '../flow-types'

import {
  BETWEEN_PAREN_REGEX,
  ALL_COMMAS_REGEX,
  NUMBER_REGEX,
  NON_NUMER_REGEX,
  makeError,
} from '../utils'

import {
  BOX_SHADOW,
  TRANSFORM,
} from '../constants'

const EMPTY_UNIT: string = '0px';

const debug = Debug('react-animatronics:fashionistas:common');

const isCommaString = (str: string): boolean => {
  return (
    typeof str === 'string'
    && str.includes(',')
    && !str.includes('rgb')
    && !str.includes('hsl')
  );
}

export const isColorString = (str: string): boolean => {
  let color;
  try {
    color = chroma(str);
  } catch (e) {
    color = false;
  }
  return !!color;
}

// NOTE: replace decimals and negative signs otherwise it'll get caught by the
// NON_NUMBER_REGEX
// TODO: why is the NON_NUMBER_REGEX check in here?
export const isNumberString = (str: string): boolean => (
  !isNaN(parseFloat(str))
  && !NON_NUMER_REGEX.test(str.replace('.', '').replace('-', ''))
);

// FIXME: can have false positives
export const isUnitString = (str: string): boolean => (
  NUMBER_REGEX.test(str)
);

export const createColorFashion = (raw: string): ColorFashion => ({
  isBasicType: true,
  isColorType: true,
  value: chroma(raw).hex(),
});

export const createStaticFashion = (raw: string): StaticFashion => ({
  isBasicType: true,
  isStaticType: true,
  value: raw,
});

export const createNumberFashion = (raw: string|number): NumberFashion => ({
  isBasicType: true,
  isNumberType: true,
  value: parseFloat(raw),
});

// EXPERIMENT: Perhaps departing from implicit returns since they make
// debugging more difficult.
export const createUnitFashion = (raw: string): UnitFashion => {
  debug('creating unit fashion for "%s"', raw);
  return {
    isBasicType: true,
    isUnitType: true,
    value: parseFloat(NUMBER_REGEX.exec(raw)[0]),
    unit: raw.slice(NUMBER_REGEX.exec(raw)[0].length),
  }
};

export const parseTransformName = (transform: string): string =>
  transform.slice(0, transform.indexOf('('));

const parseTransformStyle = (transform: string): Fashion => {
  return parseStyle(BETWEEN_PAREN_REGEX.exec(transform)[1]);
}

const normalizeRawTransform = (raw: string): string[] => raw
  .replace(ALL_COMMAS_REGEX, ',')
  .split(' ');

export const createTransformFashion = (raw: string): CompositeFashion => ({
  isCompositeType: true,
  names: normalizeRawTransform(raw).map(parseTransformName),
  styles: normalizeRawTransform(raw).map(parseTransformStyle),
});

/**
 * @param raw - e.g. "6px 12px" (margin/padding shorthand)
 * @returns CompositeFashion
 */
export const createSpacingFashion = (raw: string, name: string): CompositeFashion => {
  const segments = raw.split(' ');
  const numSegments = segments.filter(s => !!s).length;
  if (numSegments < 1 || numSegments > 4) {
    throw makeError(
      `Received an invalid style for ${ name || "margin/padding" }: "${ raw }".`,
      `Margins/paddings should have between 1 to 4 values (no less than 1 and`,
      `no more than 4).`
    );
  }
  const parsed = segments.map(createUnitFashion);
  return {
    isCompositeType: true,
    styles: numSegments === 1 ?
      [parsed[0], parsed[0], parsed[0], parsed[0]]
    : numSegments === 2 ?
      [parsed[0], parsed[1], parsed[0], parsed[1]]
    : numSegments === 3 ?
      [parsed[0], parsed[1], parsed[2], parsed[1]]
    : // numSegments === 4
      [parsed[0], parsed[1], parsed[2], parsed[3]]
  };
};

export const createCommaFashion = (raw: string, name: ?string): CompositeFashion => ({
  isCompositeType: true,
  isCommaType: true,
  styles: raw
    .split(',')
    .map(s => s.trim())
    .map(style => parseStyle(style, name)),
});

export const createBoxShadowFashion = (raw: string): CompositeFashion => {
  const segments = raw.split(' ');
  const numSegments = segments.filter(s => !!s).length;
  if (numSegments < 3 || numSegments > 5) {
    throw makeError(
      `Received an invalid style for box-shadow: "${ raw }".`,
      `Box-shadows should have between 3 to 5 values:`,
      `https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow`
    );
  }

  // Possible formats:
    // offset-x | offset-y | color
    // offset-x | offset-y | blur-radius | color
    // offset-x | offset-y | blur-radius | spread-radius | color
    // inset | offset-x | offset-y | color
  // Target format:
    // offset-x | offset-y | blur-radius | spread-radius | color

  return {
    isCompositeType: true,
    styles: numSegments === 3 ?
      [
        createUnitFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(EMPTY_UNIT),
        createUnitFashion(EMPTY_UNIT),
        createColorFashion(segments[2])
      ]
    : (numSegments === 4 && segments[0] === 'inset') ?
      [
        createStaticFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(segments[2]),
        createColorFashion(segments[3])
      ]
    : (numSegments === 4) ?
      [
        createUnitFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(segments[2]),
        createUnitFashion(EMPTY_UNIT),
        createColorFashion(segments[3])
      ]
    : // numSegments === 5
      [
        createUnitFashion(segments[0]),
        createUnitFashion(segments[1]),
        createUnitFashion(segments[2]),
        createUnitFashion(segments[3]),
        createColorFashion(segments[4])
      ]
  };
}

export const parseStyle = (raw: string|number, name: ?string): Fashion => {
  return typeof raw === 'number' ?
    createNumberFashion(raw)
  : isNumberString(raw) ?
    createNumberFashion(raw)
  : typeof raw === 'string' && name === TRANSFORM ?
    createTransformFashion(raw)
  : typeof name === 'string' && (name.includes('margin') || name.includes('padding')) ?
    createSpacingFashion(raw, name)
  : isCommaString(raw) ?
    createCommaFashion(raw, name)
  : name === BOX_SHADOW ?
    createBoxShadowFashion(raw)
  : isColorString(raw) ?
    createColorFashion(raw)
  : isUnitString(raw) ?
    createUnitFashion(raw)
  :
    createStaticFashion(raw)
};

export const stringifyColor = (color: ColorFashion) => `${ chroma(color.value).hex() }`;

export const stringifyNumber = (number: NumberFashion) => `${ number.value }`;

export const stringifyUnit = (style: UnitFashion) => `${ style.value }${ style.unit }`;

export const stringifyComposite = (composite: CompositeFashion) => composite.styles
  .reduce((arr: string[], style: Fashion, index: number) => {
    const name: ?string = composite.names && composite.names[index];
    return arr.concat(
      name
        ? `${ name }(${ stringifyFashion(style) })`
        : stringifyFashion(style)
    );
  }, [])
  .join(composite.isCommaType ? ', ' : ' ');

const stringifyBasic = (style: Fashion): string => (
  style.isColorType ?
    stringifyColor(style)
  : style.isNumberType ?
    stringifyNumber(style)
  : style.isUnitType ?
    stringifyUnit(style)
  :
    ''
);

export const stringifyFashion = (style: Fashion): string => (
  style.isCompositeType ?
    stringifyComposite(style)
  : // default: unknown style
    stringifyBasic(style)
);

// IMPROVE: By default, all "transforms" will be converted, even if they have
// the same units. We can be more efficient about this, but it's unclear if
// that's worth it right now.
export const haveConvertibleUnits = (rawA: string, rawB: string, styleName: string): boolean => {
  if (styleName === TRANSFORM) return true;
  const fashionA = parseStyle(rawA, styleName);
  const fashionB = parseStyle(rawB, styleName);
  return (!fashionA.unit || !fashionB.unit)
    ? false
    : fashionA.unit !== fashionB.unit;
};
<|endoftext|>


<commit_before>/* global Fae */

'use strict';

Fae.form.ajax = {

  init: function() {
    this.set_elements();
    if(!this.has_reinit) {
      this.nested_addedit_links();
      this.nested_addedit_submission();
      this.index_addedit_links();
      this.index_addedit_submission();
    }
    this.delete_no_form();
    this.apply_cookies();
    if (this.$filter_form.length) {
      this.filter_select();
      this.filter_submission();
    }
    this.image_delete_links();
  },

  has_reinit: false,

  set_elements: function() {
    this.$addedit_form = $('.js-addedit-form');
    this.$index_addedit_form = $('.js-index-addedit-form');
    this.$filter_form = $('.js-filter-form');
  },

  nested_addedit_links: function() {
    this.$addedit_form.on('click', '.js-add-link, .js-edit-link', function(ev) {
      ev.preventDefault();
      var $this = $(this);
      var $parent = $this.closest('.js-addedit-form');

      // scroll to the last column of the tbody, where the form will start
      Fae.helpers.scroll_to($parent.find("tbody tr:last-child"), 90);

      Fae.form.ajax.addedit_actions($this, $parent);
    });
  },

  index_addedit_links: function() {
    this.$index_addedit_form.on('click', '.js-add-link, .js-edit-link', function(ev) {
      ev.preventDefault();
      var $this = $(this);
      var $parent = $('.js-addedit-form');

      // scroll to the last column of the tbody, where the form will start
      Fae.helpers.scroll_to($parent.find("tbody tr:last-child"), 90);

      Fae.form.ajax.addedit_actions($this, $parent);
    });
  },

  addedit_actions: function(field, parent) {
    var $this = $(field);
    var $wrapper = $(parent).find('.js-addedit-form-wrapper');

    $.get($this.attr('href'), function(data){
      // check to see if the content is hidden and slide it down if it is.
      if ($wrapper.is(":hidden")) {
        // replace the content of the form area and initiate the chosen and fileinputer
        $wrapper.html(data).find(".select select").fae_chosen({ width: '300px' });
        $wrapper.find(".input.file").fileinputer();
        $wrapper.slideDown();
      } else {
        // if it is visible, replace its content by retaining height
        $wrapper.height($wrapper.height());

        // replace the content of the form area and then remove that height and then chosen and then fileinputer
        $wrapper.html(data).css("height", "").find(".select select").fae_chosen();
        $wrapper.find(".input.file").fileinputer();
      }

      Fae.form.dates.date_picker();
      Fae.form.dates.daterange_picker();
      Fae.form.text.slugger();

      $wrapper.find(".hint").hinter();
    });
  },

  nested_addedit_submission: function() {
    var _this = this;

    this.$addedit_form.on('ajax:success', function(evt, data, status, xhr){

      var $target = $(evt.target);

      // ignore calls not returning html
      if (data !== ' ' && $(data)[0]) {
        var $this = $(this);
        var $parent = $this.parent();
        // we're manipulating the return so let's store in a var and keep 'data' intact
        var html = data;

        // remotipart returns data inside textarea, let's grab it from there
        if ($(html)[0].localName === 'textarea') {
          html = $(data)[0].value;
        }

        if ($(html)[1] && $(html)[1].className === 'main_content-section-area') {
          // we're returning the table, replace everything

          var $form_wrapper = $this.find('.js-addedit-form-wrapper');

          // if there's a form wrap, slide it up before replacing content
          if ($form_wrapper.length) {
            $form_wrapper.slideUp(function(){
              _this.addedit_replace_and_reinit($this, $(html)[1].innerHTML, $target);
            });
          } else {
            _this.addedit_replace_and_reinit($this, $(html)[1].innerHTML, $target);
          }

          if (!$target.hasClass("js-delete-link")) {
            Fae.helpers.scroll_to($parent);
          }
        } else if ($(html)[0].className === 'form_content-wrapper') {
          // we're returning the form due to an error, just replace the form
          $this.find('.form_content-wrapper').replaceWith(html);
          $this.find('.select select').fae_chosen();
          $this.find(".input.file").fileinputer();

          Fae.helpers.scroll_to($this.find('.js-addedit-form-wrapper'));
        }

        _this.has_reinit = true;
        _this.init();
        Fae.fade_notices();

      } else if ($target.hasClass("js-asset-delete-link")) {
        // handle remove asset links
        $target.parent().fadeOut('fast', function() {
          $(this).next('.asset-inputs').fadeIn('fast');
        });
      }
    });
  },

  index_addedit_submission: function() {
    var _this = this;

    this.$index_addedit_form.on('ajax:success', function(evt, data, status, xhr){

      var $target = $(evt.target);

      // ignore calls not returning html
      if (data !== ' ' && $(data)[0]) {
        var $this = $(this);
        var $parent = $this.parent();

        // we're manipulating the return so let's store in a var and keep 'data' intact
        var html = data;

        if ($(html)[0] && $(html)[0].className === 'js-index-addedit-form') {
          // we're returning the table, replace everything

          var $form_wrapper = $this.find('.js-addedit-form-wrapper');

          // if there's a form wrap, slide it up before replacing content
          if ($form_wrapper.length) {
            $form_wrapper.slideUp(function(){
              _this.addedit_replace_and_reinit($this, $(html)[0].innerHTML, $target);
            });
          } else {
            _this.addedit_replace_and_reinit($this, $(html)[0].innerHTML, $target);
          }

          if (!$target.hasClass("js-delete-link")) {
            Fae.helpers.scroll_to($parent);
          }
        }

        _this.has_reinit = true;
        _this.init();
        Fae.fade_notices();

      } else if ($target.hasClass("js-asset-delete-link")) {
        // handle remove asset links
        $target.parent().fadeOut('fast', function() {
          $(this).next('.asset-inputs').fadeIn('fast');
        });
      }
    });
  },

  addedit_replace_and_reinit: function($this, html, $target) {
    $this.html(html)
      .find(".select select").fae_chosen();

    Fae.tables.rowSorting();
  },

  filter_submission: function(params) {
    var _this = this;
    _this.$filter_form
      .on('ajax:success', function(evt, data, status, xhr){
        $(this).next('table').replaceWith($(data).find('table').first());
      })
      .on('click', '.js-reset-btn', function(ev) {
        var form = $(this).closest('form')[0];
        form.reset();
        $(form).find('select').val('').trigger('chosen:updated');
        // reset hashies
        window.location.hash = ''
      })
      .on('change', 'select', function() {
        _this.$filter_form.submit();
      });
  },

  apply_cookies: function() {
    var _this = this;
    var cookie_key = $('.js-filter-form').data('cookie-key');

    if (cookie_key) {
      var set_cookie = $.cookie(cookie_key);
      if (set_cookie && (set_cookie.length > 2)) {
        var cookie = JSON.parse(set_cookie);
        var keys = Object.keys(cookie)
        var hash = '?';

        for(var i = 0; i < keys.length; i++) {
          if(hash !== '?') {
            hash += '&';
          }
          hash += keys[i] + '=' + cookie[keys[i]];
        }

        if( hash !== '?') {
          window.location.hash = hash;
        }
      }

      var callback = function(params){
        $.cookie(cookie_key, JSON.stringify(params));

        var hash = window.location.hash;
        Fae.form.ajax.set_filter_dropdowns(hash);
      }

      _this.grind = new Grinder(callback);
    }
  },

  // update hash when filter dropdowns changed
  filter_select: function(){
    var _this = this;
    $('.js-filter-form .table-filter-group').on('change', function(){
      if ($('.js-filter-form').data('cookie-key')) {
        var key = $(this).find('select').attr('id').split('filter_')[1];
        var value = $(this).find('option:selected').val();

        _this.grind.update(key, value, false, true);
      }
    });
  },

  // check for cookie or hash and set dropdowns/ url accordingly
  set_filter_dropdowns: function(hash) {
    var cookie_name = $('.js-filter-form').data('cookie-key')
    if (cookie_name) {
      var parsed = JSON.parse($.cookie(cookie_name));
    } else {
      var parsed = this.grind.parse(hash);
    }

    if (!$.isEmptyObject(parsed)) {
      $.each(parsed, function(k, v){
        $('.js-filter-form .table-filter-group').each(function(){
          var key = $(this).find('select').attr('id').split('filter_')[1];
          var value = $(this).find('option:selected').val();
          if (k == key) {
            $(this).find('option').each(function(){
              if ($(this).val() == v) {
                $(this).prop('selected', 'selected');
                $('#filter_' + key).trigger('chosen:updated');
              };
            });
          }
        });
      });
      $('.js-filter-form').submit();
    }
  },

  delete_no_form: function() {
    // on deletes that don't exist in a form like file upload area
    $('.js-asset-delete-link').on('ajax:success', function(){
      var $this = $(this);
      if (!$this.closest('.js-addedit-form-wrapper').length) {
        var $parent = $this.closest('.asset-actions');
        var $inputs = $parent.next('.asset-inputs');
        $parent.fadeOut(function(){
          $inputs.fadeIn();
        });
      }
    });
  },

  //ajax image delete links
  image_delete_links: function() {
    $('.imageDeleteLink').click(function(e) {
      e.preventDefault();
      if (confirm('Are you sure you want to delete this image?')) {
        $.post($(this).attr('href'),'html');
        $(this).parent().next().show();
        $(this).parent().hide();
      }
    });
  },
};
<commit_msg>set $parent and unwrap<commit_after>/* global Fae */

'use strict';

Fae.form.ajax = {

  init: function() {
    this.set_elements();
    if(!this.has_reinit) {
      this.nested_addedit_links();
      this.nested_addedit_submission();
      this.index_addedit_links();
      this.index_addedit_submission();
    }
    this.delete_no_form();
    this.apply_cookies();
    if (this.$filter_form.length) {
      this.filter_select();
      this.filter_submission();
    }
    this.image_delete_links();
  },

  has_reinit: false,

  set_elements: function() {
    this.$addedit_form = $('.js-addedit-form');
    this.$index_addedit_form = $('.js-index-addedit-form');
    this.$filter_form = $('.js-filter-form');
  },

  nested_addedit_links: function() {
    this.$addedit_form.on('click', '.js-add-link, .js-edit-link', function(ev) {
      ev.preventDefault();
      var $this = $(this);
      var $parent = $this.closest('.js-addedit-form');

      // scroll to the last column of the tbody, where the form will start
      Fae.helpers.scroll_to($parent.find("tbody tr:last-child"), 90);

      Fae.form.ajax.addedit_actions($this, $parent);
    });
  },

  index_addedit_links: function() {
    this.$index_addedit_form.on('click', '.js-add-link, .js-edit-link', function(ev) {
      ev.preventDefault();
      var $this = $(this);
      var $parent = $('.js-addedit-form');

      // scroll to the last column of the tbody, where the form will start
      Fae.helpers.scroll_to($parent.find("tbody tr:last-child"), 90);

      Fae.form.ajax.addedit_actions($this, $parent);
    });
  },

  addedit_actions: function(field, $parent) {
    var $this = $(field);
    var $wrapper = $parent.find('.js-addedit-form-wrapper');

    $.get($this.attr('href'), function(data){
      // check to see if the content is hidden and slide it down if it is.
      if ($wrapper.is(":hidden")) {
        // replace the content of the form area and initiate the chosen and fileinputer
        $wrapper.html(data).find(".select select").fae_chosen({ width: '300px' });
        $wrapper.find(".input.file").fileinputer();
        $wrapper.slideDown();
      } else {
        // if it is visible, replace its content by retaining height
        $wrapper.height($wrapper.height());

        // replace the content of the form area and then remove that height and then chosen and then fileinputer
        $wrapper.html(data).css("height", "").find(".select select").fae_chosen();
        $wrapper.find(".input.file").fileinputer();
      }

      Fae.form.dates.date_picker();
      Fae.form.dates.daterange_picker();
      Fae.form.text.slugger();

      $wrapper.find(".hint").hinter();
    });
  },

  nested_addedit_submission: function() {
    var _this = this;

    this.$addedit_form.on('ajax:success', function(evt, data, status, xhr){

      var $target = $(evt.target);

      // ignore calls not returning html
      if (data !== ' ' && $(data)[0]) {
        var $this = $(this);
        var $parent = $this.parent();
        // we're manipulating the return so let's store in a var and keep 'data' intact
        var html = data;

        // remotipart returns data inside textarea, let's grab it from there
        if ($(html)[0].localName === 'textarea') {
          html = $(data)[0].value;
        }

        if ($(html)[1] && $(html)[1].className === 'main_content-section-area') {
          // we're returning the table, replace everything

          var $form_wrapper = $this.find('.js-addedit-form-wrapper');

          // if there's a form wrap, slide it up before replacing content
          if ($form_wrapper.length) {
            $form_wrapper.slideUp(function(){
              _this.addedit_replace_and_reinit($this, $(html)[1].innerHTML, $target);
            });
          } else {
            _this.addedit_replace_and_reinit($this, $(html)[1].innerHTML, $target);
          }

          if (!$target.hasClass("js-delete-link")) {
            Fae.helpers.scroll_to($parent);
          }
        } else if ($(html)[0].className === 'form_content-wrapper') {
          // we're returning the form due to an error, just replace the form
          $this.find('.form_content-wrapper').replaceWith(html);
          $this.find('.select select').fae_chosen();
          $this.find(".input.file").fileinputer();

          Fae.helpers.scroll_to($this.find('.js-addedit-form-wrapper'));
        }

        _this.has_reinit = true;
        _this.init();
        Fae.fade_notices();

      } else if ($target.hasClass("js-asset-delete-link")) {
        // handle remove asset links
        $target.parent().fadeOut('fast', function() {
          $(this).next('.asset-inputs').fadeIn('fast');
        });
      }
    });
  },

  index_addedit_submission: function() {
    var _this = this;

    this.$index_addedit_form.on('ajax:success', function(evt, data, status, xhr){

      var $target = $(evt.target);

      // ignore calls not returning html
      if (data !== ' ' && $(data)[0]) {
        var $this = $(this);
        var $parent = $this.parent();

        // we're manipulating the return so let's store in a var and keep 'data' intact
        var html = data;

        if ($(html)[0] && $(html)[0].className === 'js-index-addedit-form') {
          // we're returning the table, replace everything

          var $form_wrapper = $this.find('.js-addedit-form-wrapper');

          // if there's a form wrap, slide it up before replacing content
          if ($form_wrapper.length) {
            $form_wrapper.slideUp(function(){
              _this.addedit_replace_and_reinit($this, $(html)[0].innerHTML, $target);
            });
          } else {
            _this.addedit_replace_and_reinit($this, $(html)[0].innerHTML, $target);
          }

          if (!$target.hasClass("js-delete-link")) {
            Fae.helpers.scroll_to($parent);
          }
        }

        _this.has_reinit = true;
        _this.init();
        Fae.fade_notices();

      } else if ($target.hasClass("js-asset-delete-link")) {
        // handle remove asset links
        $target.parent().fadeOut('fast', function() {
          $(this).next('.asset-inputs').fadeIn('fast');
        });
      }
    });
  },

  addedit_replace_and_reinit: function($this, html, $target) {
    $this.html(html)
      .find(".select select").fae_chosen();

    Fae.tables.rowSorting();
  },

  filter_submission: function(params) {
    var _this = this;
    _this.$filter_form
      .on('ajax:success', function(evt, data, status, xhr){
        $(this).next('table').replaceWith($(data).find('table').first());
      })
      .on('click', '.js-reset-btn', function(ev) {
        var form = $(this).closest('form')[0];
        form.reset();
        $(form).find('select').val('').trigger('chosen:updated');
        // reset hashies
        window.location.hash = ''
      })
      .on('change', 'select', function() {
        _this.$filter_form.submit();
      });
  },

  apply_cookies: function() {
    var _this = this;
    var cookie_key = $('.js-filter-form').data('cookie-key');

    if (cookie_key) {
      var set_cookie = $.cookie(cookie_key);
      if (set_cookie && (set_cookie.length > 2)) {
        var cookie = JSON.parse(set_cookie);
        var keys = Object.keys(cookie)
        var hash = '?';

        for(var i = 0; i < keys.length; i++) {
          if(hash !== '?') {
            hash += '&';
          }
          hash += keys[i] + '=' + cookie[keys[i]];
        }

        if( hash !== '?') {
          window.location.hash = hash;
        }
      }

      var callback = function(params){
        $.cookie(cookie_key, JSON.stringify(params));

        var hash = window.location.hash;
        Fae.form.ajax.set_filter_dropdowns(hash);
      }

      _this.grind = new Grinder(callback);
    }
  },

  // update hash when filter dropdowns changed
  filter_select: function(){
    var _this = this;
    $('.js-filter-form .table-filter-group').on('change', function(){
      if ($('.js-filter-form').data('cookie-key')) {
        var key = $(this).find('select').attr('id').split('filter_')[1];
        var value = $(this).find('option:selected').val();

        _this.grind.update(key, value, false, true);
      }
    });
  },

  // check for cookie or hash and set dropdowns/ url accordingly
  set_filter_dropdowns: function(hash) {
    var cookie_name = $('.js-filter-form').data('cookie-key')
    if (cookie_name) {
      var parsed = JSON.parse($.cookie(cookie_name));
    } else {
      var parsed = this.grind.parse(hash);
    }

    if (!$.isEmptyObject(parsed)) {
      $.each(parsed, function(k, v){
        $('.js-filter-form .table-filter-group').each(function(){
          var key = $(this).find('select').attr('id').split('filter_')[1];
          var value = $(this).find('option:selected').val();
          if (k == key) {
            $(this).find('option').each(function(){
              if ($(this).val() == v) {
                $(this).prop('selected', 'selected');
                $('#filter_' + key).trigger('chosen:updated');
              };
            });
          }
        });
      });
      $('.js-filter-form').submit();
    }
  },

  delete_no_form: function() {
    // on deletes that don't exist in a form like file upload area
    $('.js-asset-delete-link').on('ajax:success', function(){
      var $this = $(this);
      if (!$this.closest('.js-addedit-form-wrapper').length) {
        var $parent = $this.closest('.asset-actions');
        var $inputs = $parent.next('.asset-inputs');
        $parent.fadeOut(function(){
          $inputs.fadeIn();
        });
      }
    });
  },

  //ajax image delete links
  image_delete_links: function() {
    $('.imageDeleteLink').click(function(e) {
      e.preventDefault();
      if (confirm('Are you sure you want to delete this image?')) {
        $.post($(this).attr('href'),'html');
        $(this).parent().next().show();
        $(this).parent().hide();
      }
    });
  },
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

// compile typescript code and run unit tests

const $ = require("./common");

async function main() {
  process.env.NODE_ENV = "test";
  await $.showVersions(["npm"]);

  $(await $.npm("install"));

  $(await $.npm("run ts-check"));

  if (process.platform === "linux") {
    $(
      await $.sh(
        'xvfb-run -a -s "-screen 0 1280x720x24" npm test -- --thorough'
      )
    );
    $(
      await $.sh(
        'xvfb-run -a -s "-screen 0 1280x720x24" npm run integration-tests'
      )
    );
    $(await $.npm("run upload-coverage"));
  } else {
    $(await $.npm("test -- --thorough"));
    $(await $.npm("run integration-tests"));
  }
}

main();
<commit_msg>Don't use xvfb-run for unit tests anymore, remove --thorough<commit_after>#!/usr/bin/env node

// compile typescript code and run unit tests

const $ = require("./common");

async function main() {
  process.env.NODE_ENV = "test";
  await $.showVersions(["npm"]);

  $(await $.npm("install"));

  $(await $.npm("run ts-check"));

  if (process.platform === "linux") {
    $(await $.npm("test"));
    $(
      await $.sh(
        'xvfb-run -a -s "-screen 0 1280x720x24" npm run integration-tests'
      )
    );
    $(await $.npm("run upload-coverage"));
  } else {
    $(await $.npm("test"));
    $(await $.npm("run integration-tests"));
  }
}

main();
<|endoftext|>


<commit_before>import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';

export default class IssuableIndex {
  constructor(pagePrefix) {
    this.initBulkUpdate(pagePrefix);
    IssuableIndex.resetIncomingEmailToken();
  }
  initBulkUpdate(pagePrefix) {
    const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
    const alreadyInitialized = !!this.bulkUpdateSidebar;

    if (userCanBulkUpdate && !alreadyInitialized) {
      IssuableBulkUpdateActions.init({
        prefixId: pagePrefix,
      });

      this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
    }
  }

  static resetIncomingEmailToken() {
    $('.incoming-email-token-reset').on('click', (e) => {
      e.preventDefault();

      $.ajax({
        type: 'PUT',
        url: $('.incoming-email-token-reset').attr('href'),
        dataType: 'json',
        success(response) {
          $('#issuable_email').val(response.new_address).focus();
        },
        beforeSend() {
          $('.incoming-email-token-reset').text('resetting...');
        },
        complete() {
          $('.incoming-email-token-reset').text('reset it');
        },
      });
    });
  }
}
<commit_msg>Converted issuable_index to axios<commit_after>import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';

export default class IssuableIndex {
  constructor(pagePrefix) {
    this.initBulkUpdate(pagePrefix);
    IssuableIndex.resetIncomingEmailToken();
  }
  initBulkUpdate(pagePrefix) {
    const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
    const alreadyInitialized = !!this.bulkUpdateSidebar;

    if (userCanBulkUpdate && !alreadyInitialized) {
      IssuableBulkUpdateActions.init({
        prefixId: pagePrefix,
      });

      this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
    }
  }

  static resetIncomingEmailToken() {
    $('.incoming-email-token-reset').on('click', (e) => {
      e.preventDefault();

      $('.incoming-email-token-reset').text('resetting...');

      axios.put($('.incoming-email-token-reset').attr('href'))
        .then(({ data }) => {
          $('#issuable_email').val(data.new_address).focus();

          $('.incoming-email-token-reset').text('reset it');
        })
        .catch(() => {
          flash(__('There was an error when reseting email token.'));

          $('.incoming-email-token-reset').text('reset it');
        });
    });
  }
}
<|endoftext|>


<commit_before>$(() => {
    _CodidactKeyboard = {
        state: 'home',
        selectedItem: null,
        user_id: parseInt($('.header--item.is-complex.is-visible-on-mobile[href^="/users/"]').attr('href').split("/").pop()),
        is_mod: !!$('.header--item[href="/mod/flags"]').length,
        categories: function () {
            category_elements = $("a.category-header--tab");
            return_obj = {};
            category_elements.each(function () {
                return_obj[this.innerText] = this.getAttribute('href');
            });
            return return_obj;
        },
        dialog: function (msg) {
            _CodidactKeyboard.dialogClose();
            d = document.createElement("div")
            d.classList.add("__keyboard_help");
            d.innerText = msg;
            document.body.appendChild(d);
        },
        dialogClose: function () {
            $(".__keyboard_help").remove();
            _CodidactKeyboard.state = 'home';
        },
        updateSelected: function () {
            $(".__keyboard_selected").removeClass('__keyboard_selected');
            if (_CodidactKeyboard.selectedItem) {
                _CodidactKeyboard.selectedItem.classList.add('__keyboard_selected');
                _CodidactKeyboard.selectedItem.scrollIntoView({ behavior: 'smooth' });
                _CodidactKeyboard.selectedItem.focus();

                _CodidactKeyboard.selectedItemData = {
                    type: _CodidactKeyboard.selectedItem.getAttribute("data-ckb-item-type"),
                    post_id: _CodidactKeyboard.selectedItem.getAttribute("data-ckb-post-id")
                };
            }
        }
    }

    // Use html, so that all prior attempts to access keyup event have priority
    $("html").on("keyup", function (e) {
        if (e.target != document.body) return;
        if (e.key == "Escape") {
            _CodidactKeyboard.dialogClose();
        } else if (_CodidactKeyboard.state == 'home') {
            homeMenu(e);
        } else if (_CodidactKeyboard.state == 'goto') {
            gotoMenu(e);
        } else if (_CodidactKeyboard.state == 'goto/category') {
            categoryMenu(e);
        } else if (_CodidactKeyboard.state == 'goto/category-tags') {
            categoryTagsMenu(e);
        } else if (_CodidactKeyboard.state == 'tools') {
            toolsMenu(e);
        } else if (_CodidactKeyboard.state == 'tools/vote') {
            voteMenu(e);
        }
    });

    function homeMenu(e) {
        if (e.key == "?") {
            _CodidactKeyboard.dialog('Codidact Keyboard Shortcuts\n' +
                '===========================\n' +
                '?  Open this help\n' +
                'n  New post\n' +
                '   (in current category)\n' +
                's  Search for something\n' +
                'g  Go to a page...\n\n' +
                'a  Go to answer field\n\n' +
                'Selection shortcuts:\n\n' +
                'j  Move one item down\n' +
                'k  Move one item up\n' +
                't  Use a tool (on selection)\n\n' +
                '(Selection shortcuts will select\n' +
                'first post, if none selected)'

            );
        } else if (e.key == 'n') {
            new_post_link = $('a.category-header--nav-item.is-button').attr('href');
            if (new_post_link)
                window.location.href = new_post_link;
        } else if (e.key == 'g') {
            _CodidactKeyboard.dialog('Go to ...\n' +
                '=========\n' +
                'm  Main page\n' +
                'u  User list\n' +
                'h  Help\n' +
                'p  Your profile page\n' +
                'c  Category ...\n' +
                't  Tags of category ...' +
                (_CodidactKeyboard.is_mod ? '\nf  Flags (mod only)' : '')
            );
            _CodidactKeyboard.state = 'goto';
        } else if (e.key == 'k') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            else {
                _CodidactKeyboard.selectedItem = $(_CodidactKeyboard.selectedItem).nextAll('[data-ckb-list-item]')[0] || _CodidactKeyboard.selectedItem;
            }
            _CodidactKeyboard.updateSelected();
        } else if (e.key == 'j') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            else {
                _CodidactKeyboard.selectedItem = $(_CodidactKeyboard.selectedItem).prevAll('[data-ckb-list-item]')[0] || _CodidactKeyboard.selectedItem;
            }
            _CodidactKeyboard.updateSelected();
        } else if (e.key == 't') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            _CodidactKeyboard.updateSelected();

            if (_CodidactKeyboard.selectedItemData.type == "post") {
                _CodidactKeyboard.dialog('Use tool ...\n' +
                    '============\n' +
                    'f  Flag\n' +
                    'e  Edit\n' +
                    'c  Comment\n' +
                    'l  Get permalink\n' +
                    'h  View history\n' +
                    'v  Vote ...' +
                    (_CodidactKeyboard.is_mod ? '\nt  Use tools' : '')
                );
                _CodidactKeyboard.state = 'tools';
            }
        } else if (e.key == 'a') {
            cl = $('#answer_body_markdown');
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.focus();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'Enter') {
            if (_CodidactKeyboard.selectedItemData.type == "link") {
                link = $(_CodidactKeyboard.selectedItem).find("[data-ckb-item-link]").attr("href");
                window.location.href = link;
            }
        }
    }

    function gotoMenu(e) {
        if (e.key == 'm') {
            window.location.href = '/';
        } else if (e.key == 'u') {
            window.location.href = '/users';
        } else if (e.key == 'h') {
            window.location.href = '/help';
        } else if (e.key == 'p') {
            window.location.href = '/users/' + _CodidactKeyboard.user_id;
        } else if (e.key == 'f') {
            window.location.href = '/mod/flags';
        } else if (e.key == 'f') {
            window.location.href = '/mod/flags';
        } else if (e.key == "t") {
            data = _CodidactKeyboard.categories();
            data = Object.entries(data);
            string_response = "";
            for (var i = 0; i < data.length; i++) {
                entry = data[i];
                string_response += (i + 1) + "  " + entry[0] + "\n"
            }
            _CodidactKeyboard.dialog('Go to tags of category ...\n' +
                '==================\n' +
                string_response.trim()
            );
            _CodidactKeyboard.state = 'goto/category-tags';
            window.location.href = tlink;
        } else if (e.key == 'c') {
            data = _CodidactKeyboard.categories();
            data = Object.entries(data);
            string_response = "";
            for (var i = 0; i < data.length; i++) {
                entry = data[i];
                string_response += (i + 1) + "  " + entry[0] + "\n"
            }
            _CodidactKeyboard.dialog('Go to category ...\n' +
                '==================\n' +
                "c  Category List\n" +
                string_response.trim()
            );
            _CodidactKeyboard.state = 'goto/category';
        }

    }

    function categoryMenu(e) {
        if (e.key == "c") {
            window.location.href = "/categories";
        } else {
            number = parseInt(e.key);
            if (number != NaN) {
                data = _CodidactKeyboard.categories();
                data = Object.entries(data);

                category = data[number - 1];
                window.location.href = category[1];
            }
        }
    }

    function categoryTagsMenu(e) {
        number = parseInt(e.key);
        if (number != NaN) {
            data = _CodidactKeyboard.categories();
            data = Object.entries(data);

            category = data[number - 1];
            window.location.href = category[1] + "/tags";
        }
    }

    function toolsMenu(e) {
        if (e.key == 'e') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-pencil-alt').parent().attr("href");
        } else if (e.key == 'h') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-history').parent().attr("href");
        } else if (e.key == 'l') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-link').parent().attr("href");
        } else if (e.key == 'c') {
            cl = $(_CodidactKeyboard.selectedItem).find('.js-add-comment');
            cl.nextAll("form").css("display", "block");
            cl.nextAll("form")[0].scrollIntoView({ behavior: "smooth" });
            cl.nextAll("form").find(".js-comment-content").focus();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'f') {
            cl = $(_CodidactKeyboard.selectedItem).find('.post--action-dialog.js-flag-box');
            cl.addClass("is-active");
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.find(".js-flag-comment").focus();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'v') {
            _CodidactKeyboard.dialog('Vote ...\n' +
                '========\n' +
                'u  Up\n' +
                'd  Down\n' +
                'c  Close'
            );
            _CodidactKeyboard.state = 'tools/vote';
        } else if (e.key == 't') {
            cl = $(_CodidactKeyboard.selectedItem).find('a.tools--item i.fa.fa-wrench').parent();
            cl = $(cl.attr("data-modal"));
            cl.toggleClass("is-active");
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }

    }

    function voteMenu(e) {
        if (e.key == 'u') {
            cl = $(_CodidactKeyboard.selectedItem).find('.vote-button[data-vote-type="1"]');
            cl.click();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'd') {
            cl = $(_CodidactKeyboard.selectedItem).find('.vote-button[data-vote-type="-1"]');
            cl.click();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'c') {
            cl = $(_CodidactKeyboard.selectedItem).find('.post--action-dialog.js-close-box');
            cl.addClass("is-active");
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }

    }
});
<commit_msg>Fix keyboard tools script for anonymous users<commit_after>$(() => {
    const userLink = $('.header--item.is-complex.is-visible-on-mobile[href^="/users/"]').attr('href');

    _CodidactKeyboard = {
        state: 'home',
        selectedItem: null,
        user_id: !!userLink ? parseInt(userLink.split("/").pop(), 10) : null,
        is_mod: !!$('.header--item[href="/mod/flags"]').length,
        categories: function () {
            category_elements = $("a.category-header--tab");
            return_obj = {};
            category_elements.each(function () {
                return_obj[this.innerText] = this.getAttribute('href');
            });
            return return_obj;
        },
        dialog: function (msg) {
            _CodidactKeyboard.dialogClose();
            d = document.createElement("div")
            d.classList.add("__keyboard_help");
            d.innerText = msg;
            document.body.appendChild(d);
        },
        dialogClose: function () {
            $(".__keyboard_help").remove();
            _CodidactKeyboard.state = 'home';
        },
        updateSelected: function () {
            $(".__keyboard_selected").removeClass('__keyboard_selected');
            if (_CodidactKeyboard.selectedItem) {
                _CodidactKeyboard.selectedItem.classList.add('__keyboard_selected');
                _CodidactKeyboard.selectedItem.scrollIntoView({ behavior: 'smooth' });
                _CodidactKeyboard.selectedItem.focus();

                _CodidactKeyboard.selectedItemData = {
                    type: _CodidactKeyboard.selectedItem.getAttribute("data-ckb-item-type"),
                    post_id: _CodidactKeyboard.selectedItem.getAttribute("data-ckb-post-id")
                };
            }
        }
    }

    // Use html, so that all prior attempts to access keyup event have priority
    $("html").on("keyup", function (e) {
        if (e.target != document.body) return;
        if (e.key == "Escape") {
            _CodidactKeyboard.dialogClose();
        } else if (_CodidactKeyboard.state == 'home') {
            homeMenu(e);
        } else if (_CodidactKeyboard.state == 'goto') {
            gotoMenu(e);
        } else if (_CodidactKeyboard.state == 'goto/category') {
            categoryMenu(e);
        } else if (_CodidactKeyboard.state == 'goto/category-tags') {
            categoryTagsMenu(e);
        } else if (_CodidactKeyboard.state == 'tools') {
            toolsMenu(e);
        } else if (_CodidactKeyboard.state == 'tools/vote') {
            voteMenu(e);
        }
    });

    function homeMenu(e) {
        if (e.key == "?") {
            _CodidactKeyboard.dialog('Codidact Keyboard Shortcuts\n' +
                '===========================\n' +
                '?  Open this help\n' +
                'n  New post\n' +
                '   (in current category)\n' +
                's  Search for something\n' +
                'g  Go to a page...\n\n' +
                'a  Go to answer field\n\n' +
                'Selection shortcuts:\n\n' +
                'j  Move one item down\n' +
                'k  Move one item up\n' +
                't  Use a tool (on selection)\n\n' +
                '(Selection shortcuts will select\n' +
                'first post, if none selected)'

            );
        } else if (e.key == 'n') {
            new_post_link = $('a.category-header--nav-item.is-button').attr('href');
            if (new_post_link)
                window.location.href = new_post_link;
        } else if (e.key == 'g') {
            _CodidactKeyboard.dialog('Go to ...\n' +
                '=========\n' +
                'm  Main page\n' +
                'u  User list\n' +
                'h  Help\n' +
                'p  Your profile page\n' +
                'c  Category ...\n' +
                't  Tags of category ...' +
                (_CodidactKeyboard.is_mod ? '\nf  Flags (mod only)' : '')
            );
            _CodidactKeyboard.state = 'goto';
        } else if (e.key == 'k') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            else {
                _CodidactKeyboard.selectedItem = $(_CodidactKeyboard.selectedItem).nextAll('[data-ckb-list-item]')[0] || _CodidactKeyboard.selectedItem;
            }
            _CodidactKeyboard.updateSelected();
        } else if (e.key == 'j') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            else {
                _CodidactKeyboard.selectedItem = $(_CodidactKeyboard.selectedItem).prevAll('[data-ckb-list-item]')[0] || _CodidactKeyboard.selectedItem;
            }
            _CodidactKeyboard.updateSelected();
        } else if (e.key == 't') {
            if (_CodidactKeyboard.selectedItem == null) _CodidactKeyboard.selectedItem = $("[data-ckb-list-item]:first-of-type")[0];
            _CodidactKeyboard.updateSelected();

            if (_CodidactKeyboard.selectedItemData.type == "post") {
                _CodidactKeyboard.dialog('Use tool ...\n' +
                    '============\n' +
                    'f  Flag\n' +
                    'e  Edit\n' +
                    'c  Comment\n' +
                    'l  Get permalink\n' +
                    'h  View history\n' +
                    'v  Vote ...' +
                    (_CodidactKeyboard.is_mod ? '\nt  Use tools' : '')
                );
                _CodidactKeyboard.state = 'tools';
            }
        } else if (e.key == 'a') {
            cl = $('#answer_body_markdown');
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.focus();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'Enter') {
            if (_CodidactKeyboard.selectedItemData.type == "link") {
                link = $(_CodidactKeyboard.selectedItem).find("[data-ckb-item-link]").attr("href");
                window.location.href = link;
            }
        }
    }

    function gotoMenu(e) {
        if (e.key == 'm') {
            window.location.href = '/';
        } else if (e.key == 'u') {
            window.location.href = '/users';
        } else if (e.key == 'h') {
            window.location.href = '/help';
        } else if (e.key == 'p') {
            window.location.href = '/users/' + _CodidactKeyboard.user_id;
        } else if (e.key == 'f') {
            window.location.href = '/mod/flags';
        } else if (e.key == 'f') {
            window.location.href = '/mod/flags';
        } else if (e.key == "t") {
            data = _CodidactKeyboard.categories();
            data = Object.entries(data);
            string_response = "";
            for (var i = 0; i < data.length; i++) {
                entry = data[i];
                string_response += (i + 1) + "  " + entry[0] + "\n"
            }
            _CodidactKeyboard.dialog('Go to tags of category ...\n' +
                '==================\n' +
                string_response.trim()
            );
            _CodidactKeyboard.state = 'goto/category-tags';
            window.location.href = tlink;
        } else if (e.key == 'c') {
            data = _CodidactKeyboard.categories();
            data = Object.entries(data);
            string_response = "";
            for (var i = 0; i < data.length; i++) {
                entry = data[i];
                string_response += (i + 1) + "  " + entry[0] + "\n"
            }
            _CodidactKeyboard.dialog('Go to category ...\n' +
                '==================\n' +
                "c  Category List\n" +
                string_response.trim()
            );
            _CodidactKeyboard.state = 'goto/category';
        }

    }

    function categoryMenu(e) {
        if (e.key == "c") {
            window.location.href = "/categories";
        } else {
            number = parseInt(e.key);
            if (number != NaN) {
                data = _CodidactKeyboard.categories();
                data = Object.entries(data);

                category = data[number - 1];
                window.location.href = category[1];
            }
        }
    }

    function categoryTagsMenu(e) {
        number = parseInt(e.key);
        if (number != NaN) {
            data = _CodidactKeyboard.categories();
            data = Object.entries(data);

            category = data[number - 1];
            window.location.href = category[1] + "/tags";
        }
    }

    function toolsMenu(e) {
        if (e.key == 'e') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-pencil-alt').parent().attr("href");
        } else if (e.key == 'h') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-history').parent().attr("href");
        } else if (e.key == 'l') {
            window.location.href = $(_CodidactKeyboard.selectedItem).find('.tools--item i.fa.fa-link').parent().attr("href");
        } else if (e.key == 'c') {
            cl = $(_CodidactKeyboard.selectedItem).find('.js-add-comment');
            cl.nextAll("form").css("display", "block");
            cl.nextAll("form")[0].scrollIntoView({ behavior: "smooth" });
            cl.nextAll("form").find(".js-comment-content").focus();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'f') {
            cl = $(_CodidactKeyboard.selectedItem).find('.post--action-dialog.js-flag-box');
            cl.addClass("is-active");
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.find(".js-flag-comment").focus();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'v') {
            _CodidactKeyboard.dialog('Vote ...\n' +
                '========\n' +
                'u  Up\n' +
                'd  Down\n' +
                'c  Close'
            );
            _CodidactKeyboard.state = 'tools/vote';
        } else if (e.key == 't') {
            cl = $(_CodidactKeyboard.selectedItem).find('a.tools--item i.fa.fa-wrench').parent();
            cl = $(cl.attr("data-modal"));
            cl.toggleClass("is-active");
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }

    }

    function voteMenu(e) {
        if (e.key == 'u') {
            cl = $(_CodidactKeyboard.selectedItem).find('.vote-button[data-vote-type="1"]');
            cl.click();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'd') {
            cl = $(_CodidactKeyboard.selectedItem).find('.vote-button[data-vote-type="-1"]');
            cl.click();
            _CodidactKeyboard.dialogClose();
        } else if (e.key == 'c') {
            cl = $(_CodidactKeyboard.selectedItem).find('.post--action-dialog.js-close-box');
            cl.addClass("is-active");
            cl[0].scrollIntoView({ behavior: "smooth" });
            cl.focus();
            _CodidactKeyboard.dialogClose();
        }

    }
});
<|endoftext|>


<commit_before>// MAS.bootstrap.I18n_locale == en || cy == file path in require config
define(
  ['jquery', 'globals', 'pubsub', 'log', 'i18n'],
  function($, globals, pubsub, log, i18n) {
    'use strict';

    var MAS = $.extend({}, globals, pubsub, log);
    var dataLayer = dataLayer || [];

    MAS.text = i18n;

    // Fire analytics events
    // TO USE: MAS.publish('analytics:trigger', {object with props})
    MAS.subscribe('analytics:trigger', function(e, data) {
      MAS.log('mas_analytics.triggerAnalytics', data);
      dataLayer.push(data);
    });

    return MAS;
  }
);
<commit_msg>Make tracking facade more resiliant<commit_after>// MAS.bootstrap.I18n_locale == en || cy == file path in require config
define(
  ['jquery', 'globals', 'pubsub', 'log', 'i18n'],
  function($, globals, pubsub, log, i18n) {
    'use strict';

    var MAS = $.extend({}, globals, pubsub, log);
    window.dataLayer = window.dataLayer || [];

    MAS.text = i18n;

    // Fire analytics events
    // TO USE: MAS.publish('analytics:trigger', {object with props})
    MAS.subscribe('analytics:trigger', function(e, data) {
      MAS.log('mas_analytics.triggerAnalytics', data);
      window.dataLayer.push(data);
    });

    return MAS;
  }
);
<|endoftext|>


<commit_before>/*
 * Converts JSON data received from the server to flat array of values.
 */
function jsonToValuesArray(jsonData) {
  var errMsgs = [];
  for (var key in jsonData) {
  	var values = jsonData[key];
    $.each(values, function (idx, val) {
      errMsgs.push(val);
	});
  }
  return errMsgs;
}

/*
 * Calls callback function on AJAX success (because built-in functions don't
 * work!)
 */
$.fn.onAjaxComplete = function (cb) {
  $(this)
  .on('ajax:success', function () {
	cb();
  })
  .on('ajax:error', function () {
	cb();
  });
}

// Number of all tutorial steps
var TUTORIAL_STEPS_CNT = 26;

/**
 * Initializes tutorial steps for the current page.
 * NOTE: You can specify steps manually in JS with steps parameter (preferred
 * way), or hardcode them in HTML
 * NOTE: If some steps edit page, then this function needs to be called several
 * times for the same page, but for different steps. The same goes if the page
 * has discontinuous tutorial steps. In such cases, use steps branching, e.g.:
 * @example
 * var tutorialData = Cookies.get('tutorial_data');
 * if (tutorialData) {
 *   tutorialData = JSON.parse(tutorialData);
 *   var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
 *
 *   if (stepNum >= 6 && stepNum <= 7) {
 *     ...
 *   } else if ...
 * NOTE: If an element the popup is pointing at is of lesser horizontal length
 * than the popup itself, then it will not be positioned correctly if it's
 * position is top or bottom, so set/change the step's position to either left
 * or right (and don't use any custom styling!), e.g.:
 * @example
 * var steps = [
 *   {
 *     ...
 *     position: 'right'
 *   },
 *   {
 *   ...
 * ];
 * NOTE: If only one page step is needed, then make pageFirstStepN ==
 * pageLastStepN (both represent the one and only step number)
 *
 * @param  {number} pageFirstStepN Page's first step number
 * @param  {number} pageLastStepN Page's last step number
 * @param  {string} nextPagePath Next page absolute path
 * @param {function} beforeCb Callback called before the tutorial starts. Mainly
 *  used for setting 'pointer-events: none' on the elements the page's steps
 *  highlight.
 * @param {function} endCb Callback called after the tutorial ends. Mainly used
 *  for setting 'pointer-events: auto' on the elements the page's steps
 *  highlight.
 * @param {object} steps Optional JSON containing introJs steps. They can be
 *  specified here, or hardcoded in HTML.
 */
function initPageTutorialSteps(pageFirstStepN, pageLastStepN, nextPagePath,
                               beforeCb, endCb, steps) {
  var tutorialData = Cookies.get('tutorial_data');
  if (tutorialData) {
    tutorialData = JSON.parse(tutorialData);
    var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
    if (isNaN(stepNum)) {
      // Cookies data initialization
      stepNum = 1;
      Cookies.set('current_tutorial_step', stepNum);
      tutorialData[0].backPagesPaths = [];
      Cookies.set('tutorial_data', tutorialData);
    }
    var thisPagePath = window.location.pathname;
    beforeCb();

    // Initialize tutorial for the current page's steps
    var doneLabel = (pageLastStepN === TUTORIAL_STEPS_CNT) ?
     'Start using sciNote' : 'End tutorial';
    if (_.isUndefined(steps)) {
      introJs()
       .setOptions({
         overlayOpacity: '0.2',
         prevLabel: 'Back',
         nextLabel: 'Next',
         skipLabel: 'End tutorial',
         doneLabel: doneLabel,
         showBullets: false,
         showStepNumbers: false,
         exitOnOverlayClick: false,
         exitOnEsc: false,
         disableInteraction: true,
         keyboardNavigation: false,
         tooltipClass: 'custom next-page-link'
       })
       .goToStep(stepNum - (pageFirstStepN - 1))
       .onexit(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
         location.reload();
       })
       .oncomplete(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
         location.reload();
       })
       .start();
    } else {
      if (pageFirstStepN === pageLastStepN) {
        // Only one page step, so add another fake one, so the back and next
        // buttons are added to the popup
        steps.push({});
      }
      introJs()
       .setOptions({
         overlayOpacity: '0.2',
         prevLabel: 'Back',
         nextLabel: 'Next',
         skipLabel: 'End tutorial',
         doneLabel: doneLabel,
         showBullets: false,
         showStepNumbers: false,
         exitOnOverlayClick: false,
         exitOnEsc: false,
         disableInteraction: true,
         keyboardNavigation: false,
         tooltipClass: 'custom next-page-link',
         steps: steps
       })
       .onexit(function() {
         location.reload();
       })
       .oncomplete(function() {
         location.reload();
       })
       .goToStep(stepNum - (pageFirstStepN - 1))
       .onexit(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
       })
       .oncomplete(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
       })
       .start();
    }

    // Page navigation when coming to this page from previous/next page
    $(function() {
      if (stepNum === pageFirstStepN && stepNum > 1) {
        $('.introjs-prevbutton').removeClass('introjs-disabled');
      } else if (stepNum === pageLastStepN && stepNum < TUTORIAL_STEPS_CNT) {
        $('.introjs-nextbutton').removeClass('introjs-disabled');
      }
    });

    // Page navigation when already on this page

    $('.introjs-skipbutton').click(function() {
      Cookies.remove('tutorial_data');
      Cookies.remove('current_tutorial_step');

      endCb();
    });

    $('.introjs-prevbutton').click(function() {
      if (stepNum > 1) {
        Cookies.set('current_tutorial_step', --stepNum);

        if (stepNum === pageFirstStepN && stepNum > 1) {
          $('.introjs-prevbutton').removeClass('introjs-disabled');
        } else if (stepNum < pageFirstStepN) {
          // Go to previous page;

          var prevPagePath = tutorialData[0].backPagesPaths.pop();
          Cookies.set('tutorial_data', tutorialData);
          $('.introjs-prevbutton').attr('href', prevPagePath);
          introJs().exit();
          endCb();
        }
      }
    });

    $('.introjs-nextbutton').click(function() {
      if (stepNum < TUTORIAL_STEPS_CNT) {
        Cookies.set('current_tutorial_step', ++stepNum);

        if (stepNum === pageLastStepN && stepNum < TUTORIAL_STEPS_CNT) {
          $('.introjs-nextbutton').removeClass('introjs-disabled');
        } else if (stepNum > pageLastStepN) {
          // Go to next page

          tutorialData[0].backPagesPaths.push(thisPagePath);
          Cookies.set('tutorial_data', tutorialData);
          $('.introjs-nextbutton').attr('href', nextPagePath);
          introJs().exit();
          endCb();
        }
      }
    });
  }
}

/**
 * Add redirection links on dropdown elements. You must specify 'href'
 * attribute yourself, and the dropdown elments which don't have them, will get
 * '#' by default.
 * @param  {number} selectedIdx Index of element to be selected
 * @param  {string} urlParam    URL parameter to pass to the link URLs
 * @return {Object}             This
 */
$.fn.makeDropdownOptionsLinks = function(selectedIdx, urlParam) {
  selectedIdx = _.isUndefined(selectedIdx) ? 1 : selectedIdx;

  $(this).change(function() {
    window.location.href = addParam($(this).find('option:selected')
                                      .attr('href'), urlParam);
  });

  $(this).find('option')
    .each(function() {
      if ($(this).is('[href]')) {
        $(this).addClass('link-look');
      } else {
        $(this).attr('href', '#');
      }
    })
    .eq(selectedIdx).attr('selected', true);

  return this;
};
<commit_msg>Remove unneeded function from utils.js [SCI-819]<commit_after>/*
 * Converts JSON data received from the server to flat array of values.
 */
function jsonToValuesArray(jsonData) {
  var errMsgs = [];
  for (var key in jsonData) {
  	var values = jsonData[key];
    $.each(values, function (idx, val) {
      errMsgs.push(val);
	});
  }
  return errMsgs;
}

/*
 * Calls callback function on AJAX success (because built-in functions don't
 * work!)
 */
$.fn.onAjaxComplete = function (cb) {
  $(this)
  .on('ajax:success', function () {
	cb();
  })
  .on('ajax:error', function () {
	cb();
  });
}

// Number of all tutorial steps
var TUTORIAL_STEPS_CNT = 26;

/**
 * Initializes tutorial steps for the current page.
 * NOTE: You can specify steps manually in JS with steps parameter (preferred
 * way), or hardcode them in HTML
 * NOTE: If some steps edit page, then this function needs to be called several
 * times for the same page, but for different steps. The same goes if the page
 * has discontinuous tutorial steps. In such cases, use steps branching, e.g.:
 * @example
 * var tutorialData = Cookies.get('tutorial_data');
 * if (tutorialData) {
 *   tutorialData = JSON.parse(tutorialData);
 *   var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
 *
 *   if (stepNum >= 6 && stepNum <= 7) {
 *     ...
 *   } else if ...
 * NOTE: If an element the popup is pointing at is of lesser horizontal length
 * than the popup itself, then it will not be positioned correctly if it's
 * position is top or bottom, so set/change the step's position to either left
 * or right (and don't use any custom styling!), e.g.:
 * @example
 * var steps = [
 *   {
 *     ...
 *     position: 'right'
 *   },
 *   {
 *   ...
 * ];
 * NOTE: If only one page step is needed, then make pageFirstStepN ==
 * pageLastStepN (both represent the one and only step number)
 *
 * @param  {number} pageFirstStepN Page's first step number
 * @param  {number} pageLastStepN Page's last step number
 * @param  {string} nextPagePath Next page absolute path
 * @param {function} beforeCb Callback called before the tutorial starts. Mainly
 *  used for setting 'pointer-events: none' on the elements the page's steps
 *  highlight.
 * @param {function} endCb Callback called after the tutorial ends. Mainly used
 *  for setting 'pointer-events: auto' on the elements the page's steps
 *  highlight.
 * @param {object} steps Optional JSON containing introJs steps. They can be
 *  specified here, or hardcoded in HTML.
 */
function initPageTutorialSteps(pageFirstStepN, pageLastStepN, nextPagePath,
                               beforeCb, endCb, steps) {
  var tutorialData = Cookies.get('tutorial_data');
  if (tutorialData) {
    tutorialData = JSON.parse(tutorialData);
    var stepNum = parseInt(Cookies.get('current_tutorial_step'), 10);
    if (isNaN(stepNum)) {
      // Cookies data initialization
      stepNum = 1;
      Cookies.set('current_tutorial_step', stepNum);
      tutorialData[0].backPagesPaths = [];
      Cookies.set('tutorial_data', tutorialData);
    }
    var thisPagePath = window.location.pathname;
    beforeCb();

    // Initialize tutorial for the current page's steps
    var doneLabel = (pageLastStepN === TUTORIAL_STEPS_CNT) ?
     'Start using sciNote' : 'End tutorial';
    if (_.isUndefined(steps)) {
      introJs()
       .setOptions({
         overlayOpacity: '0.2',
         prevLabel: 'Back',
         nextLabel: 'Next',
         skipLabel: 'End tutorial',
         doneLabel: doneLabel,
         showBullets: false,
         showStepNumbers: false,
         exitOnOverlayClick: false,
         exitOnEsc: false,
         disableInteraction: true,
         keyboardNavigation: false,
         tooltipClass: 'custom next-page-link'
       })
       .goToStep(stepNum - (pageFirstStepN - 1))
       .onexit(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
         location.reload();
       })
       .oncomplete(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
         location.reload();
       })
       .start();
    } else {
      if (pageFirstStepN === pageLastStepN) {
        // Only one page step, so add another fake one, so the back and next
        // buttons are added to the popup
        steps.push({});
      }
      introJs()
       .setOptions({
         overlayOpacity: '0.2',
         prevLabel: 'Back',
         nextLabel: 'Next',
         skipLabel: 'End tutorial',
         doneLabel: doneLabel,
         showBullets: false,
         showStepNumbers: false,
         exitOnOverlayClick: false,
         exitOnEsc: false,
         disableInteraction: true,
         keyboardNavigation: false,
         tooltipClass: 'custom next-page-link',
         steps: steps
       })
       .onexit(function() {
         location.reload();
       })
       .oncomplete(function() {
         location.reload();
       })
       .goToStep(stepNum - (pageFirstStepN - 1))
       .onexit(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
       })
       .oncomplete(function() {
         Cookies.remove('tutorial_data');
         Cookies.remove('current_tutorial_step');
       })
       .start();
    }

    // Page navigation when coming to this page from previous/next page
    $(function() {
      if (stepNum === pageFirstStepN && stepNum > 1) {
        $('.introjs-prevbutton').removeClass('introjs-disabled');
      } else if (stepNum === pageLastStepN && stepNum < TUTORIAL_STEPS_CNT) {
        $('.introjs-nextbutton').removeClass('introjs-disabled');
      }
    });

    // Page navigation when already on this page

    $('.introjs-skipbutton').click(function() {
      Cookies.remove('tutorial_data');
      Cookies.remove('current_tutorial_step');

      endCb();
    });

    $('.introjs-prevbutton').click(function() {
      if (stepNum > 1) {
        Cookies.set('current_tutorial_step', --stepNum);

        if (stepNum === pageFirstStepN && stepNum > 1) {
          $('.introjs-prevbutton').removeClass('introjs-disabled');
        } else if (stepNum < pageFirstStepN) {
          // Go to previous page;

          var prevPagePath = tutorialData[0].backPagesPaths.pop();
          Cookies.set('tutorial_data', tutorialData);
          $('.introjs-prevbutton').attr('href', prevPagePath);
          introJs().exit();
          endCb();
        }
      }
    });

    $('.introjs-nextbutton').click(function() {
      if (stepNum < TUTORIAL_STEPS_CNT) {
        Cookies.set('current_tutorial_step', ++stepNum);

        if (stepNum === pageLastStepN && stepNum < TUTORIAL_STEPS_CNT) {
          $('.introjs-nextbutton').removeClass('introjs-disabled');
        } else if (stepNum > pageLastStepN) {
          // Go to next page

          tutorialData[0].backPagesPaths.push(thisPagePath);
          Cookies.set('tutorial_data', tutorialData);
          $('.introjs-nextbutton').attr('href', nextPagePath);
          introJs().exit();
          endCb();
        }
      }
    });
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import { List, ListItem, Button, Text } from 'react-native-elements';
import Svg, { LinearGradient, Rect, Defs, Stop } from 'react-native-svg';

const FONT_SIZE_DEFAULT = 30;
const FONT_SIZE_SMALL = 20;

const styles = StyleSheet.create({
  header: {
    width: '100%',
    marginTop: '5%',
    textAlign: 'center',
    fontSize: FONT_SIZE_DEFAULT
  },
  container: {
    flexDirection: 'column',
    flex: 1,
    width: '100%',
    height: '100%',
    justifyContent: 'space-between',
    padding: 0
  },
  svg: {
    position: 'absolute',
    zIndex: 0,
    left: 0,
    top: 0,
    width: '100%',
    height: '100%'
  }
});

export default class FavouritesView extends React.Component {

  renderRow (data, idx) {
    return (
      <ListItem
        roundAvatar
        key={idx}
        title={data.label}
        subtitle={data.formattedPrice}
        avatar={{uri:data.largeImageURL}}
        onPress={() => {
          this.props.onSelect(data, idx);
        }}
      />
    )
  }

  render() {

    return (
      <View>
        <Svg style={styles.svg}>
          <Defs>
            <LinearGradient id="lgrad" x1="0%" y1="100%" x2="100%" y2="0%" >
              <Stop offset="0" stopColor="rgb(255, 255, 255)" stopOpacity="1" />
              <Stop offset="1" stopColor="rgb(156, 199, 255)" stopOpacity="1" />
            </LinearGradient>
          </Defs>
          <Rect x="0" y="0" width="100%" height="100%" fill="url(#lgrad)"/>
        </Svg>
        <View style={styles.container}>
          <List containerStyle={{marginTop: 0 }}>
            {this.props.favourites.map((data, idx) => this.renderRow(data, idx))}
          </List>
        </View>
      </View>
    );
  }

}
<commit_msg>make list scrollable<commit_after>import React, { Component } from 'react';
import { StyleSheet, View, ScrollView } from 'react-native';
import { List, ListItem } from 'react-native-elements';
import Svg, { LinearGradient, Rect, Defs, Stop } from 'react-native-svg';

const FONT_SIZE_DEFAULT = 30;
const FONT_SIZE_SMALL = 20;

const styles = StyleSheet.create({
  header: {
    width: '100%',
    marginTop: '5%',
    textAlign: 'center',
    fontSize: FONT_SIZE_DEFAULT
  },
  container: {
    width: '100%',
    height: '100%',
    padding: 0
  },
  list: {
    backgroundColor: '#fff'
  },
  svg: {
    position: 'absolute',
    zIndex: 0,
    left: 0,
    top: 0,
    width: '100%',
    height: '100%'
  }
});

export default class FavouritesView extends React.Component {

  renderRow (data, idx) {
    return (
      <ListItem
        roundAvatar
        key={idx}
        title={data.label}
        subtitle={data.formattedPrice}
        avatar={{uri:data.largeImageURL}}
        onPress={() => {
          this.props.onSelect(data, idx);
        }}
      />
    )
  }

  render() {

    return (
      <View style={styles.container}>
        <Svg style={styles.svg}>
          <Defs>
            <LinearGradient id="lgrad" x1="0%" y1="100%" x2="100%" y2="0%" >
              <Stop offset="0" stopColor="rgb(255, 255, 255)" stopOpacity="1" />
              <Stop offset="1" stopColor="rgb(156, 199, 255)" stopOpacity="1" />
            </LinearGradient>
          </Defs>
          <Rect x="0" y="0" width="100%" height="100%" fill="url(#lgrad)"/>
        </Svg>
        <ScrollView contentContainerStyle={styles.list}>
          {this.props.favourites.map((data, idx) => this.renderRow(data, idx))}
        </ScrollView>
      </View>
    );
  }

}
<|endoftext|>


<commit_before>import React, { PropTypes, Component } from 'react';
import ProgressBar from './ProgressBar';
import Button from './Button';
import CardIcons from './CardIcons';
import Image from './Image';
import * as UserActionCreators from '../../actions/UserActionCreators'
import translate from '../../i18n/Translate';

@translate('CardContent')
export default class CardContent extends Component {

    static propTypes = {
        contentId     : PropTypes.number.isRequired,
        title         : PropTypes.string,
        description   : PropTypes.string,
        types         : PropTypes.array.isRequired,
        url           : PropTypes.string.isRequired,
        embed_id      : PropTypes.string,
        embed_type    : PropTypes.string,
        thumbnail     : PropTypes.string,
        synonymous    : PropTypes.array.isRequired,
        matching      : PropTypes.number,
        rate          : PropTypes.bool,
        hideLikeButton: PropTypes.bool.isRequired,
        fixedHeight   : PropTypes.bool,
        loggedUserId  : PropTypes.number.isRequired,
        onClickHandler: PropTypes.func,
        // Injected by @translate:
        strings       : PropTypes.object
    };

    constructor(props) {
        super(props);

        this.onRate = this.onRate.bind(this);
        this.onClickHandler = this.onClickHandler.bind(this);

        this.state = {
            embedHtml: null
        }
    }

    onRate() {
        const {loggedUserId, contentId} = this.props;
        if (!this.props.rate) {
            UserActionCreators.likeContent(loggedUserId, contentId);
        } else {
            UserActionCreators.deleteLikeContent(loggedUserId, contentId);
        }
    }

    onClickHandler() {
        const {url, types, embed_type, embed_id, onClickHandler} = this.props;
        if (typeof onClickHandler !== 'undefined') {
            onClickHandler();
        } else {
            const isVideo = types.indexOf('Video') > -1;
            if (isVideo) {
                this.preVisualizeYoutube(embed_type, embed_id);
            } else {
                window.cordova ? document.location = url : window.open(url);
            }
        }
    }

    preVisualizeYoutube = function (embed_type, embed_id) {
        let html = null;
        switch (embed_type) {
            case 'youtube':
                html = ;
                break;
            default:
                break;
        }

        this.setState({
            embedHtml: html
        });
    };

    render() {
        const {title, description, types, rate, hideLikeButton, fixedHeight, thumbnail, url, matching, strings} = this.props;
        const cardTitle = title ?
{title.substr(0, 20)}{title.length > 20 ? '...' : ''}
 :
 {strings.emptyTitle}
;
        const subTitle = description ?
{description.substr(0, 20)}{description.length > 20 ? '...' : ''}
 : fixedHeight ?

 : '';
        const likeButtonText = rate ? strings.unlike : strings.like;
        const likeButton = hideLikeButton ? '' :
<Button {...this.props} onClick={this.onRate}>{likeButtonText}</Button>
;
        const imageClass = fixedHeight ? 'image fixed-height-image' : 'image';
        const isImage = types.indexOf('Image') > -1;
        const defaultSrc = 'img/default-content-image.jpg';
        let imgSrc = defaultSrc;
        if (thumbnail) {
            imgSrc = thumbnail;
        } else if (isImage) {
            imgSrc = url;
        }
        return (

                {isImage ? '' :

                                {cardTitle}

                            {subTitle}

                }

                    <CardIcons types={types}/>

                        {this.state.embedHtml ? this.state.embedHtml :

                                    <Image src={imgSrc} defaultSrc={defaultSrc}/>

                        }
                        {!this.state.embedHtml && typeof matching !== 'undefined' ?

{strings.compatibility} {matching ? matching + '%' : '?'}

                                <ProgressBar percentage={matching}/>

                            :
                            null
                        }

                {likeButton ?

                        {likeButton}

                    : ''
                }

        );
    }

}

CardContent.defaultProps = {
    strings: {
        like         : 'Like',
        unlike       : 'Remove',
        compatibility: 'Compatibility',
        emptyTitle   : 'Title'
    }
};<commit_msg>Finish 0.8.1<commit_after>import React, { PropTypes, Component } from 'react';
import ProgressBar from './ProgressBar';
import Button from './Button';
import CardIcons from './CardIcons';
import Image from './Image';
import * as UserActionCreators from '../../actions/UserActionCreators'
import translate from '../../i18n/Translate';

@translate('CardContent')
export default class CardContent extends Component {

    static propTypes = {
        contentId     : PropTypes.number.isRequired,
        title         : PropTypes.string,
        description   : PropTypes.string,
        types         : PropTypes.array.isRequired,
        url           : PropTypes.string.isRequired,
        embed_id      : PropTypes.string,
        embed_type    : PropTypes.string,
        thumbnail     : PropTypes.string,
        synonymous    : PropTypes.array.isRequired,
        matching      : PropTypes.number,
        rate          : PropTypes.bool,
        hideLikeButton: PropTypes.bool.isRequired,
        fixedHeight   : PropTypes.bool,
        loggedUserId  : PropTypes.number.isRequired,
        onClickHandler: PropTypes.func,
        // Injected by @translate:
        strings       : PropTypes.object
    };

    constructor(props) {
        super(props);

        this.onRate = this.onRate.bind(this);
        this.handleClick = this.handleClick.bind(this);

        this.state = {
            embedHtml: null
        }
    }

    onRate() {
        const {loggedUserId, contentId} = this.props;
        if (!this.props.rate) {
            UserActionCreators.likeContent(loggedUserId, contentId);
        } else {
            UserActionCreators.deleteLikeContent(loggedUserId, contentId);
        }
    }

    handleClick() {
        const {url, types, embed_type, embed_id, onClickHandler} = this.props;
        if (typeof onClickHandler !== 'undefined') {
            onClickHandler();
        } else {
            const isVideo = types.indexOf('Video') > -1;
            if (isVideo) {
                this.preVisualizeYoutube(embed_type, embed_id);
            } else {
                window.cordova ? document.location = url : window.open(url);
            }
        }
    }

    preVisualizeYoutube = function (embed_type, embed_id) {
        let html = null;
        switch (embed_type) {
            case 'youtube':
                html = ;
                break;
            default:
                break;
        }

        this.setState({
            embedHtml: html
        });
    };

    render() {
        const {title, description, types, rate, hideLikeButton, fixedHeight, thumbnail, url, matching, strings} = this.props;
        const cardTitle = title ?
{title.substr(0, 20)}{title.length > 20 ? '...' : ''}
 :
 {strings.emptyTitle}
;
        const subTitle = description ?
{description.substr(0, 20)}{description.length > 20 ? '...' : ''}
 : fixedHeight ?

 : '';
        const likeButtonText = rate ? strings.unlike : strings.like;
        const likeButton = hideLikeButton ? '' :
<Button {...this.props} onClick={this.onRate}>{likeButtonText}</Button>
;
        const imageClass = fixedHeight ? 'image fixed-height-image' : 'image';
        const isImage = types.indexOf('Image') > -1;
        const defaultSrc = 'img/default-content-image.jpg';
        let imgSrc = defaultSrc;
        if (thumbnail) {
            imgSrc = thumbnail;
        } else if (isImage) {
            imgSrc = url;
        }
        return (

                {isImage ? '' :

                                {cardTitle}

                            {subTitle}

                }

                    <CardIcons types={types}/>

                        {this.state.embedHtml ? this.state.embedHtml :

                                    <Image src={imgSrc} defaultSrc={defaultSrc}/>

                        }
                        {!this.state.embedHtml && typeof matching !== 'undefined' ?

{strings.compatibility} {matching ? matching + '%' : '?'}

                                <ProgressBar percentage={matching}/>

                            :
                            null
                        }

                {likeButton ?

                        {likeButton}

                    : ''
                }

        );
    }

}

CardContent.defaultProps = {
    strings: {
        like         : 'Like',
        unlike       : 'Remove',
        compatibility: 'Compatibility',
        emptyTitle   : 'Title'
    }
};<|endoftext|>


<commit_before>'use strict';
/* globals ngModule, inject, describe, beforeEach, it */

require('../../../../test/js/test-helpers');

var assert = require('assert');

/*
 * Load module
 */

require('../');

describe('filters', function() {

  beforeEach(ngModule('kzApp.filters'));

  describe('convertTime', function() {
    it('should convert given runtime into dd.MM.yyyy HH:mm:ss.ms format', inject(function($filter) {
      assert.equal($filter('convertTime')('23.23'), '00:23.23');
    }));
  });

});<commit_msg>numOnly test added<commit_after>'use strict';
/* globals ngModule, inject, describe, beforeEach, it */

require('../../../../test/js/test-helpers');

var assert = require('assert');

/*
 * Load module
 */

require('../');

describe('filters', function() {

  beforeEach(ngModule('kzApp.filters'));

  describe('convertTime', function() {
    it('should convert given runtime into dd.MM.yyyy HH:mm:ss.ms format', inject(function($filter) {
      assert.equal($filter('convertTime')('23.23'), '00:23.23');
    }));
  });

  describe('numOnly', function() {
    it('should strip all but numbers from given string', inject(function($filter) {
      assert.equal($filter('numOnly')('abcfg.{4343}_?'), '4343');
    }));
  });

});<|endoftext|>