<commit_before>/*
 * Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
 * Copyright [2016-2022] EMBL-European Bioinformatics Institute
 *
 * 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.
 */

/**
 * selectToToggle - Show/hide an HTML block(s) according to value selected in a <select>, or <input type="radio|checkbox"> element
 * Reserved JS class prefix: _stt
 * Reserved CSS class prefix: none
 * Note: Be careful if there are more than one selectToToggle elements on a page with one or more options having same values (use className on the option tags in those cases)
 **/
(function ($) {
  $.selectToToggle = function (el) {
    var toggle = function () {
      var data      = $(this).data('selectToToggle');
      var wrapper   = data.wrapper;
      var toggleMap = data.toggleMap;
      var currValue = this.nodeName === 'INPUT' && this.type === 'checkbox' && !this.checked ? false : this.value; // if checkbox is not ticked, ignore it's value

      var escapeDotInClassName = function (className) {
        var i = 0;

        return className.replace(/[.]/g, function (match) {
           i += 1;
           return i > 1 ? '\\\.' : '.';
        });
      };

      // go through all the selectors in the toggleMap and hide them except the one that corresponds to current element's value
      for (var val in toggleMap) {
        if (val !== currValue) {
          var elemClass = escapeDotInClassName(toggleMap[val]);

          wrapper.find(elemClass).hide().removeAttr('checked').filter('option').each(function() { // if hiding an option element, also disable it to make it work in webkit
            var option = $(this);

            if (typeof option.data('sttDisabled') === 'undefined') {
              option.data('sttDisabled', !!this.disabled); // remember original disabled attribute
            }
          }).prop('disabled', true);
        }
      }

      var currElemClass = escapeDotInClassName(toggleMap[currValue]);

      // show the html block corresponsing to current element's value
      wrapper.find(currElemClass).show().filter('option').prop('disabled', function() {
        return $(this).data('sttDisabled');
      }).filter('select option').parent().each(function() {
        var dropdown = $(this);
        if (!dropdown.find('option:selected:enabled').length) { //in case any selected option gets hidden in this, select the first visible option
          dropdown.find('option:enabled').first().prop('selected', true);
          if (dropdown.data('selectToToggle')) {
            dropdown.trigger('change.selectToToggle');
          }
        }
      });
    };

    el.off('.selectToToggle').on('change.selectToToggle', toggle).filter('select, input:checked').first().triggerHandler('change.selectToToggle');
  };

  $.fn.selectToToggle = function (
    toggleMap,  // map of select element's option value to corresponding jquery selectors strings (as accepted by find() method) (Optional - defaults to '._stt_[className]' if class name uses prefix _stt__, or '._stt_[value]' otherwise)
                // string 'trigger' to trigger toggling for an existing element (this is useful is option is selected by JS)
                // string 'destroy' to remove all selectToToggle data and events from the element
    wrapper     // wrapper element to call method 'find(selectors)' on - defaults to $(document.body)
  ) {

    return this.each(function () {
      var el        = $(this);
      var data      = el.data('selectToToggle');

      var getAllEls = function(el, wrapper) {
        return el[0].nodeName === 'INPUT' ? wrapper.find('input[name=' + el[0].name + ']') : el;
      };

      if (data) {
        if (toggleMap === 'trigger') {
          if (el[0].nodeName === 'SELECT' || el[0].checked) {
            el.trigger('change.selectToToggle');
          }
        } else if (toggleMap === 'destroy') {
          getAllEls(el, data.wrapper).off('.selectToToggle').removeData('selectToToggle');
        }

      } else {
        var tMap  = $.extend({}, toggleMap);
        wrapper   = wrapper || $(document.body);
        el        = getAllEls(el, wrapper);

        if ($.isEmptyObject(tMap)) {
          (this.nodeName == 'SELECT' ? el.find('option') : el).each(function() {
            if (this.value) {
              var filters = $.map(this.className.match(/(\s+|^)_stt__([^\s]+)/g) || [], function(str) { return str.replace('_stt__', '._stt_') });
                  filters.push('._stt_' + this.value);
              tMap[this.value] = this.className.match(/(\s+|^)_sttmulti($|\s+)/) ? (filters[1] || filters.join(',')) : filters[0];
            }
          });
        }

        el.data('selectToToggle', {'wrapper' : wrapper, 'toggleMap': tMap});

        $.selectToToggle(el);
      }
    });
  };
})(jQuery);
<commit_msg>Fix broken multiple species selector in BLAST form (#921)<commit_after>/*
 * Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
 * Copyright [2016-2022] EMBL-European Bioinformatics Institute
 *
 * 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.
 */

/**
 * selectToToggle - Show/hide an HTML block(s) according to value selected in a <select>, or <input type="radio|checkbox"> element
 * Reserved JS class prefix: _stt
 * Reserved CSS class prefix: none
 * Note: Be careful if there are more than one selectToToggle elements on a page with one or more options having same values (use className on the option tags in those cases)
 **/
(function ($) {
  $.selectToToggle = function (el) {
    var toggle = function () {
      var data      = $(this).data('selectToToggle');
      var wrapper   = data.wrapper;
      var toggleMap = data.toggleMap;
      var currValue = this.nodeName === 'INPUT' && this.type === 'checkbox' && !this.checked ? false : this.value; // if checkbox is not ticked, ignore it's value

      var escapeDotInClassName = function (className) {
        var i = 0;

        return className ? className.replace(/[.]/g, function (match) {
           i += 1;
           return i > 1 ? '\\\.' : '.';
        }) : '';
      };

      // go through all the selectors in the toggleMap and hide them except the one that corresponds to current element's value
      for (var val in toggleMap) {
        if (val !== currValue) {
          var elemClass = escapeDotInClassName(toggleMap[val]);

          wrapper.find(elemClass).hide().removeAttr('checked').filter('option').each(function() { // if hiding an option element, also disable it to make it work in webkit
            var option = $(this);

            if (typeof option.data('sttDisabled') === 'undefined') {
              option.data('sttDisabled', !!this.disabled); // remember original disabled attribute
            }
          }).prop('disabled', true);
        }
      }

      var currElemClass = escapeDotInClassName(toggleMap[currValue]);

      // show the html block corresponsing to current element's value
      wrapper.find(currElemClass).show().filter('option').prop('disabled', function() {
        return $(this).data('sttDisabled');
      }).filter('select option').parent().each(function() {
        var dropdown = $(this);
        if (!dropdown.find('option:selected:enabled').length) { //in case any selected option gets hidden in this, select the first visible option
          dropdown.find('option:enabled').first().prop('selected', true);
          if (dropdown.data('selectToToggle')) {
            dropdown.trigger('change.selectToToggle');
          }
        }
      });
    };

    el.off('.selectToToggle').on('change.selectToToggle', toggle).filter('select, input:checked').first().triggerHandler('change.selectToToggle');
  };

  $.fn.selectToToggle = function (
    toggleMap,  // map of select element's option value to corresponding jquery selectors strings (as accepted by find() method) (Optional - defaults to '._stt_[className]' if class name uses prefix _stt__, or '._stt_[value]' otherwise)
                // string 'trigger' to trigger toggling for an existing element (this is useful if option is selected by JS)
                // string 'destroy' to remove all selectToToggle data and events from the element
    wrapper     // wrapper element to call method 'find(selectors)' on - defaults to $(document.body)
  ) {

    return this.each(function () {
      var el        = $(this);
      var data      = el.data('selectToToggle');

      var getAllEls = function(el, wrapper) {
        return el[0].nodeName === 'INPUT' ? wrapper.find('input[name=' + el[0].name + ']') : el;
      };

      if (data) {
        if (toggleMap === 'trigger') {
          if (el[0].nodeName === 'SELECT' || el[0].checked) {
            el.trigger('change.selectToToggle');
          }
        } else if (toggleMap === 'destroy') {
          getAllEls(el, data.wrapper).off('.selectToToggle').removeData('selectToToggle');
        }

      } else {
        var tMap  = $.extend({}, toggleMap);
        wrapper   = wrapper || $(document.body);
        el        = getAllEls(el, wrapper);

        if ($.isEmptyObject(tMap)) {
          (this.nodeName === 'SELECT' ? el.find('option') : el).each(function() {
            if (this.value) {
              var filters = $.map(this.className.match(/(\s+|^)_stt__([^\s]+)/g) || [], function(str) { return str.replace('_stt__', '._stt_') });
                  filters.push('._stt_' + this.value);
              tMap[this.value] = this.className.match(/(\s+|^)_sttmulti($|\s+)/) ? (filters[1] || filters.join(',')) : filters[0];
            }
          });
        }

        el.data('selectToToggle', {'wrapper' : wrapper, 'toggleMap': tMap});

        $.selectToToggle(el);
      }
    });
  };
})(jQuery);
<|endoftext|>


<commit_before>/**
 * Copyright 2018 The Subscribe with Google Authors. 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.
 */

import {Entitlement, Entitlements} from '../api/entitlements';
import {JwtHelper} from '../utils/jwt';
import {Toast} from '../ui/toast';
import {feArgs, feUrl} from '../runtime/services';
import {serviceUrl} from './services';

const SERVICE_ID = 'subscribe.google.com';
const TOAST_STORAGE_KEY = 'toast';
const ENTS_STORAGE_KEY = 'ents';
const IS_READY_TO_PAY_STORAGE_KEY = 'isreadytopay';

/**
 */
export class EntitlementsManager {
  /**
   * @param {!Window} win
   * @param {!../model/page-config.PageConfig} pageConfig
   * @param {!./fetcher.Fetcher} fetcher
   * @param {!./deps.DepsDef} deps
   */
  constructor(win, pageConfig, fetcher, deps) {
    /** @private @const {!Window} */
    this.win_ = win;

    /** @private @const {!../model/page-config.PageConfig} */
    this.pageConfig_ = pageConfig;

    /** @private @const {string} */
    this.publicationId_ = this.pageConfig_.getPublicationId();

    /** @private @const {!./fetcher.Fetcher} */
    this.fetcher_ = fetcher;

    /** @private @const {!./deps.DepsDef} */
    this.deps_ = deps;

    /** @private @const {!JwtHelper} */
    this.jwtHelper_ = new JwtHelper();

    /** @private {?Promise<!Entitlements>} */
    this.responsePromise_ = null;

    /** @private {number} */
    this.positiveRetries_ = 0;

    /** @private {boolean} */
    this.blockNextNotification_ = false;

    /** @private @const {!./storage.Storage} */
    this.storage_ = deps.storage();

    /** @private @const {!../runtime/analytics-service.AnalyticsService} */
    this.analyticsService_ = deps.analytics();

    /** @private @const {!../api/subscriptions.Config} */
    this.config_ = deps.config();
  }

  /**
   * @param {boolean=} expectPositive
   */
  reset(expectPositive) {
    this.responsePromise_ = null;
    this.positiveRetries_ = Math.max(
      this.positiveRetries_,
      expectPositive ? 3 : 0
    );
    if (expectPositive) {
      this.storage_.remove(ENTS_STORAGE_KEY);
      this.storage_.remove(IS_READY_TO_PAY_STORAGE_KEY);
    }
  }

  /**
   * Clears all of the entitlements state and cache.
   */
  clear() {
    this.responsePromise_ = null;
    this.positiveRetries_ = 0;
    this.unblockNextNotification();
    this.storage_.remove(ENTS_STORAGE_KEY);
    this.storage_.remove(TOAST_STORAGE_KEY);
    this.storage_.remove(IS_READY_TO_PAY_STORAGE_KEY);
  }

  /**
   * @return {string}
   * @private
   */
  getQueryString_() {
    return this.win_.location.search;
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   */
  getEntitlements(encryptedDocumentKey) {
    if (!this.responsePromise_) {
      this.responsePromise_ = this.getEntitlementsFlow_(encryptedDocumentKey);
    }
    return this.responsePromise_.then((response) => {
      if (response.isReadyToPay != null) {
        this.analyticsService_.setReadyToPay(response.isReadyToPay);
      }
      return response;
    });
  }

  /**
   * @param {string} raw
   * @param {boolean=} isReadyToPay
   * @return {boolean}
   */
  pushNextEntitlements(raw, isReadyToPay) {
    const entitlements = this.getValidJwtEntitlements_(
      raw,
      /* requireNonExpired */ true,
      isReadyToPay
    );
    if (entitlements && entitlements.enablesThis()) {
      this.storage_.set(ENTS_STORAGE_KEY, raw);
      return true;
    }
    return false;
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  getEntitlementsFlow_(encryptedDocumentKey) {
    return this.fetchEntitlementsWithCaching_(encryptedDocumentKey).then(
      (entitlements) => {
        this.onEntitlementsFetched_(entitlements);
        return entitlements;
      }
    );
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  fetchEntitlementsWithCaching_(encryptedDocumentKey) {
    return Promise.all([
      this.storage_.get(ENTS_STORAGE_KEY),
      this.storage_.get(IS_READY_TO_PAY_STORAGE_KEY),
    ]).then((cachedValues) => {
      const raw = cachedValues[0];
      const irtp = cachedValues[1];
      // Try cache first.
      if (raw && !encryptedDocumentKey) {
        const cached = this.getValidJwtEntitlements_(
          raw,
          /* requireNonExpired */ true,
          irtpStringToBoolean(irtp)
        );
        if (cached && cached.enablesThis()) {
          // Already have a positive response.
          this.positiveRetries_ = 0;
          return cached;
        }
      }
      // If cache didn't match, perform fetch.
      return this.fetchEntitlements_(encryptedDocumentKey).then((ents) => {
        // If entitlements match the product, store them in cache.
        if (ents && ents.enablesThis() && ents.raw) {
          this.storage_.set(ENTS_STORAGE_KEY, ents.raw);
        }
        return ents;
      });
    });
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  fetchEntitlements_(encryptedDocumentKey) {
    // TODO(dvoytenko): Replace retries with consistent fetch.
    let positiveRetries = this.positiveRetries_;
    this.positiveRetries_ = 0;
    const attempt = () => {
      positiveRetries--;
      return this.fetch_(encryptedDocumentKey).then((entitlements) => {
        if (entitlements.enablesThis() || positiveRetries <= 0) {
          return entitlements;
        }
        return new Promise((resolve) => {
          this.win_.setTimeout(() => {
            resolve(attempt());
          }, 550);
        });
      });
    };
    return attempt();
  }

  /**
   * @param {boolean} value
   */
  setToastShown(value) {
    this.storage_.set(TOAST_STORAGE_KEY, value ? '1' : '0');
  }

  /**
   */
  blockNextNotification() {
    this.blockNextNotification_ = true;
  }

  /**
   */
  unblockNextNotification() {
    this.blockNextNotification_ = false;
  }

  /**
   * The JSON must either contain a "signedEntitlements" with JWT, or
   * "entitlements" field with plain JSON object.
   * @param {!Object} json
   * @return {!Entitlements}
   */
  parseEntitlements(json) {
    const isReadyToPay = json['isReadyToPay'];
    if (isReadyToPay == null) {
      this.storage_.remove(IS_READY_TO_PAY_STORAGE_KEY);
    } else {
      this.storage_.set(IS_READY_TO_PAY_STORAGE_KEY, String(isReadyToPay));
    }
    const signedData = json['signedEntitlements'];
    const decryptedDocumentKey = json['decryptedDocumentKey'];
    if (signedData) {
      const entitlements = this.getValidJwtEntitlements_(
        signedData,
        /* requireNonExpired */ false,
        isReadyToPay,
        decryptedDocumentKey
      );
      if (entitlements) {
        return entitlements;
      }
    } else {
      const plainEntitlements = json['entitlements'];
      if (plainEntitlements) {
        return this.createEntitlements_(
          '',
          plainEntitlements,
          isReadyToPay,
          decryptedDocumentKey
        );
      }
    }
    // Empty response.
    return this.createEntitlements_('', [], isReadyToPay);
  }

  /**
   * @param {string} raw
   * @param {boolean} requireNonExpired
   * @param {boolean=} isReadyToPay
   * @param {?string=} decryptedDocumentKey
   * @return {?Entitlements}
   * @private
   */
  getValidJwtEntitlements_(
    raw,
    requireNonExpired,
    isReadyToPay,
    decryptedDocumentKey
  ) {
    try {
      const jwt = this.jwtHelper_.decode(raw);
      if (requireNonExpired) {
        const now = Date.now();
        const exp = jwt['exp'];
        if (parseFloat(exp) * 1000 < now) {
          return null;
        }
      }
      const entitlementsClaim = jwt['entitlements'];
      return (
        (entitlementsClaim &&
          this.createEntitlements_(
            raw,
            entitlementsClaim,
            isReadyToPay,
            decryptedDocumentKey
          )) ||
        null
      );
    } catch (e) {
      // Ignore the error.
      this.win_.setTimeout(() => {
        throw e;
      });
    }
    return null;
  }

  /**
   * @param {string} raw
   * @param {!Object|!Array<!Object>} json
   * @param {boolean=} isReadyToPay
   * @param {?string=} decryptedDocumentKey
   * @return {!Entitlements}
   * @private
   */
  createEntitlements_(raw, json, isReadyToPay, decryptedDocumentKey) {
    return new Entitlements(
      SERVICE_ID,
      raw,
      Entitlement.parseListFromJson(json),
      this.pageConfig_.getProductId(),
      this.ack_.bind(this),
      isReadyToPay,
      decryptedDocumentKey
    );
  }

  /**
   * @param {!Entitlements} entitlements
   * @private
   */
  onEntitlementsFetched_(entitlements) {
    // Skip any notifications and toast if other flows are ongoing.
    // TODO(dvoytenko): what's the right action when pay flow was canceled?
    const blockNotification = this.blockNextNotification_;
    this.blockNextNotification_ = false;
    if (blockNotification) {
      return;
    }

    // Notify on the received entitlements.
    this.deps_
      .callbacks()
      .triggerEntitlementsResponse(Promise.resolve(entitlements));

    // Show a toast if needed.
    this.maybeShowToast_(entitlements);
  }

  /**
   * @param {!Entitlements} entitlements
   * @return {!Promise}
   * @private
   */
  maybeShowToast_(entitlements) {
    const entitlement = entitlements.getEntitlementForThis();
    if (!entitlement) {
      return Promise.resolve();
    }
    // Check if storage bit is set. It's only set by the `Entitlements.ack`
    // method.
    return this.storage_.get(TOAST_STORAGE_KEY).then((value) => {
      if (value == '1') {
        // Already shown;
        return;
      }
      if (entitlement) {
        this.showToast_(entitlement);
      }
    });
  }

  /**
   * @param {!Entitlement} entitlement
   * @private
   */
  showToast_(entitlement) {
    const source = entitlement.source || 'google';
    return new Toast(
      this.deps_,
      feUrl('/toastiframe'),
      feArgs({
        'publicationId': this.publicationId_,
        'source': source,
      })
    ).open();
  }

  /**
   * @param {!Entitlements} entitlements
   * @private
   */
  ack_(entitlements) {
    if (entitlements.getEntitlementForThis()) {
      this.setToastShown(true);
    }
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  fetch_(encryptedDocumentKey) {
    let url =
      '/publication/' +
      encodeURIComponent(this.publicationId_) +
      '/entitlements';
    if (encryptedDocumentKey) {
      //TODO(chenshay): Make this a 'Post'.
      url += '?crypt=' + encodeURIComponent(encryptedDocumentKey);
    }
    return this.fetcher_
      .fetchCredentialedJson(serviceUrl(url))
      .then((json) => this.parseEntitlements(json));
  }
}

/**
 * Convert String value of isReadyToPay
 * (from JSON or Cache) to a boolean value.
 * @param {string} value
 * @return {boolean|undefined}
 * @private
 */
function irtpStringToBoolean(value) {
  switch (value) {
    case 'true':
      return true;
    case 'false':
      return false;
    default:
      return undefined;
  }
}
<commit_msg>remove obsolete TODO<commit_after>/**
 * Copyright 2018 The Subscribe with Google Authors. 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.
 */

import {Entitlement, Entitlements} from '../api/entitlements';
import {JwtHelper} from '../utils/jwt';
import {Toast} from '../ui/toast';
import {feArgs, feUrl} from '../runtime/services';
import {serviceUrl} from './services';

const SERVICE_ID = 'subscribe.google.com';
const TOAST_STORAGE_KEY = 'toast';
const ENTS_STORAGE_KEY = 'ents';
const IS_READY_TO_PAY_STORAGE_KEY = 'isreadytopay';

/**
 */
export class EntitlementsManager {
  /**
   * @param {!Window} win
   * @param {!../model/page-config.PageConfig} pageConfig
   * @param {!./fetcher.Fetcher} fetcher
   * @param {!./deps.DepsDef} deps
   */
  constructor(win, pageConfig, fetcher, deps) {
    /** @private @const {!Window} */
    this.win_ = win;

    /** @private @const {!../model/page-config.PageConfig} */
    this.pageConfig_ = pageConfig;

    /** @private @const {string} */
    this.publicationId_ = this.pageConfig_.getPublicationId();

    /** @private @const {!./fetcher.Fetcher} */
    this.fetcher_ = fetcher;

    /** @private @const {!./deps.DepsDef} */
    this.deps_ = deps;

    /** @private @const {!JwtHelper} */
    this.jwtHelper_ = new JwtHelper();

    /** @private {?Promise<!Entitlements>} */
    this.responsePromise_ = null;

    /** @private {number} */
    this.positiveRetries_ = 0;

    /** @private {boolean} */
    this.blockNextNotification_ = false;

    /** @private @const {!./storage.Storage} */
    this.storage_ = deps.storage();

    /** @private @const {!../runtime/analytics-service.AnalyticsService} */
    this.analyticsService_ = deps.analytics();

    /** @private @const {!../api/subscriptions.Config} */
    this.config_ = deps.config();
  }

  /**
   * @param {boolean=} expectPositive
   */
  reset(expectPositive) {
    this.responsePromise_ = null;
    this.positiveRetries_ = Math.max(
      this.positiveRetries_,
      expectPositive ? 3 : 0
    );
    if (expectPositive) {
      this.storage_.remove(ENTS_STORAGE_KEY);
      this.storage_.remove(IS_READY_TO_PAY_STORAGE_KEY);
    }
  }

  /**
   * Clears all of the entitlements state and cache.
   */
  clear() {
    this.responsePromise_ = null;
    this.positiveRetries_ = 0;
    this.unblockNextNotification();
    this.storage_.remove(ENTS_STORAGE_KEY);
    this.storage_.remove(TOAST_STORAGE_KEY);
    this.storage_.remove(IS_READY_TO_PAY_STORAGE_KEY);
  }

  /**
   * @return {string}
   * @private
   */
  getQueryString_() {
    return this.win_.location.search;
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   */
  getEntitlements(encryptedDocumentKey) {
    if (!this.responsePromise_) {
      this.responsePromise_ = this.getEntitlementsFlow_(encryptedDocumentKey);
    }
    return this.responsePromise_.then((response) => {
      if (response.isReadyToPay != null) {
        this.analyticsService_.setReadyToPay(response.isReadyToPay);
      }
      return response;
    });
  }

  /**
   * @param {string} raw
   * @param {boolean=} isReadyToPay
   * @return {boolean}
   */
  pushNextEntitlements(raw, isReadyToPay) {
    const entitlements = this.getValidJwtEntitlements_(
      raw,
      /* requireNonExpired */ true,
      isReadyToPay
    );
    if (entitlements && entitlements.enablesThis()) {
      this.storage_.set(ENTS_STORAGE_KEY, raw);
      return true;
    }
    return false;
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  getEntitlementsFlow_(encryptedDocumentKey) {
    return this.fetchEntitlementsWithCaching_(encryptedDocumentKey).then(
      (entitlements) => {
        this.onEntitlementsFetched_(entitlements);
        return entitlements;
      }
    );
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  fetchEntitlementsWithCaching_(encryptedDocumentKey) {
    return Promise.all([
      this.storage_.get(ENTS_STORAGE_KEY),
      this.storage_.get(IS_READY_TO_PAY_STORAGE_KEY),
    ]).then((cachedValues) => {
      const raw = cachedValues[0];
      const irtp = cachedValues[1];
      // Try cache first.
      if (raw && !encryptedDocumentKey) {
        const cached = this.getValidJwtEntitlements_(
          raw,
          /* requireNonExpired */ true,
          irtpStringToBoolean(irtp)
        );
        if (cached && cached.enablesThis()) {
          // Already have a positive response.
          this.positiveRetries_ = 0;
          return cached;
        }
      }
      // If cache didn't match, perform fetch.
      return this.fetchEntitlements_(encryptedDocumentKey).then((ents) => {
        // If entitlements match the product, store them in cache.
        if (ents && ents.enablesThis() && ents.raw) {
          this.storage_.set(ENTS_STORAGE_KEY, ents.raw);
        }
        return ents;
      });
    });
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  fetchEntitlements_(encryptedDocumentKey) {
    // TODO(dvoytenko): Replace retries with consistent fetch.
    let positiveRetries = this.positiveRetries_;
    this.positiveRetries_ = 0;
    const attempt = () => {
      positiveRetries--;
      return this.fetch_(encryptedDocumentKey).then((entitlements) => {
        if (entitlements.enablesThis() || positiveRetries <= 0) {
          return entitlements;
        }
        return new Promise((resolve) => {
          this.win_.setTimeout(() => {
            resolve(attempt());
          }, 550);
        });
      });
    };
    return attempt();
  }

  /**
   * @param {boolean} value
   */
  setToastShown(value) {
    this.storage_.set(TOAST_STORAGE_KEY, value ? '1' : '0');
  }

  /**
   */
  blockNextNotification() {
    this.blockNextNotification_ = true;
  }

  /**
   */
  unblockNextNotification() {
    this.blockNextNotification_ = false;
  }

  /**
   * The JSON must either contain a "signedEntitlements" with JWT, or
   * "entitlements" field with plain JSON object.
   * @param {!Object} json
   * @return {!Entitlements}
   */
  parseEntitlements(json) {
    const isReadyToPay = json['isReadyToPay'];
    if (isReadyToPay == null) {
      this.storage_.remove(IS_READY_TO_PAY_STORAGE_KEY);
    } else {
      this.storage_.set(IS_READY_TO_PAY_STORAGE_KEY, String(isReadyToPay));
    }
    const signedData = json['signedEntitlements'];
    const decryptedDocumentKey = json['decryptedDocumentKey'];
    if (signedData) {
      const entitlements = this.getValidJwtEntitlements_(
        signedData,
        /* requireNonExpired */ false,
        isReadyToPay,
        decryptedDocumentKey
      );
      if (entitlements) {
        return entitlements;
      }
    } else {
      const plainEntitlements = json['entitlements'];
      if (plainEntitlements) {
        return this.createEntitlements_(
          '',
          plainEntitlements,
          isReadyToPay,
          decryptedDocumentKey
        );
      }
    }
    // Empty response.
    return this.createEntitlements_('', [], isReadyToPay);
  }

  /**
   * @param {string} raw
   * @param {boolean} requireNonExpired
   * @param {boolean=} isReadyToPay
   * @param {?string=} decryptedDocumentKey
   * @return {?Entitlements}
   * @private
   */
  getValidJwtEntitlements_(
    raw,
    requireNonExpired,
    isReadyToPay,
    decryptedDocumentKey
  ) {
    try {
      const jwt = this.jwtHelper_.decode(raw);
      if (requireNonExpired) {
        const now = Date.now();
        const exp = jwt['exp'];
        if (parseFloat(exp) * 1000 < now) {
          return null;
        }
      }
      const entitlementsClaim = jwt['entitlements'];
      return (
        (entitlementsClaim &&
          this.createEntitlements_(
            raw,
            entitlementsClaim,
            isReadyToPay,
            decryptedDocumentKey
          )) ||
        null
      );
    } catch (e) {
      // Ignore the error.
      this.win_.setTimeout(() => {
        throw e;
      });
    }
    return null;
  }

  /**
   * @param {string} raw
   * @param {!Object|!Array<!Object>} json
   * @param {boolean=} isReadyToPay
   * @param {?string=} decryptedDocumentKey
   * @return {!Entitlements}
   * @private
   */
  createEntitlements_(raw, json, isReadyToPay, decryptedDocumentKey) {
    return new Entitlements(
      SERVICE_ID,
      raw,
      Entitlement.parseListFromJson(json),
      this.pageConfig_.getProductId(),
      this.ack_.bind(this),
      isReadyToPay,
      decryptedDocumentKey
    );
  }

  /**
   * @param {!Entitlements} entitlements
   * @private
   */
  onEntitlementsFetched_(entitlements) {
    // Skip any notifications and toast if other flows are ongoing.
    // TODO(dvoytenko): what's the right action when pay flow was canceled?
    const blockNotification = this.blockNextNotification_;
    this.blockNextNotification_ = false;
    if (blockNotification) {
      return;
    }

    // Notify on the received entitlements.
    this.deps_
      .callbacks()
      .triggerEntitlementsResponse(Promise.resolve(entitlements));

    // Show a toast if needed.
    this.maybeShowToast_(entitlements);
  }

  /**
   * @param {!Entitlements} entitlements
   * @return {!Promise}
   * @private
   */
  maybeShowToast_(entitlements) {
    const entitlement = entitlements.getEntitlementForThis();
    if (!entitlement) {
      return Promise.resolve();
    }
    // Check if storage bit is set. It's only set by the `Entitlements.ack`
    // method.
    return this.storage_.get(TOAST_STORAGE_KEY).then((value) => {
      if (value == '1') {
        // Already shown;
        return;
      }
      if (entitlement) {
        this.showToast_(entitlement);
      }
    });
  }

  /**
   * @param {!Entitlement} entitlement
   * @private
   */
  showToast_(entitlement) {
    const source = entitlement.source || 'google';
    return new Toast(
      this.deps_,
      feUrl('/toastiframe'),
      feArgs({
        'publicationId': this.publicationId_,
        'source': source,
      })
    ).open();
  }

  /**
   * @param {!Entitlements} entitlements
   * @private
   */
  ack_(entitlements) {
    if (entitlements.getEntitlementForThis()) {
      this.setToastShown(true);
    }
  }

  /**
   * @param {?string=} encryptedDocumentKey
   * @return {!Promise<!Entitlements>}
   * @private
   */
  fetch_(encryptedDocumentKey) {
    let url =
      '/publication/' +
      encodeURIComponent(this.publicationId_) +
      '/entitlements';
    if (encryptedDocumentKey) {
      url += '?crypt=' + encodeURIComponent(encryptedDocumentKey);
    }
    return this.fetcher_
      .fetchCredentialedJson(serviceUrl(url))
      .then((json) => this.parseEntitlements(json));
  }
}

/**
 * Convert String value of isReadyToPay
 * (from JSON or Cache) to a boolean value.
 * @param {string} value
 * @return {boolean|undefined}
 * @private
 */
function irtpStringToBoolean(value) {
  switch (value) {
    case 'true':
      return true;
    case 'false':
      return false;
    default:
      return undefined;
  }
}
<|endoftext|>


<commit_before>// React = Npm.require('react');
Components = {};
<commit_msg>Clean up.<commit_after>Components = {};
<|endoftext|>


<commit_before>import React from 'react';

import Analyzer from 'Parser/Core/Analyzer';
import Tab from 'Main/Tab';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import SPELLS from 'common/SPELLS';
import Icon from 'common/Icon';
import SpellLink from 'common/SpellLink';
import Wrapper from 'common/Wrapper';

import ComboPointTracker from './ComboPointTracker';

import ResourceBreakdown from '../ResourceTracker/ResourceBreakdown';
import resourceSuggest from '../ResourceTracker/ResourceSuggest';

class ComboPointDetails extends Analyzer {
  static dependencies = {
    comboPointTracker: ComboPointTracker,
  };

  makeExtraSuggestion(spell) {
    return <Wrapper>Avoid wasting combo points when casting <SpellLink id={spell.id}  /> </Wrapper>
  }

  suggestions(when) {
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.BACKSTAB,
      minor: 0.05,
      avg: 0.10,
      major: 0.15,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.BACKSTAB),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.GLOOMBLADE_TALENT,
      minor: 0.05,
      avg: 0.10,
      major: 0.15,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.GLOOMBLADE_TALENT),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.SHADOWSTRIKE,
      minor: 0.05,
      avg: 0.10,
      major: 0.15,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.SHADOWSTRIKE),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.SHURIKEN_STORM,
      minor: 0.1,
      avg: 0.2,
      major: 0.3,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.SHURIKEN_STORM),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.SHADOW_TECHNIQUES,
      minor: 0.1,
      avg: 0.15,
      major: 0.20,
      extraSuggestion: <span> Use a weak Aura to track <SpellLink id={SPELLS.SHADOW_TECHNIQUES.id}/>. This is an advanced suggestion and should not be addressed first. </span>,
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.GOREMAWS_BITE_ENERGY,
      minor: 0.05,
      avg: 0.1,
      major: 0.15,
      extraSuggestion: <span> Cast <SpellLink id={SPELLS.GOREMAWS_BITE.id}/> when you are on or below 3 combo points </span>,
    });
    }

  statistic() {
    const pointsWasted = this.comboPointTracker.wasted;
    const pointsWastedPerMinute = (pointsWasted / this.owner.fightDuration) * 1000 * 60;
    return (
      <StatisticBox
        icon={<Icon icon="ability_rogue_masterofsubtlety" alt="Waisted Combo Points" />}
        value={`${pointsWastedPerMinute.toFixed(2)}`}
        label="Wasted Combo Points per minute"
        tooltip={`You waisted a total of ${pointsWasted} combo points. Some waste is expected due to the random nature of some generation abilities.`}
      />
    );
  }

  tab() {
    return {
      title: 'Combo Point usage',
      url: 'combo-points',
      render: () => (
        <Tab title="Combo Point usage breakdown">
          <ResourceBreakdown
            tracker={this.comboPointTracker}
            resourceName="Combo Points"
            showSpenders={true}
          />
        </Tab>
      ),
    };
 }

  statisticOrder = STATISTIC_ORDER.CORE(5);
}

export default ComboPointDetails;
<commit_msg>missing semicolon<commit_after>import React from 'react';

import Analyzer from 'Parser/Core/Analyzer';
import Tab from 'Main/Tab';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import SPELLS from 'common/SPELLS';
import Icon from 'common/Icon';
import SpellLink from 'common/SpellLink';
import Wrapper from 'common/Wrapper';

import ComboPointTracker from './ComboPointTracker';

import ResourceBreakdown from '../ResourceTracker/ResourceBreakdown';
import resourceSuggest from '../ResourceTracker/ResourceSuggest';

class ComboPointDetails extends Analyzer {
  static dependencies = {
    comboPointTracker: ComboPointTracker,
  };

  makeExtraSuggestion(spell) {
    return <Wrapper>Avoid wasting combo points when casting <SpellLink id={spell.id}  /> </Wrapper>;
  }

  suggestions(when) {
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.BACKSTAB,
      minor: 0.05,
      avg: 0.10,
      major: 0.15,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.BACKSTAB),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.GLOOMBLADE_TALENT,
      minor: 0.05,
      avg: 0.10,
      major: 0.15,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.GLOOMBLADE_TALENT),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.SHADOWSTRIKE,
      minor: 0.05,
      avg: 0.10,
      major: 0.15,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.SHADOWSTRIKE),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.SHURIKEN_STORM,
      minor: 0.1,
      avg: 0.2,
      major: 0.3,
      extraSuggestion: this.makeExtraSuggestion(SPELLS.SHURIKEN_STORM),
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.SHADOW_TECHNIQUES,
      minor: 0.1,
      avg: 0.15,
      major: 0.20,
      extraSuggestion: <span> Use a weak Aura to track <SpellLink id={SPELLS.SHADOW_TECHNIQUES.id}/>. This is an advanced suggestion and should not be addressed first. </span>,
    });
    resourceSuggest(when,  this.comboPointTracker, {
      spell: SPELLS.GOREMAWS_BITE_ENERGY,
      minor: 0.05,
      avg: 0.1,
      major: 0.15,
      extraSuggestion: <span> Cast <SpellLink id={SPELLS.GOREMAWS_BITE.id}/> when you are on or below 3 combo points </span>,
    });
    }

  statistic() {
    const pointsWasted = this.comboPointTracker.wasted;
    const pointsWastedPerMinute = (pointsWasted / this.owner.fightDuration) * 1000 * 60;
    return (
      <StatisticBox
        icon={<Icon icon="ability_rogue_masterofsubtlety" alt="Waisted Combo Points" />}
        value={`${pointsWastedPerMinute.toFixed(2)}`}
        label="Wasted Combo Points per minute"
        tooltip={`You waisted a total of ${pointsWasted} combo points. Some waste is expected due to the random nature of some generation abilities.`}
      />
    );
  }

  tab() {
    return {
      title: 'Combo Point usage',
      url: 'combo-points',
      render: () => (
        <Tab title="Combo Point usage breakdown">
          <ResourceBreakdown
            tracker={this.comboPointTracker}
            resourceName="Combo Points"
            showSpenders={true}
          />
        </Tab>
      ),
    };
 }

  statisticOrder = STATISTIC_ORDER.CORE(5);
}

export default ComboPointDetails;
<|endoftext|>


<commit_before>import reqwest from 'reqwest';
import SearchAppDispatcher from '../dispatcher/SearchAppDispatcher';
import ActionTypes from '../constants/ActionTypes';

class TranscriptSearchAPI {

    search(query, interval) {
        var path = '/api/search?interval=' + encodeURIComponent(interval) + '&query=' + encodeURIComponent(query);

        return reqwest(path).then(result => {
            SearchAppDispatcher.handleServerAction({
                type: ActionTypes.SEARCH_RESULT,
                query: query,
                result: result
            });
        });
    }

}

module.exports = new TranscriptSearchAPI();
<commit_msg>Whitespace change to test deployment.<commit_after>import reqwest from 'reqwest';
import SearchAppDispatcher from '../dispatcher/SearchAppDispatcher';
import ActionTypes from '../constants/ActionTypes';

class TranscriptSearchAPI {
    search(query, interval) {
        var path = '/api/search?interval=' + encodeURIComponent(interval) + '&query=' + encodeURIComponent(query);

        return reqwest(path).then(result => {
            SearchAppDispatcher.handleServerAction({
                type: ActionTypes.SEARCH_RESULT,
                query: query,
                result: result
            });
        });
    }
}

module.exports = new TranscriptSearchAPI();
<|endoftext|>


<commit_before>/**
 * selectToToggle - Show/hide an HTML block(s) according to value selected in a <select>, or <input type="radio"> element
 * Reserved JS class prefix: _stt
 * Reserved CSS class prefix: none
 * Note: Be careful if there are more than one selectToToggle elements on a page with one or more options having same values (use className on the option tags in those cases)
 **/
(function ($) {
  $.selectToToggle = function (el, toggleMap, wrapper) {

    var toggle = function() {
      for (var val in toggleMap) {
        wrapper.find(toggleMap[val]).hide();
      }
      wrapper.find(toggleMap[this.value]).show();
    };

    el.on('change.selectToToggle', toggle);
    if (el[0].nodeName == 'SELECT' || el[0].checked) {
      toggle.apply(el[0]);
    }
  };

  $.fn.selectToToggle = function (
    toggleMap,  // map of select element's option value to corresponding jquery selectors strings (as accepted by find() method) (Optional - defaults to '._stt_[className]' if class name uses prefix _stt__, or '._stt_[value]' otherwise)
    wrapper     // wrapper element to call method 'find(selectors)' on - defaults to $(document.body)
  ) {

    return this.each(function () {
      var input = $(this);
      var tMap  = $.extend({}, toggleMap);
      wrapper   = wrapper || $(document.body);
      if ($.isEmptyObject(tMap)) {
        (this.nodeName == 'SELECT' ? input.find('option') : wrapper.find('input[name=' + this.name + ']')).each(function() {
          if (this.value) {
            tMap[this.value] = '._stt_' + ((this.className.match(/(?:\s+|^)_stt__([^\s]+)/) || []).pop() || this.value);
          }
        });
      }
      $.selectToToggle(input, tMap, wrapper);
    });
  };
})(jQuery);<commit_msg>improved to make it work with <option> elements - now the option element does not stay selected if it's made hidden<commit_after>/**
 * selectToToggle - Show/hide an HTML block(s) according to value selected in a <select>, or <input type="radio"> element
 * Reserved JS class prefix: _stt
 * Reserved CSS class prefix: none
 * Note: Be careful if there are more than one selectToToggle elements on a page with one or more options having same values (use className on the option tags in those cases)
 **/
(function ($) {
  $.selectToToggle = function (el, toggleMap, wrapper) {

    var toggle = function() {
      for (var val in toggleMap) {
        if (val != this.value) {
          wrapper.find(toggleMap[val]).hide().removeAttr('selected checked');
        }
      }

      wrapper.find(toggleMap[this.value]).show().filter('select option').parent().each(function() { //show the requried html block
        var dropdown = $(this);
        if (!dropdown.find('option:selected:visible').length) { //in case any selected option got hidden in this, select one of the visible ones
          dropdown.find('option:visible').first().attr('selected', true);
        }
      });
    };

    el.on('change.selectToToggle', toggle);
    if (el[0].nodeName == 'SELECT' || el[0].checked) {
      toggle.apply(el[0]);
    }
  };

  $.fn.selectToToggle = function (
    toggleMap,  // map of select element's option value to corresponding jquery selectors strings (as accepted by find() method) (Optional - defaults to '._stt_[className]' if class name uses prefix _stt__, or '._stt_[value]' otherwise)
    wrapper     // wrapper element to call method 'find(selectors)' on - defaults to $(document.body)
  ) {

    return this.each(function () {
      var input = $(this);
      var tMap  = $.extend({}, toggleMap);
      wrapper   = wrapper || $(document.body);
      if ($.isEmptyObject(tMap)) {
        (this.nodeName == 'SELECT' ? input.find('option') : wrapper.find('input[name=' + this.name + ']')).each(function() {
          if (this.value) {
            tMap[this.value] = '._stt_' + ((this.className.match(/(?:\s+|^)_stt__([^\s]+)/) || []).pop() || this.value);
          }
        });
      }
      $.selectToToggle(input, tMap, wrapper);
    });
  };
})(jQuery);<|endoftext|>


<commit_before>
var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
        setTimeout(function() {
            navigator.splashscreen.hide();}, 2000);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');

        console.log('Received Event: ' + id);
    },
};

app.initialize();<commit_msg>スプラッシュスクリーンを非表示にする処理の呼び出しを500msに変更<commit_after>
var app = {
    // Application Constructor
    initialize: function() {
        this.bindEvents();
    },
    // Bind Event Listeners
    //
    // Bind any events that are required on startup. Common events are:
    // 'load', 'deviceready', 'offline', and 'online'.
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
        setTimeout(function() {
            navigator.splashscreen.hide();}, 500);
    },
    // deviceready Event Handler
    //
    // The scope of 'this' is the event. In order to call the 'receivedEvent'
    // function, we must explicitly call 'app.receivedEvent(...);'
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // Update DOM on a Received Event
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');

        console.log('Received Event: ' + id);
    },
};

app.initialize();<|endoftext|>


<commit_before>/*jslint indent: 2, nomen: true, maxlen: 100, white: true  plusplus: true */
/*global _, $, d3*/
/*global ColourMapper*/
////////////////////////////////////////////////////////////////////////////////
/// @brief Graph functionality
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

/*
* flags example format:
* {
*   shape: {
*     type: EdgeShaper.shapes.ARROW
*   }
*   label: "key" \\ function(node)
*   actions: {
*     click: function(edge)
*   }
* }
*
*
*/

function EdgeShaper(parent, flags, idfunc) {
  "use strict";

  var self = this,
    edges = [],
    toplevelSVG,

    idFunction = function(d) {
      return d.source._id + "-" + d.target._id;
    },
    noop = function (line, g) {

    },
    colourMapper = new ColourMapper(),
    events,
    addUpdate,
    addShape = noop,
    addLabel = noop,
    addColor = noop,

    unbindEvents = function() {
     events = {
       click: noop,
       dblclick: noop,
       mousedown: noop,
       mouseup: noop,
       mousemove: noop
     };
     addUpdate = noop;
    },

    addEvents = function (line, g) {
      _.each(events, function (func, type) {
        g.on(type, func);
      });
    },

    bindEvent = function (type, func) {
      if (type === "update") {
        addUpdate = func;
      } else if (events[type] === undefined) {
        throw "Sorry Unknown Event " + type + " cannot be bound.";
      } else {
        events[type] = func;
      }
    },

    addQue = function (line, g) {
      addShape(line, g);
      addLabel(line, g);
      addColor(line, g);
      addEvents(line, g);
    },

    shapeEdges = function (newEdges) {
      if (newEdges !== undefined) {
        edges = newEdges;
      }
      var line,
        g = self.parent
          .selectAll(".link")
          .data(edges, idFunction);
      // Append the group and class to all new
      g.enter()
        .append("g")
        .attr("class", "link") // link is CSS class that might be edited
        .attr("id", idFunction);
      // Remove all old
      g.exit().remove();
      // Remove all elements that are still included.
      g.selectAll("* > *").remove();
      line = g.append("line");
      addQue(line, g);
    },

    getCorner = function(s, t) {
      return Math.atan2(t.y - s.y, t.x - s.x) * 180 / Math.PI;
    },

    getDistance = function(s, t) {
      return Math.sqrt(
        (t.y - s.y)
        * (t.y - s.y)
        + (t.x - s.x)
        * (t.x - s.x)
      );
    },

    updateEdges = function () {

      var edges = self.parent.selectAll(".link")
        // Set source x coordinate for edge.
        .attr("transform", function(d) {
          return "translate("
            + d.source.x + ", "
            + d.source.y + ")"
            + "rotate("
            + getCorner(d.source, d.target)
            + ")";
        });
      edges.select("line")
          .attr("x2", function(d) {
            return getDistance(d.source, d.target);
          });
      addUpdate(edges);
    },

    parseShapeFlag = function (shape) {
      $("svg defs marker#arrow").remove();
      switch (shape.type) {
        case EdgeShaper.shapes.NONE:
          addShape = noop;
          break;
        case EdgeShaper.shapes.ARROW:
          addShape = function (line, g) {
            line.attr("marker-end", "url(#arrow)");
          };
          if (toplevelSVG.selectAll("defs")[0].length === 0) {
            toplevelSVG.append("defs");
          }
          toplevelSVG
            .select("defs")
            .append("marker")
            .attr("id", "arrow")
            .attr("refX", "22")
            .attr("refY", "5")
            .attr("markerUnits", "strokeWidth")
            .attr("markerHeight", "10")
            .attr("markerWidth", "10")
            .attr("orient", "auto")
            .append("path")
              .attr("d", "M 0 0 L 10 5 L 0 10 z");
          break;
        default:
          throw "Sorry given Shape not known!";
      }
    },

    parseLabelFlag = function (label) {
      if (_.isFunction(label)) {
        addLabel = function (line, g) {
          g.append("text") // Append a label for the edge
            .attr("text-anchor", "middle") // Define text-anchor
            .text(label);
        };
      } else {
        addLabel = function (line, g) {
          g.append("text") // Append a label for the edge
            .attr("text-anchor", "middle") // Define text-anchor
            .text(function(d) {
              // Which value should be used as label
              return d._data[label] !== undefined ? d._data[label] : "";
            });
        };
      }
      addUpdate = function (edges) {
        edges.select("text")
          .attr("transform", function(d) {
            return "translate("
              + getDistance(d.source, d.target) / 2
              + ", -3)";
          });
      };
    },

    parseActionFlag = function (actions) {
      if (actions.reset !== undefined && actions.reset) {
        unbindEvents();
      }
      _.each(actions, function(func, type) {
        if (type !== "reset") {
          bindEvent(type, func);
        }
      });
    },

    parseColorFlag = function (color) {
      $("svg defs #gradientEdgeColor").remove();
      switch (color.type) {
        case "single":
          addColor = function (line, g) {
            line.attr("stroke", color.stroke);
          };
          break;
        case "gradient":
          if (toplevelSVG.selectAll("defs")[0].length === 0) {
            toplevelSVG.append("defs");
          }
          var gradient = toplevelSVG
            .select("defs")
            .append("linearGradient")
            .attr("id", "gradientEdgeColor");
          gradient.append("stop")
            .attr("offset", "0")
            .attr("stop-color", color.source);
          gradient.append("stop")
            .attr("offset", "0.4")
            .attr("stop-color", color.source);
          gradient.append("stop")
            .attr("offset", "0.6")
            .attr("stop-color", color.target);
          gradient.append("stop")
            .attr("offset", "1")
            .attr("stop-color", color.target);
          addColor = function (line, g) {
            line.attr("stroke", "url(#gradientEdgeColor)");
            line.attr("y2", "0.0000000000000001");
          };
          break;
        case "attribute":
          addColor = function (line, g) {
             g.attr("stroke", function(e) {
               return colourMapper.getColour(e._data[color.key]);
             });
          };
          break;
        default:
          throw "Sorry given colour-scheme not known";
      }
    },

    parseConfig = function(config) {
      if (config.shape !== undefined) {
        parseShapeFlag(config.shape);
      }
      if (config.label !== undefined) {
        parseLabelFlag(config.label);
      }
      if (config.actions !== undefined) {
        parseActionFlag(config.actions);
      }
      if (config.color !== undefined) {
        parseColorFlag(config.color);
      }
    };

  self.parent = parent;

  unbindEvents();

  toplevelSVG = parent;
  while (toplevelSVG[0][0] && toplevelSVG[0][0].ownerSVGElement) {
    toplevelSVG = d3.select(toplevelSVG[0][0].ownerSVGElement);
  }

  if (flags === undefined) {
    flags = {
      color: {
        type: "single",
        stroke: "#686766"
      }
    };
  }

  if (flags.color === undefined) {
    flags.color = {
      type: "single",
      stroke: "#686766"
    };
  }

  parseConfig(flags);

  if (_.isFunction(idfunc)) {
    idFunction = idfunc;
  }

  /////////////////////////////////////////////////////////
  /// Public functions
  /////////////////////////////////////////////////////////

  self.changeTo = function(config) {
    parseConfig(config);
    shapeEdges();
    updateEdges();
  };

  self.drawEdges = function (edges) {
    shapeEdges(edges);
    updateEdges();
  };

  self.updateEdges = function () {
    updateEdges();
  };

  self.reshapeEdges = function() {
    shapeEdges();
  };
}

EdgeShaper.shapes = Object.freeze({
  "NONE": 0,
  "ARROW": 1
});
<commit_msg>GraphViewer: Positions of Edges are now always triggered on reshape, they do not disappear anymore on reshape withthout update<commit_after>/*jslint indent: 2, nomen: true, maxlen: 100, white: true  plusplus: true */
/*global _, $, d3*/
/*global ColourMapper*/
////////////////////////////////////////////////////////////////////////////////
/// @brief Graph functionality
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Michael Hackstein
/// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

/*
* flags example format:
* {
*   shape: {
*     type: EdgeShaper.shapes.ARROW
*   }
*   label: "key" \\ function(node)
*   actions: {
*     click: function(edge)
*   }
* }
*
*
*/

function EdgeShaper(parent, flags, idfunc) {
  "use strict";

  var self = this,
    edges = [],
    toplevelSVG,

    idFunction = function(d) {
      return d.source._id + "-" + d.target._id;
    },
    noop = function (line, g) {

    },
    colourMapper = new ColourMapper(),
    events,
    addUpdate,
    addShape = noop,
    addLabel = noop,
    addColor = noop,

    unbindEvents = function() {
     events = {
       click: noop,
       dblclick: noop,
       mousedown: noop,
       mouseup: noop,
       mousemove: noop
     };
     addUpdate = noop;
    },

    getCorner = function(s, t) {
      return Math.atan2(t.y - s.y, t.x - s.x) * 180 / Math.PI;
    },

    getDistance = function(s, t) {
      return Math.sqrt(
        (t.y - s.y)
        * (t.y - s.y)
        + (t.x - s.x)
        * (t.x - s.x)
      );
    },

    addEvents = function (line, g) {
      _.each(events, function (func, type) {
        g.on(type, func);
      });
    },

    bindEvent = function (type, func) {
      if (type === "update") {
        addUpdate = func;
      } else if (events[type] === undefined) {
        throw "Sorry Unknown Event " + type + " cannot be bound.";
      } else {
        events[type] = func;
      }
    },

    addPosition = function (line, g) {
      g.attr("transform", function(d) {
        return "translate("
          + d.source.x + ", "
          + d.source.y + ")"
          + "rotate("
          + getCorner(d.source, d.target)
          + ")";
      });
      line.attr("x2", function(d) {
        return getDistance(d.source, d.target);
      });
    },

    addQue = function (line, g) {
      addShape(line, g);
      addLabel(line, g);
      addColor(line, g);
      addEvents(line, g);
      addPosition(line, g);
    },

    shapeEdges = function (newEdges) {
      if (newEdges !== undefined) {
        edges = newEdges;
      }
      var line,
        g = self.parent
          .selectAll(".link")
          .data(edges, idFunction);
      // Append the group and class to all new
      g.enter()
        .append("g")
        .attr("class", "link") // link is CSS class that might be edited
        .attr("id", idFunction);
      // Remove all old
      g.exit().remove();
      // Remove all elements that are still included.
      g.selectAll("* > *").remove();
      line = g.append("line");
      addQue(line, g);
    },

    updateEdges = function () {
      var g = self.parent.selectAll(".link"),
        line = g.select("line");
      addPosition(line, g);
      addUpdate(g);
    },

    parseShapeFlag = function (shape) {
      $("svg defs marker#arrow").remove();
      switch (shape.type) {
        case EdgeShaper.shapes.NONE:
          addShape = noop;
          break;
        case EdgeShaper.shapes.ARROW:
          addShape = function (line, g) {
            line.attr("marker-end", "url(#arrow)");
          };
          if (toplevelSVG.selectAll("defs")[0].length === 0) {
            toplevelSVG.append("defs");
          }
          toplevelSVG
            .select("defs")
            .append("marker")
            .attr("id", "arrow")
            .attr("refX", "22")
            .attr("refY", "5")
            .attr("markerUnits", "strokeWidth")
            .attr("markerHeight", "10")
            .attr("markerWidth", "10")
            .attr("orient", "auto")
            .append("path")
              .attr("d", "M 0 0 L 10 5 L 0 10 z");
          break;
        default:
          throw "Sorry given Shape not known!";
      }
    },

    parseLabelFlag = function (label) {
      if (_.isFunction(label)) {
        addLabel = function (line, g) {
          g.append("text") // Append a label for the edge
            .attr("text-anchor", "middle") // Define text-anchor
            .text(label);
        };
      } else {
        addLabel = function (line, g) {
          g.append("text") // Append a label for the edge
            .attr("text-anchor", "middle") // Define text-anchor
            .text(function(d) {
              // Which value should be used as label
              return d._data[label] !== undefined ? d._data[label] : "";
            });
        };
      }
      addUpdate = function (edges) {
        edges.select("text")
          .attr("transform", function(d) {
            return "translate("
              + getDistance(d.source, d.target) / 2
              + ", -3)";
          });
      };
    },

    parseActionFlag = function (actions) {
      if (actions.reset !== undefined && actions.reset) {
        unbindEvents();
      }
      _.each(actions, function(func, type) {
        if (type !== "reset") {
          bindEvent(type, func);
        }
      });
    },

    parseColorFlag = function (color) {
      $("svg defs #gradientEdgeColor").remove();
      switch (color.type) {
        case "single":
          addColor = function (line, g) {
            line.attr("stroke", color.stroke);
          };
          break;
        case "gradient":
          if (toplevelSVG.selectAll("defs")[0].length === 0) {
            toplevelSVG.append("defs");
          }
          var gradient = toplevelSVG
            .select("defs")
            .append("linearGradient")
            .attr("id", "gradientEdgeColor");
          gradient.append("stop")
            .attr("offset", "0")
            .attr("stop-color", color.source);
          gradient.append("stop")
            .attr("offset", "0.4")
            .attr("stop-color", color.source);
          gradient.append("stop")
            .attr("offset", "0.6")
            .attr("stop-color", color.target);
          gradient.append("stop")
            .attr("offset", "1")
            .attr("stop-color", color.target);
          addColor = function (line, g) {
            line.attr("stroke", "url(#gradientEdgeColor)");
            line.attr("y2", "0.0000000000000001");
          };
          break;
        case "attribute":
          addColor = function (line, g) {
             g.attr("stroke", function(e) {
               return colourMapper.getColour(e._data[color.key]);
             });
          };
          break;
        default:
          throw "Sorry given colour-scheme not known";
      }
    },

    parseConfig = function(config) {
      if (config.shape !== undefined) {
        parseShapeFlag(config.shape);
      }
      if (config.label !== undefined) {
        parseLabelFlag(config.label);
      }
      if (config.actions !== undefined) {
        parseActionFlag(config.actions);
      }
      if (config.color !== undefined) {
        parseColorFlag(config.color);
      }
    };

  self.parent = parent;

  unbindEvents();

  toplevelSVG = parent;
  while (toplevelSVG[0][0] && toplevelSVG[0][0].ownerSVGElement) {
    toplevelSVG = d3.select(toplevelSVG[0][0].ownerSVGElement);
  }

  if (flags === undefined) {
    flags = {
      color: {
        type: "single",
        stroke: "#686766"
      }
    };
  }

  if (flags.color === undefined) {
    flags.color = {
      type: "single",
      stroke: "#686766"
    };
  }

  parseConfig(flags);

  if (_.isFunction(idfunc)) {
    idFunction = idfunc;
  }

  /////////////////////////////////////////////////////////
  /// Public functions
  /////////////////////////////////////////////////////////

  self.changeTo = function(config) {
    parseConfig(config);
    shapeEdges();
    updateEdges();
  };

  self.drawEdges = function (edges) {
    shapeEdges(edges);
    updateEdges();
  };

  self.updateEdges = function () {
    updateEdges();
  };

  self.reshapeEdges = function() {
    shapeEdges();
  };
}

EdgeShaper.shapes = Object.freeze({
  "NONE": 0,
  "ARROW": 1
});
<|endoftext|>


<commit_before>'use strict';

var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');
var ko = require('knockout');

var ProjectSettings = require('js/projectSettings.js');
var InstitutionProjectSettings = require('js/institutionProjectSettings.js');

var $osf = require('js/osfHelpers');
require('css/addonsettings.css');

var ctx = window.contextVars;

// Initialize treebeard grid for notifications
var ProjectNotifications = require('js/notificationsTreebeard.js');
var $notificationsMsg = $('#configureNotificationsMessage');
var notificationsURL = ctx.node.urls.api  + 'subscriptions/';
// Need check because notifications settings don't exist on registration's settings page
if ($('#grid').length) {
    $.ajax({
        url: notificationsURL,
        type: 'GET',
        dataType: 'json'
    }).done(function(response) {
        new ProjectNotifications(response);
    }).fail(function(xhr, status, error) {
        $notificationsMsg.addClass('text-danger');
        $notificationsMsg.text('Could not retrieve notification settings.');
        Raven.captureMessage('Could not GET notification settings.', {
            extra: { url: notificationsURL, status: status, error: error }
        });
    });
}

//Initialize treebeard grid for wiki
var ProjectWiki = require('js/wikiSettingsTreebeard.js');
var wikiSettingsURL = ctx.node.urls.api  + 'wiki/settings/';
var $wikiMsg = $('#configureWikiMessage');

if ($('#wgrid').length) {
    $.ajax({
        url: wikiSettingsURL,
        type: 'GET',
        dataType: 'json'
    }).done(function(response) {
        new ProjectWiki(response);
    }).fail(function(xhr, status, error) {
        $wikiMsg.addClass('text-danger');
        $wikiMsg.text('Could not retrieve wiki settings.');
        Raven.captureMessage('Could not GET wiki settings.', {
            extra: { url: wikiSettingsURL, status: status, error: error }
        });
    });
}

$(document).ready(function() {
    // Apply KO bindings for Project Settings
    if ($('#institutionSettings').length) {
        new InstitutionProjectSettings('#institutionSettings', window.contextVars);
    }
    var categoryOptions = [];
    var keys = Object.keys(window.contextVars.nodeCategories);
    for (var i = 0; i < keys.length; i++) {
        categoryOptions.push({
            label: window.contextVars.nodeCategories[keys[i]],
            value: keys[i]
        });
    }
    var disableCategory = !window.contextVars.node.parentExists;
    // need check because node category doesn't exist for registrations
    if ($('#projectSettings').length) {
        var projectSettingsVM = new ProjectSettings.ProjectSettings( {
            currentTitle: ctx.node.title,
            currentDescription: ctx.node.description,
            category: ctx.node.category,
            categoryOptions: categoryOptions,
            node_id: ctx.node.id,
            updateUrl:  $osf.apiV2Url('nodes/' + ctx.node.id + '/'),
            disabled: disableCategory
        });
        ko.applyBindings(projectSettingsVM, $('#projectSettings')[0]);
    }

    $('#deleteNode').on('click', function() {
        ProjectSettings.getConfirmationCode(ctx.node.nodeType);
    });

    // TODO: Knockout-ify me
    $('#commentSettings').on('submit', function() {
        var $commentMsg = $('#configureCommentingMessage');

        var $this = $(this);
        var commentLevel = $this.find('input[name="commentLevel"]:checked').val();

        $osf.postJSON(
            ctx.node.urls.api + 'settings/comments/',
            {commentLevel: commentLevel}
        ).done(function() {
            $commentMsg.text('Successfully updated settings.');
            $commentMsg.addClass('text-success');
            if($osf.isSafari()){
                //Safari can't update jquery style change before reloading. So delay is applied here
                setTimeout(function(){window.location.reload();}, 100);
            } else {
                window.location.reload();
            }

        }).fail(function() {
            bootbox.alert({
                message: 'Could not set commenting configuration. Please try again.',
                buttons:{
                    ok:{
                        label:'Close',
                        className:'btn-default'
                    }
                }
            });
        });

        return false;

    });

    var checkedOnLoad = $('#selectAddonsForm input:checked');
    var uncheckedOnLoad = $('#selectAddonsForm input:not(:checked)');

    // Set up submission for addon selection form
    $('#selectAddonsForm').on('submit', function() {

        var formData = {};
        $('#selectAddonsForm').find('input').each(function(idx, elm) {
            var $elm = $(elm);
            formData[$elm.attr('name')] = $elm.is(':checked');
        });

        var unchecked = checkedOnLoad.filter('#selectAddonsForm input:not(:checked)');
        var checked = uncheckedOnLoad.filter('#selectAddonsForm input:checked');
        var msgElm = $(this).find('.addon-settings-message');

        var submit = function() {
            var request = $osf.postJSON(ctx.node.urls.api + 'settings/addons/', formData);
            return request;
        };

        function successMessage() {
            msgElm.text('Settings updated');
            setTimeout(
                function() {
                    msgElm.text('');
                },
                5000
            );
        }
        function successfulAddonUpdate() {
            successMessage();
            checkedOnLoad = $('#selectAddonsForm input:checked');
            uncheckedOnLoad = $('#selectAddonsForm input:not(:checked)');
            if($osf.isSafari()) {
                        //Safari can't update jquery style change before reloading. So delay is applied here
                        setTimeout(function(){window.location.reload();}, 100);
            } else {
                        window.location.reload();
            }
        }
        function failedAddonUpdate() {
            var msg = 'Sorry, we had trouble saving your settings. If this persists please contact support@osf.io';
            bootbox.alert({
                title: 'Request failed',
                message: msg,
                buttons: {
                    ok: {
                        label: 'Close',
                        className: 'btn-default'
                    }
                }
            });
        }
        // some addons disabled (unchecked warning adopted from profile-settings-addons-page.js)
        if(unchecked.length > 0) {
            var uncheckedText = $.map(unchecked, function(el){
                return ['
', $(el).closest('label').text().trim(), '
'].join('');
            }).join('');
            uncheckedText = ['
', $osf.htmlEscape(uncheckedText), '
'].join('');
            bootbox.confirm({
                title: 'Are you sure you want to remove the add-ons you have deselected? ',
                message: uncheckedText,
                callback: function(result) {
                    if (result) {
                        var request = submit();
                        request.done(successfulAddonUpdate);
                        request.fail(failedAddonUpdate);
                    } else{
                        unchecked.each(function(i, el){ $(el).prop('checked', true); });
                    }
                },
                buttons: {
                    confirm: {
                        label: 'Remove',
                        className: 'btn-danger'
                    }
                }
            });
        }
        //no addons disabled but some addons enabled
        else if(checked.length>0) {
            var request = submit();
            request.done(successfulAddonUpdate);
            request.fail(failedAddonUpdate);
        }
        // no changes to the state of the addons
        else {
            successMessage();
        }

        return false;

    });

    /* Before closing the page, Check whether the newly checked addon are updated or not */
    $(window).on('beforeunload',function() {
      //new checked items but not updated
      var checked = uncheckedOnLoad.filter('#selectAddonsForm input:checked');
      //new unchecked items but not updated
      var unchecked = checkedOnLoad.filter('#selectAddonsForm input:not(:checked)');

      if(unchecked.length > 0 || checked.length > 0) {
          return 'The changes on addon setting are not submitted!';
      }

        if (projectSettingsVM) {
            /* Before closing the page, check whether changes made to category, title or
               description are updated or not */
            if (projectSettingsVM.title() !== projectSettingsVM.titlePlaceholder ||
                projectSettingsVM.description() !== projectSettingsVM.descriptionPlaceholder ||
                projectSettingsVM.selectedCategory() !== projectSettingsVM.categoryPlaceholder) {
                return 'There are unsaved changes in your project settings.';
            }
        }
    });

    // Show capabilities modal on selecting an addon; unselect if user
    // rejects terms
    $('.addon-select').on('change', function() {
        var that = this,
            $that = $(that);
        if ($that.is(':checked')) {
            var name = $that.attr('name');
            var capabilities = $('#capabilities-' + name).html();
            if (capabilities) {
                bootbox.confirm({
                    message: capabilities,
                    callback: function(result) {
                        if (!result) {
                            $(that).attr('checked', false);
                        } else {
                            $('#selectAddonsForm').submit();
                        }
                    },
                    buttons:{
                        confirm:{
                            label:'Confirm'
                        }
                    }
               });
            } else {
                $('#selectAddonsForm').submit();
            }
        } else {
            $('#selectAddonsForm').submit();
        }
    });
});

var WikiSettingsViewModel = {
    enabled: ko.observable(ctx.wiki.isEnabled), // <- this would get set in the mako template, as usual
    wikiMessage: ko.observable('')
};

WikiSettingsViewModel.enabled.subscribe(function(newValue) {
    var self = this;
    $osf.postJSON(ctx.node.urls.api + 'settings/addons/', {wiki: newValue}
    ).done(function(response) {
        if (newValue) {
            self.wikiMessage('Wiki Enabled');
        }
        else {
            self.wikiMessage('Wiki Disabled');
        }
        //Give user time to see message before reload.
        setTimeout(function(){window.location.reload();}, 1500);
    }).fail(function(xhr, status, error) {
        $osf.growl('Error', 'Unable to update wiki');
        Raven.captureMessage('Could not update wiki.', {
            extra: {
                url: ctx.node.urls.api + 'settings/addons/', status: status, error: error
            }
        });
        setTimeout(function(){window.location.reload();}, 1500);
    });
    return true;
}, WikiSettingsViewModel);

if ($('#selectWikiForm').length) {
    $osf.applyBindings(WikiSettingsViewModel, '#selectWikiForm');
}
<commit_msg>Don't double-escape static labels<commit_after>'use strict';

var $ = require('jquery');
var bootbox = require('bootbox');
var Raven = require('raven-js');
var ko = require('knockout');

var ProjectSettings = require('js/projectSettings.js');
var InstitutionProjectSettings = require('js/institutionProjectSettings.js');

var $osf = require('js/osfHelpers');
require('css/addonsettings.css');

var ctx = window.contextVars;

// Initialize treebeard grid for notifications
var ProjectNotifications = require('js/notificationsTreebeard.js');
var $notificationsMsg = $('#configureNotificationsMessage');
var notificationsURL = ctx.node.urls.api  + 'subscriptions/';
// Need check because notifications settings don't exist on registration's settings page
if ($('#grid').length) {
    $.ajax({
        url: notificationsURL,
        type: 'GET',
        dataType: 'json'
    }).done(function(response) {
        new ProjectNotifications(response);
    }).fail(function(xhr, status, error) {
        $notificationsMsg.addClass('text-danger');
        $notificationsMsg.text('Could not retrieve notification settings.');
        Raven.captureMessage('Could not GET notification settings.', {
            extra: { url: notificationsURL, status: status, error: error }
        });
    });
}

//Initialize treebeard grid for wiki
var ProjectWiki = require('js/wikiSettingsTreebeard.js');
var wikiSettingsURL = ctx.node.urls.api  + 'wiki/settings/';
var $wikiMsg = $('#configureWikiMessage');

if ($('#wgrid').length) {
    $.ajax({
        url: wikiSettingsURL,
        type: 'GET',
        dataType: 'json'
    }).done(function(response) {
        new ProjectWiki(response);
    }).fail(function(xhr, status, error) {
        $wikiMsg.addClass('text-danger');
        $wikiMsg.text('Could not retrieve wiki settings.');
        Raven.captureMessage('Could not GET wiki settings.', {
            extra: { url: wikiSettingsURL, status: status, error: error }
        });
    });
}

$(document).ready(function() {
    // Apply KO bindings for Project Settings
    if ($('#institutionSettings').length) {
        new InstitutionProjectSettings('#institutionSettings', window.contextVars);
    }
    var categoryOptions = [];
    var keys = Object.keys(window.contextVars.nodeCategories);
    for (var i = 0; i < keys.length; i++) {
        categoryOptions.push({
            label: window.contextVars.nodeCategories[keys[i]],
            value: keys[i]
        });
    }
    var disableCategory = !window.contextVars.node.parentExists;
    // need check because node category doesn't exist for registrations
    if ($('#projectSettings').length) {
        var projectSettingsVM = new ProjectSettings.ProjectSettings( {
            currentTitle: ctx.node.title,
            currentDescription: ctx.node.description,
            category: ctx.node.category,
            categoryOptions: categoryOptions,
            node_id: ctx.node.id,
            updateUrl:  $osf.apiV2Url('nodes/' + ctx.node.id + '/'),
            disabled: disableCategory
        });
        ko.applyBindings(projectSettingsVM, $('#projectSettings')[0]);
    }

    $('#deleteNode').on('click', function() {
        ProjectSettings.getConfirmationCode(ctx.node.nodeType);
    });

    // TODO: Knockout-ify me
    $('#commentSettings').on('submit', function() {
        var $commentMsg = $('#configureCommentingMessage');

        var $this = $(this);
        var commentLevel = $this.find('input[name="commentLevel"]:checked').val();

        $osf.postJSON(
            ctx.node.urls.api + 'settings/comments/',
            {commentLevel: commentLevel}
        ).done(function() {
            $commentMsg.text('Successfully updated settings.');
            $commentMsg.addClass('text-success');
            if($osf.isSafari()){
                //Safari can't update jquery style change before reloading. So delay is applied here
                setTimeout(function(){window.location.reload();}, 100);
            } else {
                window.location.reload();
            }

        }).fail(function() {
            bootbox.alert({
                message: 'Could not set commenting configuration. Please try again.',
                buttons:{
                    ok:{
                        label:'Close',
                        className:'btn-default'
                    }
                }
            });
        });

        return false;

    });

    var checkedOnLoad = $('#selectAddonsForm input:checked');
    var uncheckedOnLoad = $('#selectAddonsForm input:not(:checked)');

    // Set up submission for addon selection form
    $('#selectAddonsForm').on('submit', function() {

        var formData = {};
        $('#selectAddonsForm').find('input').each(function(idx, elm) {
            var $elm = $(elm);
            formData[$elm.attr('name')] = $elm.is(':checked');
        });

        var unchecked = checkedOnLoad.filter('#selectAddonsForm input:not(:checked)');
        var checked = uncheckedOnLoad.filter('#selectAddonsForm input:checked');
        var msgElm = $(this).find('.addon-settings-message');

        var submit = function() {
            var request = $osf.postJSON(ctx.node.urls.api + 'settings/addons/', formData);
            return request;
        };

        function successMessage() {
            msgElm.text('Settings updated');
            setTimeout(
                function() {
                    msgElm.text('');
                },
                5000
            );
        }
        function successfulAddonUpdate() {
            successMessage();
            checkedOnLoad = $('#selectAddonsForm input:checked');
            uncheckedOnLoad = $('#selectAddonsForm input:not(:checked)');
            if($osf.isSafari()) {
                        //Safari can't update jquery style change before reloading. So delay is applied here
                        setTimeout(function(){window.location.reload();}, 100);
            } else {
                        window.location.reload();
            }
        }
        function failedAddonUpdate() {
            var msg = 'Sorry, we had trouble saving your settings. If this persists please contact support@osf.io';
            bootbox.alert({
                title: 'Request failed',
                message: msg,
                buttons: {
                    ok: {
                        label: 'Close',
                        className: 'btn-default'
                    }
                }
            });
        }
        // some addons disabled (unchecked warning adopted from profile-settings-addons-page.js)
        if(unchecked.length > 0) {
            var uncheckedText = $.map(unchecked, function(el){
                return ['
', $(el).closest('label').text().trim(), '
'].join('');
            }).join('');
            uncheckedText = ['
', uncheckedText, '
'].join('');
            bootbox.confirm({
                title: 'Are you sure you want to remove the add-ons you have deselected? ',
                message: uncheckedText,
                callback: function(result) {
                    if (result) {
                        var request = submit();
                        request.done(successfulAddonUpdate);
                        request.fail(failedAddonUpdate);
                    } else{
                        unchecked.each(function(i, el){ $(el).prop('checked', true); });
                    }
                },
                buttons: {
                    confirm: {
                        label: 'Remove',
                        className: 'btn-danger'
                    }
                }
            });
        }
        //no addons disabled but some addons enabled
        else if(checked.length>0) {
            var request = submit();
            request.done(successfulAddonUpdate);
            request.fail(failedAddonUpdate);
        }
        // no changes to the state of the addons
        else {
            successMessage();
        }

        return false;

    });

    /* Before closing the page, Check whether the newly checked addon are updated or not */
    $(window).on('beforeunload',function() {
      //new checked items but not updated
      var checked = uncheckedOnLoad.filter('#selectAddonsForm input:checked');
      //new unchecked items but not updated
      var unchecked = checkedOnLoad.filter('#selectAddonsForm input:not(:checked)');

      if(unchecked.length > 0 || checked.length > 0) {
          return 'The changes on addon setting are not submitted!';
      }

        if (projectSettingsVM) {
            /* Before closing the page, check whether changes made to category, title or
               description are updated or not */
            if (projectSettingsVM.title() !== projectSettingsVM.titlePlaceholder ||
                projectSettingsVM.description() !== projectSettingsVM.descriptionPlaceholder ||
                projectSettingsVM.selectedCategory() !== projectSettingsVM.categoryPlaceholder) {
                return 'There are unsaved changes in your project settings.';
            }
        }
    });

    // Show capabilities modal on selecting an addon; unselect if user
    // rejects terms
    $('.addon-select').on('change', function() {
        var that = this,
            $that = $(that);
        if ($that.is(':checked')) {
            var name = $that.attr('name');
            var capabilities = $('#capabilities-' + name).html();
            if (capabilities) {
                bootbox.confirm({
                    message: capabilities,
                    callback: function(result) {
                        if (!result) {
                            $(that).attr('checked', false);
                        } else {
                            $('#selectAddonsForm').submit();
                        }
                    },
                    buttons:{
                        confirm:{
                            label:'Confirm'
                        }
                    }
               });
            } else {
                $('#selectAddonsForm').submit();
            }
        } else {
            $('#selectAddonsForm').submit();
        }
    });
});

var WikiSettingsViewModel = {
    enabled: ko.observable(ctx.wiki.isEnabled), // <- this would get set in the mako template, as usual
    wikiMessage: ko.observable('')
};

WikiSettingsViewModel.enabled.subscribe(function(newValue) {
    var self = this;
    $osf.postJSON(ctx.node.urls.api + 'settings/addons/', {wiki: newValue}
    ).done(function(response) {
        if (newValue) {
            self.wikiMessage('Wiki Enabled');
        }
        else {
            self.wikiMessage('Wiki Disabled');
        }
        //Give user time to see message before reload.
        setTimeout(function(){window.location.reload();}, 1500);
    }).fail(function(xhr, status, error) {
        $osf.growl('Error', 'Unable to update wiki');
        Raven.captureMessage('Could not update wiki.', {
            extra: {
                url: ctx.node.urls.api + 'settings/addons/', status: status, error: error
            }
        });
        setTimeout(function(){window.location.reload();}, 1500);
    });
    return true;
}, WikiSettingsViewModel);

if ($('#selectWikiForm').length) {
    $osf.applyBindings(WikiSettingsViewModel, '#selectWikiForm');
}
<|endoftext|>