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

CLASS({
  package: 'foam.u2',
  name: 'DAOListView',
  extends: 'foam.u2.View',

  requires: [
    'foam.u2.DetailView',
    'foam.u2.md.CitationView',
  ],
  imports: [
    'selection$',
  ],

  constants: {
    ROW_CLICK: ['row-click'],
  },

  properties: [
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'data',
      postSet: function(old, nu) {
        this.dao = nu;
      },
    },
    {
      // Separate from data so it can be a DAOProperty.
      model_: 'foam.core.types.DAOProperty',
      name: 'dao',
    },
    {
      name: 'daoListener_',
      lazyFactory: function() {
        return {
          put: this.onDAOPut,
          remove: this.onDAORemove,
          reset: this.onDAOReset
        };
      }
    },
    {
      type: 'ViewFactory',
      name: 'rowView',
    },
    {
      name: 'rows',
      factory: function() {
        return {};
      }
    },
    ['nodeName', 'div']
  ],
  templates: [
    function CSS() {/*
      .foam-u2-DAOListView {
      }
    */}
  ],
  methods: [
    function initE() {
      this.dao$Proxy.pipe(this.daoListener_);
      this.cls('foam-u2-DAOListView');
    },
  ],
  listeners: [
    {
      name: 'onDAOPut',
      code: function(obj) {
        if ( this.rows[obj.id] ) {
          this.rows[obj.id].data = obj;
          return;
        }

        var Y = this.Y.sub({ data: obj });

        var child = this.rowView ?
            this.rowView({ data: obj }, Y) :
            obj.toRowE ? obj.toRowE(Y) :
            obj.toE ? obj.toE(Y) :
            this.DetailView.create({ data: obj }, Y);

        child.on('click', function() {
          this.publish(this.ROW_CLICK);
          this.selection = obj;
        }.bind(this));

        this.rows[obj.id] = child;
        this.add(child);
      }
    },
    {
      name: 'onDAORemove',
      code: function(obj) {
        if ( this.rows[obj.id] ) {
          this.removeChild(this.rows[obj.id]);
          delete this.rows[obj.id];
        }
      }
    },
    {
      name: 'onDAOReset',
      code: function(obj) {
        this.removeAllChildren();
        this.rows = {};
        this.dao.select(this.daoListener_);
      }
    }
  ]
});
<commit_msg>foam.u2.DAOListView: Include the object in ROW_CLICK events.  Default to CitationView instead of DetailView.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.u2',
  name: 'DAOListView',
  extends: 'foam.u2.View',

  requires: [
    'foam.u2.DetailView',
    'foam.u2.md.CitationView',
  ],
  imports: [
    'selection$',
  ],

  constants: {
    ROW_CLICK: ['row-click'],
  },

  properties: [
    {
      model_: 'foam.core.types.DAOProperty',
      name: 'data',
      postSet: function(old, nu) {
        this.dao = nu;
      },
    },
    {
      // Separate from data so it can be a DAOProperty.
      model_: 'foam.core.types.DAOProperty',
      name: 'dao',
    },
    {
      name: 'daoListener_',
      lazyFactory: function() {
        return {
          put: this.onDAOPut,
          remove: this.onDAORemove,
          reset: this.onDAOReset
        };
      }
    },
    {
      type: 'ViewFactory',
      name: 'rowView',
    },
    {
      name: 'rows',
      factory: function() {
        return {};
      }
    },
    ['nodeName', 'div']
  ],
  templates: [
    function CSS() {/*
      .foam-u2-DAOListView {
      }
    */}
  ],
  methods: [
    function initE() {
      this.dao$Proxy.pipe(this.daoListener_);
      this.cls('foam-u2-DAOListView');
    },
  ],
  listeners: [
    {
      name: 'onDAOPut',
      code: function(obj) {
        if ( this.rows[obj.id] ) {
          this.rows[obj.id].data = obj;
          return;
        }

        var Y = this.Y.sub({ data: obj });

        var child = this.rowView ?
            this.rowView({ data: obj }, Y) :
            obj.toRowE ? obj.toRowE(Y) :
            obj.toE ? obj.toE(Y) :
            this.CitationView.create({ data: obj }, Y);

        child.on('click', function() {
          this.publish(this.ROW_CLICK, obj);
          this.selection = obj;
        }.bind(this));

        this.rows[obj.id] = child;
        this.add(child);
      }
    },
    {
      name: 'onDAORemove',
      code: function(obj) {
        if ( this.rows[obj.id] ) {
          this.removeChild(this.rows[obj.id]);
          delete this.rows[obj.id];
        }
      }
    },
    {
      name: 'onDAOReset',
      code: function(obj) {
        this.removeAllChildren();
        this.rows = {};
        this.dao.select(this.daoListener_);
      }
    }
  ]
});
<|endoftext|>


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

CLASS({
  package: 'foam.u2.md',
  name: 'TextArea',
  extends: 'foam.u2.md.Input',

  properties: [
    {
      name: 'displayHeight',
      type: 'Int',
      defaultValue: 10
    }
  ],

  requires: [
    'foam.u2.TextArea'
  ],

  methods: [
    function fromProperty(p) {
      this.displayHeight = p.displayHeight;
      return this.SUPER(p);
    },

    function inputE() {
      var self = this;
      return this.start('textarea')
        .cls(this.myCls('textarea'))
        .attrs({ rows: this.displayHeight })
        .on('focus', function() { self.focused_ = true; })
        .on('blur', function() { self.focused_ = false; });
    }
  ],
  templates: [
    function CSS() {/*
      $ {
        align-items: center;
        display: flex;
        margin: 8px;
        padding: 32px 8px 8px 8px;
        position: relative;
      }
      $-label {
        color: #999;
        flex-grow: 1;
        font-size: 14px;
        font-weight: 500;
        position: absolute;
        top: 32px;
        transition: font-size 0.5s, top 0.5s;
        z-index: 0;
      }
      $-label-offset {
        font-size: 85%;
        top: 8px;
      }
      $-no-label {
        padding-top: 8px;
      }
      $-textarea {
        background: transparent;
        border-bottom: 1px solid #e0e0e0;
        border-left: none;
        border-top: none;
        border-right: none;
        color: #444;
        flex-grow: 1;
        font-family: inherit;
        font-size: inherit;
        margin-bottom: -8px;
        padding: 0 0 7px 0;
        resize: none;
        z-index: 1;
      }
      $-textarea:focus {
        border-bottom: 2px solid #4285f4;
        padding: 0 0 6px 0;
        outline: none;
      }
    */}
  ]
});
<commit_msg>Fix foam.u2.md.TextArea after focused was added to Element.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  package: 'foam.u2.md',
  name: 'TextArea',
  extends: 'foam.u2.md.Input',

  properties: [
    {
      name: 'displayHeight',
      type: 'Int',
      defaultValue: 10
    }
  ],

  requires: [
    'foam.u2.TextArea'
  ],

  methods: [
    function fromProperty(p) {
      this.displayHeight = p.displayHeight;
      return this.SUPER(p);
    },

    function inputE() {
      var self = this;
      return this.start('textarea')
        .cls(this.myCls('textarea'))
        .attrs({ rows: this.displayHeight })
        .on('focus', function() { self.focused = true; })
        .on('blur', function() { self.focused = false; });
    }
  ],
  templates: [
    function CSS() {/*
      $ {
        align-items: center;
        display: flex;
        margin: 8px;
        padding: 32px 8px 8px 8px;
        position: relative;
      }
      $-label {
        color: #999;
        flex-grow: 1;
        font-size: 14px;
        font-weight: 500;
        position: absolute;
        top: 32px;
        transition: font-size 0.5s, top 0.5s;
        z-index: 0;
      }
      $-label-offset {
        font-size: 85%;
        top: 8px;
      }
      $-no-label {
        padding-top: 8px;
      }
      $-textarea {
        background: transparent;
        border-bottom: 1px solid #e0e0e0;
        border-left: none;
        border-top: none;
        border-right: none;
        color: #444;
        flex-grow: 1;
        font-family: inherit;
        font-size: inherit;
        margin-bottom: -8px;
        padding: 0 0 7px 0;
        resize: none;
        z-index: 1;
      }
      $-textarea:focus {
        border-bottom: 2px solid #4285f4;
        padding: 0 0 6px 0;
        outline: none;
      }
    */}
  ]
});
<|endoftext|>


<commit_before>'use strict';

import $ from 'jquery';
import { GetYoDigits } from './foundation.util.core';
import { Plugin } from './foundation.plugin';
import { SmoothScroll } from './foundation.smoothScroll';

/**
 * Magellan module.
 * @module foundation.magellan
 * @requires foundation.smoothScroll
 */

class Magellan extends Plugin {
  /**
   * Creates a new instance of Magellan.
   * @class
   * @name Magellan
   * @fires Magellan#init
   * @param {Object} element - jQuery object to add the trigger to.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options  = $.extend({}, Magellan.defaults, this.$element.data(), options);
    this.className = 'Magellan'; // ie9 back compat

    this._init();
    this.calcPoints();
  }

  /**
   * Initializes the Magellan plugin and calls functions to get equalizer functioning on load.
   * @private
   */
  _init() {
    var id = this.$element[0].id || GetYoDigits(6, 'magellan');
    var _this = this;
    this.$targets = $('[data-magellan-target]');
    this.$links = this.$element.find('a');
    this.$element.attr({
      'data-resize': id,
      'data-scroll': id,
      'id': id
    });
    this.$active = $();
    this.scrollPos = parseInt(window.pageYOffset, 10);

    this._events();
  }

  /**
   * Calculates an array of pixel values that are the demarcation lines between locations on the page.
   * Can be invoked if new elements are added or the size of a location changes.
   * @function
   */
  calcPoints() {
    var _this = this,
        body = document.body,
        html = document.documentElement;

    this.points = [];
    this.winHeight = Math.round(Math.max(window.innerHeight, html.clientHeight));
    this.docHeight = Math.round(Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight));

    this.$targets.each(function(){
      var $tar = $(this),
          pt = Math.round($tar.offset().top - _this.options.threshold);
      $tar.targetPoint = pt;
      _this.points.push(pt);
    });
  }

  /**
   * Initializes events for Magellan.
   * @private
   */
  _events() {
    var _this = this,
        $body = $('html, body'),
        opts = {
          duration: _this.options.animationDuration,
          easing:   _this.options.animationEasing
        };
    $(window).one('load', function(){
      if(_this.options.deepLinking){
        if(location.hash){
          _this.scrollToLoc(location.hash);
        }
      }
      _this.calcPoints();
      _this._updateActive();
    });

    this.$element.on({
      'resizeme.zf.trigger': this.reflow.bind(this),
      'scrollme.zf.trigger': this._updateActive.bind(this)
    }).on('click.zf.magellan', 'a[href^="#"]', function(e) {
        e.preventDefault();
        var arrival   = this.getAttribute('href');
        _this.scrollToLoc(arrival);
      });

    this._deepLinkScroll = function(e) {
      if(_this.options.deepLinking) {
        _this.scrollToLoc(window.location.hash);
      }
    };

    $(window).on('popstate', this._deepLinkScroll);
  }

  /**
   * Function to scroll to a given location on the page.
   * @param {String} loc - a properly formatted jQuery id selector. Example: '#foo'
   * @function
   */
  scrollToLoc(loc) {
    this._inTransition = true;
    var _this = this;

    var options = {
      animationEasing: this.options.animationEasing,
      animationDuration: this.options.animationDuration,
      threshold: this.options.threshold,
      offset: this.options.offset
    };

    SmoothScroll.scrollToLoc(loc, options, function() {
      _this._inTransition = false;
      _this._updateActive();
    })
  }

  /**
   * Calls necessary functions to update Magellan upon DOM change
   * @function
   */
  reflow() {
    this.calcPoints();
    this._updateActive();
  }

  /**
   * Updates the visibility of an active location link, and updates the url hash for the page, if deepLinking enabled.
   * @private
   * @function
   * @fires Magellan#update
   */
  _updateActive(/*evt, elem, scrollPos*/) {
    if(this._inTransition) {return;}
    var winPos = /*scrollPos ||*/ parseInt(window.pageYOffset, 10),
        curIdx;

    if(winPos + this.winHeight === this.docHeight){ curIdx = this.points.length - 1; }
    else if(winPos < this.points[0]){ curIdx = undefined; }
    else{
      var isDown = this.scrollPos < winPos,
          _this = this,
          curVisible = this.points.filter(function(p, i){
            return isDown ? p - _this.options.offset <= winPos : p - _this.options.offset - _this.options.threshold <= winPos;
          });
      curIdx = curVisible.length ? curVisible.length - 1 : 0;
    }

    this.$active.removeClass(this.options.activeClass);
    if(curIdx !== undefined){
      this.$active = this.$links.filter('[href="#' + this.$targets.eq(curIdx).data('magellan-target') + '"]').addClass(this.options.activeClass);
    }else{
      this.$active = $();
    }

    if(this.options.deepLinking){
      var hash = "";
      if(curIdx !== undefined){
        hash = this.$active[0].getAttribute('href');
      }
      if(hash !== window.location.hash) {
        if(window.history.pushState){
          // If there is no active idx, move to the same url without hash
          // https://stackoverflow.com/a/5298684/4317384
          var url = curIdx !== undefined ? hash : window.location.pathname + window.location.search;
          window.history.pushState(null, null, url);
        }else{
          window.location.hash = hash;
        }
      }
    }

    this.scrollPos = winPos;
    /**
     * Fires when magellan is finished updating to the new active element.
     * @event Magellan#update
     */
    this.$element.trigger('update.zf.magellan', [this.$active]);
  }

  /**
   * Destroys an instance of Magellan and resets the url of the window.
   * @function
   */
  _destroy() {
    this.$element.off('.zf.trigger .zf.magellan')
        .find(`.${this.options.activeClass}`).removeClass(this.options.activeClass);

    if(this.options.deepLinking){
      var hash = this.$active[0].getAttribute('href');
      window.location.hash.replace(hash, '');
    }
    $(window).off('popstate', this._deepLinkScroll);
  }
}

/**
 * Default settings for plugin
 */
Magellan.defaults = {
  /**
   * Amount of time, in ms, the animated scrolling should take between locations.
   * @option
   * @type {number}
   * @default 500
   */
  animationDuration: 500,
  /**
   * Animation style to use when scrolling between locations. Can be `'swing'` or `'linear'`.
   * @option
   * @type {string}
   * @default 'linear'
   * @see {@link https://api.jquery.com/animate|Jquery animate}
   */
  animationEasing: 'linear',
  /**
   * Number of pixels to use as a marker for location changes.
   * @option
   * @type {number}
   * @default 50
   */
  threshold: 50,
  /**
   * Class applied to the active locations link on the magellan container.
   * @option
   * @type {string}
   * @default 'is-active'
   */
  activeClass: 'is-active',
  /**
   * Allows the script to manipulate the url of the current page, and if supported, alter the history.
   * @option
   * @type {boolean}
   * @default false
   */
  deepLinking: false,
  /**
   * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar.
   * @option
   * @type {number}
   * @default 0
   */
  offset: 0
}

export {Magellan};
<commit_msg>Use pull request #10906 from zurb/coreysyms-patch-magellan-constant-updates for v6.5.0<commit_after>'use strict';

import $ from 'jquery';
import { GetYoDigits } from './foundation.util.core';
import { Plugin } from './foundation.plugin';
import { SmoothScroll } from './foundation.smoothScroll';

/**
 * Magellan module.
 * @module foundation.magellan
 * @requires foundation.smoothScroll
 */

class Magellan extends Plugin {
  /**
   * Creates a new instance of Magellan.
   * @class
   * @name Magellan
   * @fires Magellan#init
   * @param {Object} element - jQuery object to add the trigger to.
   * @param {Object} options - Overrides to the default plugin settings.
   */
  _setup(element, options) {
    this.$element = element;
    this.options  = $.extend({}, Magellan.defaults, this.$element.data(), options);
    this.className = 'Magellan'; // ie9 back compat

    this._init();
    this.calcPoints();
  }

  /**
   * Initializes the Magellan plugin and calls functions to get equalizer functioning on load.
   * @private
   */
  _init() {
    var id = this.$element[0].id || GetYoDigits(6, 'magellan');
    var _this = this;
    this.$targets = $('[data-magellan-target]');
    this.$links = this.$element.find('a');
    this.$element.attr({
      'data-resize': id,
      'data-scroll': id,
      'id': id
    });
    this.$active = $();
    this.scrollPos = parseInt(window.pageYOffset, 10);

    this._events();
  }

  /**
   * Calculates an array of pixel values that are the demarcation lines between locations on the page.
   * Can be invoked if new elements are added or the size of a location changes.
   * @function
   */
  calcPoints() {
    var _this = this,
        body = document.body,
        html = document.documentElement;

    this.points = [];
    this.winHeight = Math.round(Math.max(window.innerHeight, html.clientHeight));
    this.docHeight = Math.round(Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight));

    this.$targets.each(function(){
      var $tar = $(this),
          pt = Math.round($tar.offset().top - _this.options.threshold);
      $tar.targetPoint = pt;
      _this.points.push(pt);
    });
  }

  /**
   * Initializes events for Magellan.
   * @private
   */
  _events() {
    var _this = this,
        $body = $('html, body'),
        opts = {
          duration: _this.options.animationDuration,
          easing:   _this.options.animationEasing
        };
    $(window).one('load', function(){
      if(_this.options.deepLinking){
        if(location.hash){
          _this.scrollToLoc(location.hash);
        }
      }
      _this.calcPoints();
      _this._updateActive();
    });

    this.$element.on({
      'resizeme.zf.trigger': this.reflow.bind(this),
      'scrollme.zf.trigger': this._updateActive.bind(this)
    }).on('click.zf.magellan', 'a[href^="#"]', function(e) {
        e.preventDefault();
        var arrival   = this.getAttribute('href');
        _this.scrollToLoc(arrival);
      });

    this._deepLinkScroll = function(e) {
      if(_this.options.deepLinking) {
        _this.scrollToLoc(window.location.hash);
      }
    };

    $(window).on('popstate', this._deepLinkScroll);
  }

  /**
   * Function to scroll to a given location on the page.
   * @param {String} loc - a properly formatted jQuery id selector. Example: '#foo'
   * @function
   */
  scrollToLoc(loc) {
    this._inTransition = true;
    var _this = this;

    var options = {
      animationEasing: this.options.animationEasing,
      animationDuration: this.options.animationDuration,
      threshold: this.options.threshold,
      offset: this.options.offset
    };

    SmoothScroll.scrollToLoc(loc, options, function() {
      _this._inTransition = false;
      _this._updateActive();
    })
  }

  /**
   * Calls necessary functions to update Magellan upon DOM change
   * @function
   */
  reflow() {
    this.calcPoints();
    this._updateActive();
  }

  /**
   * Updates the visibility of an active location link, and updates the url hash for the page, if deepLinking enabled.
   * @private
   * @function
   * @fires Magellan#update
   */
  _updateActive(/*evt, elem, scrollPos*/) {
    if(this._inTransition) return;

    const newScrollPos = parseInt(window.pageYOffset, 10);
    const isScrollingUp = this.scrollPos > newScrollPos;
    this.scrollPos = newScrollPos;

    let activeIdx;
    // Before the first point: no link
    if(newScrollPos < this.points[0]){ /* do nothing */ }
    // At the bottom of the page: last link
    else if(newScrollPos + this.winHeight === this.docHeight){ activeIdx = this.points.length - 1; }
    // Otherwhise, use the last visible link
    else{
      const visibleLinks = this.points.filter((p, i) => {
        return (p - this.options.offset - (isScrollingUp ? this.options.threshold : 0)) <= newScrollPos;
      });
      activeIdx = visibleLinks.length ? visibleLinks.length - 1 : 0;
    }

    // Get the new active link
    const $oldActive = this.$active;
    let activeHash = '';
    if(activeIdx !== undefined){
      this.$active = this.$links.filter('[href="#' + this.$targets.eq(activeIdx).data('magellan-target') + '"]');
      activeHash = this.$active[0].getAttribute('href');
    }else{
      this.$active = $();
    }
    const isNewActive = !(!this.$active.length && !$oldActive.length) && !this.$active.is($oldActive);
    const isNewHash = activeHash !== window.location.hash;

    // Update the active link element
    if(isNewActive) {
      $oldActive.removeClass(this.options.activeClass);
      this.$active.addClass(this.options.activeClass);
    }

    // Update the hash (it may have changed with the same active link)
    if(this.options.deepLinking && isNewHash){
      if(window.history.pushState){
        // Set or remove the hash (see: https://stackoverflow.com/a/5298684/4317384
        const url = activeHash ? activeHash : window.location.pathname + window.location.search;
        window.history.pushState(null, null, url);
      }else{
        window.location.hash = activeHash;
      }
    }

    if (isNewActive) {
      /**
       * Fires when magellan is finished updating to the new active element.
       * @event Magellan#update
       */
    	this.$element.trigger('update.zf.magellan', [this.$active]);
	  }
  }

  /**
   * Destroys an instance of Magellan and resets the url of the window.
   * @function
   */
  _destroy() {
    this.$element.off('.zf.trigger .zf.magellan')
        .find(`.${this.options.activeClass}`).removeClass(this.options.activeClass);

    if(this.options.deepLinking){
      var hash = this.$active[0].getAttribute('href');
      window.location.hash.replace(hash, '');
    }
    $(window).off('popstate', this._deepLinkScroll);
  }
}

/**
 * Default settings for plugin
 */
Magellan.defaults = {
  /**
   * Amount of time, in ms, the animated scrolling should take between locations.
   * @option
   * @type {number}
   * @default 500
   */
  animationDuration: 500,
  /**
   * Animation style to use when scrolling between locations. Can be `'swing'` or `'linear'`.
   * @option
   * @type {string}
   * @default 'linear'
   * @see {@link https://api.jquery.com/animate|Jquery animate}
   */
  animationEasing: 'linear',
  /**
   * Number of pixels to use as a marker for location changes.
   * @option
   * @type {number}
   * @default 50
   */
  threshold: 50,
  /**
   * Class applied to the active locations link on the magellan container.
   * @option
   * @type {string}
   * @default 'is-active'
   */
  activeClass: 'is-active',
  /**
   * Allows the script to manipulate the url of the current page, and if supported, alter the history.
   * @option
   * @type {boolean}
   * @default false
   */
  deepLinking: false,
  /**
   * Number of pixels to offset the scroll of the page on item click if using a sticky nav bar.
   * @option
   * @type {number}
   * @default 0
   */
  offset: 0
}

export {Magellan};
<|endoftext|>


<commit_before>
// notes:
// implement starting a file that was started before
// implement checking for an existing file in the container
// implement logging
// implement configuration file

//var q = require("q");
//var promise = require("bluebird");
var express = require("express");
var bodyParser = require("body-parser");
var wasb = require("azure-storage");
var fs = require("fs");
var base64 = require("base64-stream");
var stream = require("stream");
var profiler = require("v8-profiler");

var app = express();
app.use(express.static("client"));

var _service;

// the current upload queue
var pending = {
    list: [],

    ensureContainer: function(service, container, onSuccess, onFailure) {
        try {
            service.createContainerIfNotExists(container, function(error, result, response) {
                if (error) {
                    if (onFailure) onFailure("container?");
                } else {
                    if (onSuccess) onSuccess();
                }
            });
        } catch (ex) {
            console.log("ensureContainer: " + ex);
            if (onFailure) onFailure("container?");
        }
    },

    canWriteFile: function(service, container, name, overwrite, onSuccess, onFailure) {
        try {
            service.doesBlobExist(container, name, function(error, result, response) {
                if (error) {
                    if (onFailure) onFailure("exists?");
                } else {
                    if (!result.exists || overwrite) {
                        if (onSuccess) onSuccess();
                    } else {
                        if (onFailure) onFailure("exists");
                    }
                }
            });
        } catch (ex) {
            console.log("canWriteFile: " + ex);
            if (onFailure) onFailure("exists?");
        }
    },

    begin: function(service, container, name, onSuccess, onFailure) {
        try {

            // create a writable block blob
            var writer = service.createWriteStreamToBlockBlob(container, name, function(error, result, response) {
                if (!error) {
                    console.log("transferred");
                } else {
                    console.log("failed - " + error);
                }
            });
            writer.on("close", function() {
                console.log("close");
            });
            writer.on("finish", function() {
                console.log("finished");
            });

            // create a reference for the file
            var file = {
                container: container,
                name: name,
                sequence: 0,
                writer: writer
            };

            pending.list.push(file);
            if (onSuccess) onSuccess(file);

        } catch (ex) {
            console.log("begin: " + ex);
            if (onFailure) onFailure("write?");
        }
    },

    add: function(container, name, overwrite, onSuccess, onFailure) {
        var self = this;
        if (_service == null) {
            _service = wasb.createBlobService("2e2115eastus", "1tnb/X2r4VZNMyKOHmM4bJfollRsF1jId2pVAhTitdmszP4MH7kc39pm97ijhHtteRY5EzuDnkIBBz8tP/2CSQ==");
        }
        var service = _service;
        self.ensureContainer(service, container, function() {
            self.canWriteFile(service, container, name, overwrite, function() {
                self.begin(service, container, name, onSuccess, onFailure);
            }, onFailure);
        }, onFailure);
    },

    remove: function(container, name) {
        for (var i = 0; i < this.list.length; i++) {
            if (this.list[i].container == container && this.list[i].name == name) {
                this.list.splice(i, 1);
                return;
            }
        }
    },

    find: function(container, name) {
        for (var i = 0; i < this.list.length; i++) {
            if (this.list[i].container == container && this.list[i].name == name) return this.list[i];
        }
        return null;
    }

};

// hello
app.get("/hello", function(req, res) {
   res.send("hello");
});

app.post("/hello", function(req, res) {
   res.send("hello");
});

// home page
app.get("/", function(req, res) {
    res.redirect("/index.htm");
});

// extend the response object to include the custom error messages
express.response.sendError = function(error) {
    console.log("error: " + JSON.stringify(error));
    switch(error) {
        case "exception":
            this.status(500).send({ code: 000, msg: "The application raised an exception. Please refresh your browser and try again later or contact the system administrator." });
            break;
        case "malformed":
            this.status(500).send({ code: 100, msg: "The request sent to the server was malformed. Please refresh your browser and try again or contact the system administrator." });
            break;
        case "exists?":
        case "container?":
        case "write?":
            this.status(500).send({ code: 200, msg: "The file repository cannot be accessed right now, please try again later." });
            break;
        case "exists":
            this.status(500).send({ code: 300, msg: "The file already exists, please flag to overwrite the existing file or upload with a different filename." });
            break;
        case "out-of-sync":
            this.status(500).send({ code: 400, msg: "The upload packets were not in the expected order. Please refresh your browser and select the file for upload again." });
            break;
        default:
            this.status(500).send({ code: 999, msg: "Unknown error." });
            break;
    }
}

// upload all or part of a file
app.post("/upload", function(req, res) {
    try {

var time = process.hrtime();
process.nextTick(function() {
    diff = process.hrtime(time);
    var nano = (diff[0] * 1e9 + diff[1]) / 1e6;
    console.log(nano);
});

        if (req.query.container && req.query.name && req.query.cmd && req.query.seq) {
            var overwrite = (req.query.overwrite == "true");
            var file = pending.find(req.query.container, req.query.name);
            var decoder = base64.decode();
            switch(req.query.cmd) {

                case "complete":
                    if (!file) {
                        // upload the file
                        pending.add(req.query.container, req.query.name, overwrite).then(function(file) {
                            req.pipe(decoder).pipe(file.writer);
                            pending.remove(req.query.container, req.query.name);
                            res.status(200).end();
                        }, function(error) {
                            res.sendError(error);
                        });
                    } else {
                        // replace the in-progress upload (implicit replace)
                        file.writer.end();
                        pending.remove(req.query.container, req.query.name);
                        pending.add(req.query.container, req.query.name, true).then(function(file) {
                            req.pipe(decoder).pipe(file.writer);
                            pending.remove(req.query.container, req.query.name);
                            res.status(200).end();
                        }, function(error) {
                            pending.remove(req.query.container, req.query.name);
                            res.sendError(error);
                        });
                    }
                    break;

                case "begin":
                //if (1==3) {
                    if (!file) {
                        // upload the file
                        pending.add(req.query.container, req.query.name, overwrite, function(file) {
                            //req.pipe(decoder).pipe(file.writer, { end: false });
                            //file.sequence++;
                            //res.status(200).end();
                            res.status(501).end();
                        }, function(error) {
                            res.sendError(error);
                        });
                    } else {
                        // resume the in-progress upload (implicit continue)
                        res.status(500).send("implement continue!!!");
                    }
                //}
                //res.status(501).end();
                    break;

                case "continue":
                    if (file.sequence == req.query.seq) {
                        req.pipe(decoder).pipe(file.writer, { end: false });
                        file.sequence++;
                        res.status(200).end();
                    } else if (req.query.seq < file.sequence) {
                        // the request sequence can be lower if it didn't get confirmation of a commit, let it catch up
                        res.status(200).end();
                    } else {
                        res.sendError("out-of-sync");
                    }
                    break;

                case "end":
                    if (file.sequence == req.query.seq) {
                        req.pipe(decoder).pipe(file.writer);
                        pending.remove(req.query.container, req.query.name);
                        res.status(200).end();
                    } else if (req.query.seq < file.sequence) {
                        // the request sequence can be lower if it didn't get confirmation of a commit, let it catch up
                        res.status(200).end();
                    } else {
                        res.sendError("out-of-sync");
                    }
                    break;

                case "abort":
                    pending.remove(req.query.container, req.query.name);
                    res.status(200).end();
                    break;

            }
        } else {
            res.sendError("malformed");
        }
        console.log("done");
    } catch (ex) {
        console.log("exception: " + ex);
        res.sendError("exception");
    }
});

// start the server
var port = process.env.port || 80;
app.listen(port, function() {
   console.log("listening on port " + port + "...");
});

//promise.onPossiblyUnhandledRejection(function(error){
//    console.log("error: " + error);
//    throw error;
//});

process.on('unhandledRejection', function(reason, p){
    console.log("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason);
    // application specific logging here
});

function startProfiling() {
    var id = "profile-" + Date.now();

    // Use stdout directly to bypass eventloop
    fs.writeSync(1, 'Start profiler with Id [' + id + ']\n');

    // Start profiling
    profiler.startProfiling(id);

    // Schedule stop of profiling in x seconds
    setTimeout(function () {
        stopProfiling(id)
    }, 1 * 60 * 1000);

    console.log("profile started");
}

function stopProfiling(id) {
    //var path = "/Users/plasne/Documents";
    var path = "/home/plasne"
    var profile = profiler.stopProfiling(id);
    fs.writeFile(path + '/filerepo/' + id + '.cpuprofile', JSON.stringify(profile), function () {
        console.log('Profiler data written');
    });
}

//setTimeout(startProfiling, 10 * 1000);
<commit_msg>fully working?<commit_after>
// notes:
// implement starting a file that was started before
// implement checking for an existing file in the container
// implement logging
// implement configuration file

//var q = require("q");
//var promise = require("bluebird");
var express = require("express");
var bodyParser = require("body-parser");
var wasb = require("azure-storage");
var fs = require("fs");
var base64 = require("base64-stream");
var stream = require("stream");
var profiler = require("v8-profiler");

var app = express();
app.use(express.static("client"));

var _service;

// the current upload queue
var pending = {
    list: [],

    ensureContainer: function(service, container, onSuccess, onFailure) {
        try {
            service.createContainerIfNotExists(container, function(error, result, response) {
                if (error) {
                    if (onFailure) onFailure("container?");
                } else {
                    if (onSuccess) onSuccess();
                }
            });
        } catch (ex) {
            console.log("ensureContainer: " + ex);
            if (onFailure) onFailure("container?");
        }
    },

    canWriteFile: function(service, container, name, overwrite, onSuccess, onFailure) {
        try {
            service.doesBlobExist(container, name, function(error, result, response) {
                if (error) {
                    if (onFailure) onFailure("exists?");
                } else {
                    if (!result.exists || overwrite) {
                        if (onSuccess) onSuccess();
                    } else {
                        if (onFailure) onFailure("exists");
                    }
                }
            });
        } catch (ex) {
            console.log("canWriteFile: " + ex);
            if (onFailure) onFailure("exists?");
        }
    },

    begin: function(service, container, name, onSuccess, onFailure) {
        try {

            // create a writable block blob
            var writer = service.createWriteStreamToBlockBlob(container, name, function(error, result, response) {
                if (!error) {
                    console.log("transferred");
                } else {
                    console.log("failed - " + error);
                }
            });
            writer.on("close", function() {
                console.log("close");
            });
            writer.on("finish", function() {
                console.log("finished");
            });

            // create a reference for the file
            var file = {
                container: container,
                name: name,
                sequence: 0,
                writer: writer
            };

            pending.list.push(file);
            if (onSuccess) onSuccess(file);

        } catch (ex) {
            console.log("begin: " + ex);
            if (onFailure) onFailure("write?");
        }
    },

    add: function(container, name, overwrite, onSuccess, onFailure) {
        var self = this;
        if (_service == null) {
            _service = wasb.createBlobService("2e2115eastus", "1tnb/X2r4VZNMyKOHmM4bJfollRsF1jId2pVAhTitdmszP4MH7kc39pm97ijhHtteRY5EzuDnkIBBz8tP/2CSQ==");
        }
        var service = _service;
        self.ensureContainer(service, container, function() {
            self.canWriteFile(service, container, name, overwrite, function() {
                self.begin(service, container, name, onSuccess, onFailure);
            }, onFailure);
        }, onFailure);
    },

    remove: function(container, name) {
        for (var i = 0; i < this.list.length; i++) {
            if (this.list[i].container == container && this.list[i].name == name) {
                this.list.splice(i, 1);
                return;
            }
        }
    },

    find: function(container, name) {
        for (var i = 0; i < this.list.length; i++) {
            if (this.list[i].container == container && this.list[i].name == name) return this.list[i];
        }
        return null;
    }

};

// hello
app.get("/hello", function(req, res) {
   res.send("hello");
});

app.post("/hello", function(req, res) {
   res.send("hello");
});

// home page
app.get("/", function(req, res) {
    res.redirect("/index.htm");
});

// extend the response object to include the custom error messages
express.response.sendError = function(error) {
    console.log("error: " + JSON.stringify(error));
    switch(error) {
        case "exception":
            this.status(500).send({ code: 000, msg: "The application raised an exception. Please refresh your browser and try again later or contact the system administrator." });
            break;
        case "malformed":
            this.status(500).send({ code: 100, msg: "The request sent to the server was malformed. Please refresh your browser and try again or contact the system administrator." });
            break;
        case "exists?":
        case "container?":
        case "write?":
            this.status(500).send({ code: 200, msg: "The file repository cannot be accessed right now, please try again later." });
            break;
        case "exists":
            this.status(500).send({ code: 300, msg: "The file already exists, please flag to overwrite the existing file or upload with a different filename." });
            break;
        case "out-of-sync":
            this.status(500).send({ code: 400, msg: "The upload packets were not in the expected order. Please refresh your browser and select the file for upload again." });
            break;
        default:
            this.status(500).send({ code: 999, msg: "Unknown error." });
            break;
    }
}

// upload all or part of a file
app.post("/upload", function(req, res) {
    try {

var time = process.hrtime();
process.nextTick(function() {
    diff = process.hrtime(time);
    var nano = (diff[0] * 1e9 + diff[1]) / 1e6;
    console.log(nano);
});

        if (req.query.container && req.query.name && req.query.cmd && req.query.seq) {
            var overwrite = (req.query.overwrite == "true");
            var file = pending.find(req.query.container, req.query.name);
            var decoder = base64.decode();
            switch(req.query.cmd) {

                case "complete":
                    if (!file) {
                        // upload the file
                        pending.add(req.query.container, req.query.name, overwrite).then(function(file) {
                            req.pipe(decoder).pipe(file.writer);
                            pending.remove(req.query.container, req.query.name);
                            res.status(200).end();
                        }, function(error) {
                            res.sendError(error);
                        });
                    } else {
                        // replace the in-progress upload (implicit replace)
                        file.writer.end();
                        pending.remove(req.query.container, req.query.name);
                        pending.add(req.query.container, req.query.name, true).then(function(file) {
                            req.pipe(decoder).pipe(file.writer);
                            pending.remove(req.query.container, req.query.name);
                            res.status(200).end();
                        }, function(error) {
                            pending.remove(req.query.container, req.query.name);
                            res.sendError(error);
                        });
                    }
                    break;

                case "begin":
                    if (!file) {
                        // upload the file
                        pending.add(req.query.container, req.query.name, overwrite, function(file) {
                            req.pipe(decoder).pipe(file.writer, { end: false });
                            file.sequence++;
                            res.status(200).end();
                        }, function(error) {
                            res.sendError(error);
                        });
                    } else {
                        // resume the in-progress upload (implicit continue)
                        res.status(500).send("implement continue!!!");
                    }
                    break;

                case "continue":
                    if (file.sequence == req.query.seq) {
                        req.pipe(decoder).pipe(file.writer, { end: false });
                        file.sequence++;
                        res.status(200).end();
                    } else if (req.query.seq < file.sequence) {
                        // the request sequence can be lower if it didn't get confirmation of a commit, let it catch up
                        res.status(200).end();
                    } else {
                        res.sendError("out-of-sync");
                    }
                    break;

                case "end":
                    if (file.sequence == req.query.seq) {
                        req.pipe(decoder).pipe(file.writer);
                        pending.remove(req.query.container, req.query.name);
                        res.status(200).end();
                    } else if (req.query.seq < file.sequence) {
                        // the request sequence can be lower if it didn't get confirmation of a commit, let it catch up
                        res.status(200).end();
                    } else {
                        res.sendError("out-of-sync");
                    }
                    break;

                case "abort":
                    pending.remove(req.query.container, req.query.name);
                    res.status(200).end();
                    break;

            }
        } else {
            res.sendError("malformed");
        }
        console.log("done");
    } catch (ex) {
        console.log("exception: " + ex);
        res.sendError("exception");
    }
});

// start the server
var port = process.env.port || 80;
app.listen(port, function() {
   console.log("listening on port " + port + "...");
});

//promise.onPossiblyUnhandledRejection(function(error){
//    console.log("error: " + error);
//    throw error;
//});

process.on('unhandledRejection', function(reason, p){
    console.log("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason);
    // application specific logging here
});

function startProfiling() {
    var id = "profile-" + Date.now();

    // Use stdout directly to bypass eventloop
    fs.writeSync(1, 'Start profiler with Id [' + id + ']\n');

    // Start profiling
    profiler.startProfiling(id);

    // Schedule stop of profiling in x seconds
    setTimeout(function () {
        stopProfiling(id)
    }, 1 * 60 * 1000);

    console.log("profile started");
}

function stopProfiling(id) {
    //var path = "/Users/plasne/Documents";
    var path = "/home/plasne"
    var profile = profiler.stopProfiling(id);
    fs.writeFile(path + '/filerepo/' + id + '.cpuprofile', JSON.stringify(profile), function () {
        console.log('Profiler data written');
    });
}

//setTimeout(startProfiling, 10 * 1000);
<|endoftext|>


<commit_before>module.exports = function(app, passport, provisioning, strategyConfig) {

  var GeoaxisStrategy = require('passport-geoaxis-oauth20').Strategy
    , crypto = require('crypto')
    , User = require('../models/user')
    , Device = require('../models/device')
    , Role = require('../models/role')
    , api = require('../api')
    , config = require('../config.js')
    , log = require('../logger');

  log.info('Configuring GeoAxis authentication');

  let strategy = new GeoaxisStrategy({
    authorizationURL: strategyConfig.authorizationUrl + '/ms_oauth/oauth2/endpoints/oauthservice/authorize',
    tokenURL: strategyConfig.apiUrl + '/ms_oauth/oauth2/endpoints/oauthservice/tokens',
    userProfileURL: strategyConfig.apiUrl + '/ms_oauth/resources/userprofile/me',
    clientID: strategyConfig.clientID,
    clientSecret: strategyConfig.clientSecret,
    callbackURL: strategyConfig.callbackUrl,
    passReqToCallback: true
  },
  function(req, accessToken, refreshToken, profile, done) {
    let geoaxisUser = profile._json;
    User.getUserByAuthenticationId('geoaxis', geoaxisUser.email, function(err, user) {
      if (err) return done(err);

      var email = geoaxisUser.email;

      if (!user) {
        // Create an account for the user
        Role.getRole('USER_ROLE', function(err, role) {
          if (err) return done(err);

          var user = {
            username: email,
            displayName: email.split("@")[0],
            email: email,
            active: false,
            roleId: role._id,
            authentication: {
              type: 'geoaxis',
              id: email
            }
          };

          User.createUser(user, function(err, newUser) {
            return done(err, newUser);
          });
        });
      } else if (!user.active) {
        return done(null, user, { message: "User is not approved, please contact your MAGE administrator to approve your account."} );
      } else {
        return done(null, user, {access_token: accessToken});
      }
    });
  });

  passport.use('geoaxis', strategy);

  app.get('/auth/geoaxis/signin', passport.authenticate('geoaxis'));

  function parseLoginMetadata(req, res, next) {
    req.loginOptions = {
      userAgent: req.header['user-agent'],
      appVersion: req.param('appVersion')
    };

    next();
  }

  function authenticate(req, res, next) {
    passport.authenticate('geoaxis', function(err, user, info = {}) {
      if (err) return next(err);

      req.info = info;

      req.login(user, next);
    })(req, res, next);
  }

  function authorizeUser(req, res, next) {
    let token = req.param('access_token');

    if (req.user) {
      next();
    } else if (token) {
      log.warn('DEPRECATED - authorization with access_token has been deprecated, please use a session');

      strategy.userProfile(token, function(err, profile) {
        if (err) {
          log.error('Not authenticated based on geoaxis access token', err);
          return res.sendStatus(403);
        }

        let geoaxisUser = profile._json;
        User.getUserByAuthenticationId('geoaxis', geoaxisUser.email, function(err, user) {
          if (err) return next(err);

          if (!user || !user.active) {
            return res.sendStatus(403);
          }

          req.user = user;
          next();
        });
      });
    } else {
      return res.sendStatus(403);
    }
  }

  function authorizeDevice(req, res, next) {
    provisioning.provision.check(provisioning.strategy, {uid: req.param('uid')}, function(err, device) {
      if (err) return next(err);

      if (provisioning.strategy === 'uid' && (!device || !device.registered)) {
        return res.sendStatus(403);
      } else {
        req.device = device;
        next();
      }
    })(req, res, next);
  }

  app.get(
    '/auth/geoaxis/signup',
    function(req, res, next) {
      passport.authenticate('geoaxis', {
        state: JSON.stringify({type: 'signup', id: crypto.randomBytes(32).toString('hex')})
      })(req, res, next);
    }
  );

  // Create a new device
  // Any authenticated user can create a new device, the registered field
  // will be set to false.
  app.post('/auth/geoaxis/devices',
    authorizeUser,
    function(req, res, next) {
      var newDevice = {
        uid: req.param('uid'),
        name: req.param('name'),
        registered: false,
        description: req.param('description'),
        userAgent: req.headers['user-agent'],
        appVersion: req.param('appVersion'),
        userId: req.user.id
      };

      Device.getDeviceByUid(newDevice.uid)
        .then(device => {
          if (device) {
            // already exists, do not register
            return res.json(device);
          }

          Device.createDevice(newDevice)
            .then(device => res.json(device))
            .catch(err => next(err));
        })
        .catch(err => next(err));
    }
  );

  app.post(
    '/auth/geoaxis/authorize',
    authorizeUser,
    authorizeDevice,
    parseLoginMetadata,
    function(req, res, next) {
      new api.User().login(req.user, req.device, req.loginOptions, function(err, token) {
        if (err) return next(err);

        var api = Object.assign({}, config.api);
        api.authenticationStrategies['geoaxis'] = {
          url: api.authenticationStrategies['geoaxis'].url,
          type: api.authenticationStrategies['geoaxis'].type,
          title: api.authenticationStrategies['geoaxis'].title,
          textColor: api.authenticationStrategies['geoaxis'].textColor,
          buttonColor: api.authenticationStrategies['geoaxis'].buttonColor,
          icon: api.authenticationStrategies['geoaxis'].icon
        };

        res.json({
          user: req.user,
          device: req.device,
          token: token.token,
          expirationDate: token.expirationDate ,
          api: api
        });
      });

      req.session.destroy();
    }
  );

  app.get(
    '/auth/geoaxis/callback',
    authenticate,
    function(req, res) {
      res.render('authentication', { host: req.getRoot(), success: true, login: {user: req.user, oauth: { access_token: req.info.access_token}}});
    }
  );

};
<commit_msg>Destroy session<commit_after>module.exports = function(app, passport, provisioning, strategyConfig) {

  var GeoaxisStrategy = require('passport-geoaxis-oauth20').Strategy
    , crypto = require('crypto')
    , User = require('../models/user')
    , Device = require('../models/device')
    , Role = require('../models/role')
    , api = require('../api')
    , config = require('../config.js')
    , log = require('../logger');

  log.info('Configuring GeoAxis authentication');

  let strategy = new GeoaxisStrategy({
    authorizationURL: strategyConfig.authorizationUrl + '/ms_oauth/oauth2/endpoints/oauthservice/authorize',
    tokenURL: strategyConfig.apiUrl + '/ms_oauth/oauth2/endpoints/oauthservice/tokens',
    userProfileURL: strategyConfig.apiUrl + '/ms_oauth/resources/userprofile/me',
    clientID: strategyConfig.clientID,
    clientSecret: strategyConfig.clientSecret,
    callbackURL: strategyConfig.callbackUrl,
    passReqToCallback: true
  },
  function(req, accessToken, refreshToken, profile, done) {
    let geoaxisUser = profile._json;
    User.getUserByAuthenticationId('geoaxis', geoaxisUser.email, function(err, user) {
      if (err) return done(err);

      var email = geoaxisUser.email;

      if (!user) {
        // Create an account for the user
        Role.getRole('USER_ROLE', function(err, role) {
          if (err) return done(err);

          var user = {
            username: email,
            displayName: email.split("@")[0],
            email: email,
            active: false,
            roleId: role._id,
            authentication: {
              type: 'geoaxis',
              id: email
            }
          };

          User.createUser(user, function(err, newUser) {
            return done(err, newUser);
          });
        });
      } else if (!user.active) {
        return done(null, user, { message: "User is not approved, please contact your MAGE administrator to approve your account."} );
      } else {
        return done(null, user, {access_token: accessToken});
      }
    });
  });

  passport.use('geoaxis', strategy);

  app.get('/auth/geoaxis/signin', passport.authenticate('geoaxis'));

  function parseLoginMetadata(req, res, next) {
    req.loginOptions = {
      userAgent: req.header['user-agent'],
      appVersion: req.param('appVersion')
    };

    next();
  }

  function authenticate(req, res, next) {
    passport.authenticate('geoaxis', function(err, user, info = {}) {
      if (err) return next(err);

      req.info = info;

      req.login(user, next);
    })(req, res, next);
  }

  function authorizeUser(req, res, next) {
    let token = req.param('access_token');

    if (req.user) {
      next();
    } else if (token) {
      log.warn('DEPRECATED - authorization with access_token has been deprecated, please use a session');

      strategy.userProfile(token, function(err, profile) {
        if (err) {
          log.error('Not authenticated based on geoaxis access token', err);
          return res.sendStatus(403);
        }

        let geoaxisUser = profile._json;
        User.getUserByAuthenticationId('geoaxis', geoaxisUser.email, function(err, user) {
          if (err) return next(err);

          if (!user || !user.active) {
            return res.sendStatus(403);
          }

          req.user = user;
          next();
        });
      });
    } else {
      return res.sendStatus(403);
    }
  }

  function authorizeDevice(req, res, next) {
    provisioning.provision.check(provisioning.strategy, {uid: req.param('uid')}, function(err, device) {
      if (err) return next(err);

      if (provisioning.strategy === 'uid' && (!device || !device.registered)) {
        return res.sendStatus(403);
      } else {
        req.device = device;
        next();
      }
    })(req, res, next);
  }

  app.get(
    '/auth/geoaxis/signup',
    function(req, res, next) {
      passport.authenticate('geoaxis', {
        state: JSON.stringify({type: 'signup', id: crypto.randomBytes(32).toString('hex')})
      })(req, res, next);
    }
  );

  // Create a new device
  // Any authenticated user can create a new device, the registered field
  // will be set to false.
  app.post('/auth/geoaxis/devices',
    authorizeUser,
    function(req, res, next) {
      var newDevice = {
        uid: req.param('uid'),
        name: req.param('name'),
        registered: false,
        description: req.param('description'),
        userAgent: req.headers['user-agent'],
        appVersion: req.param('appVersion'),
        userId: req.user.id
      };

      Device.getDeviceByUid(newDevice.uid)
        .then(device => {
          if (device) {
            // already exists, do not register
            return res.json(device);
          }

          Device.createDevice(newDevice)
            .then(device => res.json(device))
            .catch(err => next(err));
        })
        .catch(err => next(err));
    }
  );

  app.post(
    '/auth/geoaxis/authorize',
    authorizeUser,
    authorizeDevice,
    parseLoginMetadata,
    function(req, res, next) {
      new api.User().login(req.user, req.device, req.loginOptions, function(err, token) {
        if (err) return next(err);

        var api = Object.assign({}, config.api);
        api.authenticationStrategies['geoaxis'] = {
          url: api.authenticationStrategies['geoaxis'].url,
          type: api.authenticationStrategies['geoaxis'].type,
          title: api.authenticationStrategies['geoaxis'].title,
          textColor: api.authenticationStrategies['geoaxis'].textColor,
          buttonColor: api.authenticationStrategies['geoaxis'].buttonColor,
          icon: api.authenticationStrategies['geoaxis'].icon
        };

        res.json({
          user: req.user,
          device: req.device,
          token: token.token,
          expirationDate: token.expirationDate ,
          api: api
        });
      });

      req.session = null;
    }
  );

  app.get(
    '/auth/geoaxis/callback',
    authenticate,
    function(req, res) {
      res.render('authentication', { host: req.getRoot(), success: true, login: {user: req.user, oauth: { access_token: req.info.access_token}}});
    }
  );

};
<|endoftext|>


<commit_before>const Hierarchy = require('@antv/hierarchy');
const Util = require('../util');
const Graph = require('./graph');

function indexOfChild(children, child) {
  let index = -1;
  Util.each(children, (former, i) => {
    if (child.id === former.id) {
      index = i;
      return false;
    }
  });
  return index;
}

class TreeGraph extends Graph {
  constructor(cfg) {
    super(cfg);
    // 用于缓存动画结束后需要删除的节点
    this.set('removeList', []);
    this.set('layoutMethod', this._getLayout());
  }
  getDefaultCfg() {
    const cfg = super.getDefaultCfg();
    // 树图默认打开动画
    cfg.animate = true;
    return cfg;
  }
  /**
   * 根据data接口的数据渲染视图
   */
  render() {
    const self = this;
    const data = self.get('data');
    if (!data) {
      throw new Error('data must be defined first');
    }
    self.clear();
    self.emit('beforerender');
    self.refreshLayout();
    self.emit('afterrender');
  }
  /**
   * 添加子树到对应 id 的节点
   * @param {object} data 子树数据模型
   * @param {string} parent 子树的父节点id
   */
  addChild(data, parent) {
    const self = this;
    // 将数据添加到源数据中，走changeData方法
    if (!Util.isString(parent)) {
      parent = parent.get('id');
    }
    const parentData = self.findById(parent).getModel();
    if (!parentData.children) {
      parentData.children = [];
    }
    parentData.children.push(data);
    self.changeData();
  }
  // 计算好layout的数据添加到graph中
  _addChild(data, parent, animate) {
    const self = this;
    const model = data.data;
    // model 中应存储真实的数据，特别是真实的 children
    model.x = data.x;
    model.y = data.y;
    const node = self.addItem('node', model);
    if (parent) {
      node.set('parent', parent);
      if (animate) {
        const origin = parent.get('origin');
        if (origin) {
          node.set('origin', origin);
        } else {
          node.set('origin', {
            x: parent.getModel().x,
            y: parent.getModel().y
          });
        }
      }
      const childrenList = parent.get('children');
      if (!childrenList) {
        parent.set('children', [ node ]);
      } else {
        childrenList.push(node);
      }
      self.addItem('edge', { source: parent, target: node, id: parent.get('id') + ':' + node.get('id') });
    }
    // 渲染到视图上应参考布局的children, 避免多绘制了收起的节点
    Util.each(data.children, child => {
      self._addChild(child, node, animate);
    });
    return node;
  }
  /**
   * 更新数据模型，差量更新并重新渲染
   * @param {object} data 数据模型
   */
  changeData(data) {
    const self = this;
    if (data) {
      self.data(data);
      self.render();
    } else {
      self.refreshLayout();
    }
  }
  /**
   * 更新源数据，差量更新子树
   * @param {object} data 子树数据模型
   * @param {string} parent 子树的父节点id
   */
  updateChild(data, parent) {
    const self = this;
    // 如果没有父节点，是全量的更新，直接重置data
    if (!parent) {
      self.changeData(data);
      return;
    }
    const parentModel = self.findById(parent).getModel();
    const current = self.findById(data.id);
    // 如果不存在该节点，则添加
    if (!current) {
      if (!parentModel.children) {
        parentModel.children = [ current ];
      } else {
        parentModel.children.push(data);
      }
    } else {
      const index = indexOfChild(parentModel.children, data);
      parent.children[index] = data;
    }
    self.changeData();
  }

  // 将数据上的变更转换到视图上
  _updateChild(data, parent, animate) {
    const self = this;
    const current = self.findById(data.id);
    // 若子树不存在，整体添加即可
    if (!current) {
      self._addChild(data, parent, animate);
      return;
    }
    // 更新新节点下所有子节点
    Util.each(data.children, child => {
      self._updateChild(child, current, animate);
    });
    // 用现在节点的children实例来删除移除的子节点
    const children = current.get('children');
    if (children) {
      const len = children.length;
      if (len > 0) {
        let child;
        for (let i = children.length - 1; i >= 0; i--) {
          child = children[i].getModel();
          if (indexOfChild(data.children, child) === -1) {
            self._removeChild(child.id, {
              x: data.x,
              y: data.y
            }, animate);
            // 更新父节点下缓存的子节点 item 实例列表
            children.splice(i, 1);
          }
        }
      }
    }
    const model = current.getModel();
    if (animate) {
      // 如果有动画，先缓存节点运动再更新节点
      current.set('origin', {
        x: model.x,
        y: model.y
      });
    }
    model.x = data.x;
    model.y = data.y;
  }
  /**
   * 删除子树
   * @param {string} id 子树根节点id
   */
  removeChild(id) {
    const self = this;
    const node = self.findById(id);
    if (!node) {
      return;
    }
    const parent = node.get('parent');
    if (parent && !parent.destroyed) {
      const siblings = parent.getModel().children;
      const index = indexOfChild(siblings, node.getModel());
      siblings.splice(index, 1);
    }
    self.changeData();
  }
  // 删除子节点Item对象
  _removeChild(id, to, animate) {
    const self = this;
    const node = self.findById(id);
    if (!node) {
      return;
    }
    Util.each(node.get('children'), child => {
      self._removeChild(child.getModel().id, to, animate);
    });
    if (animate) {
      const model = node.getModel();
      node.set('to', to);
      node.set('origin', { x: model.x, y: model.y });
      self.get('removeList').push(node);
    } else {
      self.removeItem(node);
    }
  }
  /**
   * 导出图数据
   * @return {object} data
   */
  save() {
    return this.get('data');
  }
  /**
   * 根据id获取对应的源数据
   * @param {string|object} id 元素id
   * @param {object} parent 从哪个节点开始寻找，为空时从根节点开始查找
   * @return {object} 对应源数据
   */
  findDataById(id, parent) {
    const self = this;
    if (!parent) {
      parent = self.get('data');
    }
    if (id === parent.id) {
      return parent;
    }
    let result = null;
    Util.each(parent.children, child => {
      if (child.id === id) {
        result = child;
        return false;
      }
      result = self.findDataById(id, child);
      if (result) {
        return false;
      }
    });
    return result;
  }
  /**
   * 更改并应用树布局算法
   * @param {object} layout 布局算法
   */
  changeLayout(layout) {
    const self = this;
    if (!layout) {
      console.warn('layout cannot be null');
      return;
    }
    self.set('layout', self._getLayout());
    if (layout) {
      self.refreshLayout();
    }
  }

  /**
   * 根据目前的 data 刷新布局，更新到画布上。用于变更数据之后刷新视图。
   */
  refreshLayout() {
    const self = this;
    const data = self.get('data');
    const layoutData = self.get('layoutMethod')(data);
    const animate = self.get('animate');
    const autoPaint = self.get('autoPaint');
    self.emit('beforerefreshlayout', { data, layoutData });
    self.setAutoPaint(false);
    self._updateChild(layoutData, null, animate);
    if (!animate) {
      // 如果没有动画，目前仅更新了节点的位置，刷新一下边的样式
      self.refreshPositions();
      self.paint();
    } else {
      self.layoutAnimate(layoutData, null);
    }
    self.setAutoPaint(autoPaint);
    self.emit('afterrefreshlayout', { data, layoutData });
  }
  /**
   * 布局动画接口，用于数据更新时做节点位置更新的动画
   * @param {object} data 更新的数据
   * @param {function} onFrame 定义节点位置更新时如何移动
   * @param {number} duration 动画时间
   * @param {string} ease 指定动效
   * @param {function} callback 动画结束的回调
   * @param {number} delay 动画延迟执行(ms)
   */
  layoutAnimate(data, onFrame) {
    const self = this;
    const animateCfg = this.get('animateCfg');
    self.emit('layoutanimatestart', { data });
    // 如果边中没有指定锚点，但是本身有锚点控制，在动画过程中保持锚点不变
    self.getEdges().forEach(edge => {
      const model = edge.get('model');
      if (!model.sourceAnchor) {
        model.sourceAnchor = edge.get('sourceAnchorIndex');
      }
    });
    this.get('canvas').animate({
      onFrame(ratio) {
        Util.traverseTree(data, child => {
          const node = self.findById(child.id);
          let origin = node.get('origin');
          const model = node.get('model');
          if (!origin) {
            origin = {
              x: model.x,
              y: model.y
            };
            node.set('origin', origin);
          }
          if (onFrame) {
            const attrs = onFrame(node, ratio, origin, data);
            node.set('model', Util.mix(model, attrs));
          } else {
            model.x = origin.x + (child.x - origin.x) * ratio;
            model.y = origin.y + (child.y - origin.y) * ratio;
          }
        });
        Util.each(self.get('removeList'), node => {
          const model = node.getModel();
          const from = node.get('origin');
          const to = node.get('to');
          model.x = from.x + (to.x - from.x) * ratio;
          model.y = from.y + (to.y - from.y) * ratio;
        });
        self.refreshPositions();
      }
    }, animateCfg.duration, animateCfg.ease, () => {
      Util.each(self.getNodes(), node => {
        node.set('origin', null);
      });
      Util.each(self.get('removeList'), node => {
        self.removeItem(node);
      });
      self.set('removeList', []);
      if (animateCfg.callback) {
        animateCfg.callback();
      }
      self.emit('layoutanimateend', { data });
    }, animateCfg.delay);
  }
  /**
   * 立即停止布局动画
   */
  stopLayoutAnimate() {
    this.get('canvas').stopAnimate();
    this.emit('layoutanimateend', { data: this.get('data') });
    this.layoutAnimating = false;
  }

  /**
   * 是否在布局动画
   * @return {boolean} 是否有布局动画
   */
  isLayoutAnimating() {
    return this.layoutAnimating;
  }
  _getLayout() {
    const layout = this.get('layout');
    if (!layout) {
      return null;
    }
    if (typeof layout === 'function') {
      return layout;
    }
    if (!layout.type) {
      layout.type = 'dendrogram';
    }
    if (!layout.direction) {
      layout.direction = 'TB';
    }
    return function(data) {
      return Hierarchy[layout.type](data, layout);
    };
  }
}

module.exports = TreeGraph;
<commit_msg>chore: modify get parent coord<commit_after>const Hierarchy = require('@antv/hierarchy');
const Util = require('../util');
const Graph = require('./graph');

function indexOfChild(children, child) {
  let index = -1;
  Util.each(children, (former, i) => {
    if (child.id === former.id) {
      index = i;
      return false;
    }
  });
  return index;
}

class TreeGraph extends Graph {
  constructor(cfg) {
    super(cfg);
    // 用于缓存动画结束后需要删除的节点
    this.set('removeList', []);
    this.set('layoutMethod', this._getLayout());
  }
  getDefaultCfg() {
    const cfg = super.getDefaultCfg();
    // 树图默认打开动画
    cfg.animate = true;
    return cfg;
  }
  /**
   * 根据data接口的数据渲染视图
   */
  render() {
    const self = this;
    const data = self.get('data');
    if (!data) {
      throw new Error('data must be defined first');
    }
    self.clear();
    self.emit('beforerender');
    self.refreshLayout();
    self.emit('afterrender');
  }
  /**
   * 添加子树到对应 id 的节点
   * @param {object} data 子树数据模型
   * @param {string} parent 子树的父节点id
   */
  addChild(data, parent) {
    const self = this;
    // 将数据添加到源数据中，走changeData方法
    if (!Util.isString(parent)) {
      parent = parent.get('id');
    }
    const parentData = self.findById(parent).getModel();
    if (!parentData.children) {
      parentData.children = [];
    }
    parentData.children.push(data);
    self.changeData();
  }
  // 计算好layout的数据添加到graph中
  _addChild(data, parent, animate) {
    const self = this;
    const model = data.data;
    // model 中应存储真实的数据，特别是真实的 children
    model.x = data.x;
    model.y = data.y;
    const node = self.addItem('node', model);
    if (parent) {
      node.set('parent', parent);
      if (animate) {
        const origin = parent.get('origin');
        if (origin) {
          node.set('origin', origin);
        } else {
          const parentModel = parent.getModel();
          node.set('origin', {
            x: parentModel.x,
            y: parentModel.y
          });
        }
      }
      const childrenList = parent.get('children');
      if (!childrenList) {
        parent.set('children', [ node ]);
      } else {
        childrenList.push(node);
      }
      self.addItem('edge', { source: parent, target: node, id: parent.get('id') + ':' + node.get('id') });
    }
    // 渲染到视图上应参考布局的children, 避免多绘制了收起的节点
    Util.each(data.children, child => {
      self._addChild(child, node, animate);
    });
    return node;
  }
  /**
   * 更新数据模型，差量更新并重新渲染
   * @param {object} data 数据模型
   */
  changeData(data) {
    const self = this;
    if (data) {
      self.data(data);
      self.render();
    } else {
      self.refreshLayout();
    }
  }
  /**
   * 更新源数据，差量更新子树
   * @param {object} data 子树数据模型
   * @param {string} parent 子树的父节点id
   */
  updateChild(data, parent) {
    const self = this;
    // 如果没有父节点，是全量的更新，直接重置data
    if (!parent) {
      self.changeData(data);
      return;
    }
    const parentModel = self.findById(parent).getModel();
    const current = self.findById(data.id);
    // 如果不存在该节点，则添加
    if (!current) {
      if (!parentModel.children) {
        parentModel.children = [ current ];
      } else {
        parentModel.children.push(data);
      }
    } else {
      const index = indexOfChild(parentModel.children, data);
      parent.children[index] = data;
    }
    self.changeData();
  }

  // 将数据上的变更转换到视图上
  _updateChild(data, parent, animate) {
    const self = this;
    const current = self.findById(data.id);
    // 若子树不存在，整体添加即可
    if (!current) {
      self._addChild(data, parent, animate);
      return;
    }
    // 更新新节点下所有子节点
    Util.each(data.children, child => {
      self._updateChild(child, current, animate);
    });
    // 用现在节点的children实例来删除移除的子节点
    const children = current.get('children');
    if (children) {
      const len = children.length;
      if (len > 0) {
        let child;
        for (let i = children.length - 1; i >= 0; i--) {
          child = children[i].getModel();
          if (indexOfChild(data.children, child) === -1) {
            self._removeChild(child.id, {
              x: data.x,
              y: data.y
            }, animate);
            // 更新父节点下缓存的子节点 item 实例列表
            children.splice(i, 1);
          }
        }
      }
    }
    const model = current.getModel();
    if (animate) {
      // 如果有动画，先缓存节点运动再更新节点
      current.set('origin', {
        x: model.x,
        y: model.y
      });
    }
    model.x = data.x;
    model.y = data.y;
  }
  /**
   * 删除子树
   * @param {string} id 子树根节点id
   */
  removeChild(id) {
    const self = this;
    const node = self.findById(id);
    if (!node) {
      return;
    }
    const parent = node.get('parent');
    if (parent && !parent.destroyed) {
      const siblings = parent.getModel().children;
      const index = indexOfChild(siblings, node.getModel());
      siblings.splice(index, 1);
    }
    self.changeData();
  }
  // 删除子节点Item对象
  _removeChild(id, to, animate) {
    const self = this;
    const node = self.findById(id);
    if (!node) {
      return;
    }
    Util.each(node.get('children'), child => {
      self._removeChild(child.getModel().id, to, animate);
    });
    if (animate) {
      const model = node.getModel();
      node.set('to', to);
      node.set('origin', { x: model.x, y: model.y });
      self.get('removeList').push(node);
    } else {
      self.removeItem(node);
    }
  }
  /**
   * 导出图数据
   * @return {object} data
   */
  save() {
    return this.get('data');
  }
  /**
   * 根据id获取对应的源数据
   * @param {string|object} id 元素id
   * @param {object} parent 从哪个节点开始寻找，为空时从根节点开始查找
   * @return {object} 对应源数据
   */
  findDataById(id, parent) {
    const self = this;
    if (!parent) {
      parent = self.get('data');
    }
    if (id === parent.id) {
      return parent;
    }
    let result = null;
    Util.each(parent.children, child => {
      if (child.id === id) {
        result = child;
        return false;
      }
      result = self.findDataById(id, child);
      if (result) {
        return false;
      }
    });
    return result;
  }
  /**
   * 更改并应用树布局算法
   * @param {object} layout 布局算法
   */
  changeLayout(layout) {
    const self = this;
    if (!layout) {
      console.warn('layout cannot be null');
      return;
    }
    self.set('layout', self._getLayout());
    if (layout) {
      self.refreshLayout();
    }
  }

  /**
   * 根据目前的 data 刷新布局，更新到画布上。用于变更数据之后刷新视图。
   */
  refreshLayout() {
    const self = this;
    const data = self.get('data');
    const layoutData = self.get('layoutMethod')(data);
    const animate = self.get('animate');
    const autoPaint = self.get('autoPaint');
    self.emit('beforerefreshlayout', { data, layoutData });
    self.setAutoPaint(false);
    self._updateChild(layoutData, null, animate);
    if (!animate) {
      // 如果没有动画，目前仅更新了节点的位置，刷新一下边的样式
      self.refreshPositions();
      self.paint();
    } else {
      self.layoutAnimate(layoutData, null);
    }
    self.setAutoPaint(autoPaint);
    self.emit('afterrefreshlayout', { data, layoutData });
  }
  /**
   * 布局动画接口，用于数据更新时做节点位置更新的动画
   * @param {object} data 更新的数据
   * @param {function} onFrame 定义节点位置更新时如何移动
   * @param {number} duration 动画时间
   * @param {string} ease 指定动效
   * @param {function} callback 动画结束的回调
   * @param {number} delay 动画延迟执行(ms)
   */
  layoutAnimate(data, onFrame) {
    const self = this;
    const animateCfg = this.get('animateCfg');
    self.emit('layoutanimatestart', { data });
    // 如果边中没有指定锚点，但是本身有锚点控制，在动画过程中保持锚点不变
    self.getEdges().forEach(edge => {
      const model = edge.get('model');
      if (!model.sourceAnchor) {
        model.sourceAnchor = edge.get('sourceAnchorIndex');
      }
    });
    this.get('canvas').animate({
      onFrame(ratio) {
        Util.traverseTree(data, child => {
          const node = self.findById(child.id);
          let origin = node.get('origin');
          const model = node.get('model');
          if (!origin) {
            origin = {
              x: model.x,
              y: model.y
            };
            node.set('origin', origin);
          }
          if (onFrame) {
            const attrs = onFrame(node, ratio, origin, data);
            node.set('model', Util.mix(model, attrs));
          } else {
            model.x = origin.x + (child.x - origin.x) * ratio;
            model.y = origin.y + (child.y - origin.y) * ratio;
          }
        });
        Util.each(self.get('removeList'), node => {
          const model = node.getModel();
          const from = node.get('origin');
          const to = node.get('to');
          model.x = from.x + (to.x - from.x) * ratio;
          model.y = from.y + (to.y - from.y) * ratio;
        });
        self.refreshPositions();
      }
    }, animateCfg.duration, animateCfg.ease, () => {
      Util.each(self.getNodes(), node => {
        node.set('origin', null);
      });
      Util.each(self.get('removeList'), node => {
        self.removeItem(node);
      });
      self.set('removeList', []);
      if (animateCfg.callback) {
        animateCfg.callback();
      }
      self.emit('layoutanimateend', { data });
    }, animateCfg.delay);
  }
  /**
   * 立即停止布局动画
   */
  stopLayoutAnimate() {
    this.get('canvas').stopAnimate();
    this.emit('layoutanimateend', { data: this.get('data') });
    this.layoutAnimating = false;
  }

  /**
   * 是否在布局动画
   * @return {boolean} 是否有布局动画
   */
  isLayoutAnimating() {
    return this.layoutAnimating;
  }
  _getLayout() {
    const layout = this.get('layout');
    if (!layout) {
      return null;
    }
    if (typeof layout === 'function') {
      return layout;
    }
    if (!layout.type) {
      layout.type = 'dendrogram';
    }
    if (!layout.direction) {
      layout.direction = 'TB';
    }
    return function(data) {
      return Hierarchy[layout.type](data, layout);
    };
  }
}

module.exports = TreeGraph;
<|endoftext|>


<commit_before><commit_msg>Add main doc block<commit_after>/**
 * Cangraph
 *
 * Graphing and function plotting for the HTML5 canvas. Main function plotting
 * engine was taken from the link below and modified.
 *
 * @link http://www.javascripter.net/faq/plotafunctiongraph.htm
 */<|endoftext|>


<commit_before>;(function($){
/**
 * jqGrid Bulgarian Translation
 * Tony Tomov tony@trirand.com
 * http://trirand.com/blog/
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
**/
$.jgrid = {
	defaults : {
		recordtext: "{0} - {1} от {2}",
		emptyrecords: "Няма запис(и)",
		loadtext: "Зареждам...",
		pgtext : "Стр. {0} от {1}"
	},
	search : {
		caption: "Търсене...",
		Find: "Намери",
		Reset: "Изчисти",
		odata : ['равно', 'различно', 'по-малко', 'по-малко или=','по-голямо','по-голямо или =', 'започва с','не започва с','се намира в','не се намира в','завършва с','не завършава с','съдържа', 'не съдържа' ],
	    groupOps: [	{ op: "AND", text: " И " },	{ op: "OR",  text: "ИЛИ" }	],
		matchText: " включи",
		rulesText: " клауза"
	},
	edit : {
		addCaption: "Нов Запис",
		editCaption: "Редакция Запис",
		bSubmit: "Запиши",
		bCancel: "Изход",
		bClose: "Затвори",
		saveData: "Данните са променени! Да съхраня ли промените?",
		bYes : "Да",
		bNo : "Не",
		bExit : "Отказ",
		msg: {
		    required:"Полето е задължително",
		    number:"Въведете валидно число!",
		    minValue:"стойността трябва да е по-голяма или равна от",
		    maxValue:"стойността трябва да е по-малка или равна от",
		    email: "не е валиден ел. адрес",
		    integer: "Въведете валидно цяло число",
			date: "Въведете валидна дата",
			url: "e невалиден URL. Изискава се префикс('http://' или 'https://')"
		}
	},
	view : {
	    caption: "Преглед запис",
	    bClose: "Затвори"
	},
	del : {
		caption: "Изтриване",
		msg: "Да изтрия ли избраният запис?",
		bSubmit: "Изтрий",
		bCancel: "Отказ"
	},
	nav : {
		edittext: " ",
		edittitle: "Редакция избран запис",
		addtext:" ",
		addtitle: "Добавяне нов запис",
		deltext: " ",
		deltitle: "Изтриване избран запис",
		searchtext: " ",
		searchtitle: "Търсене запис(и)",
		refreshtext: "",
		refreshtitle: "Обнови таблица",
		alertcap: "Предупреждение",
		alerttext: "Моля, изберете запис",
		viewtext: "",
		viewtitle: "Преглед избран запис"
	},
	col : {
		caption: "Колони",
		bSubmit: "Запис",
		bCancel: "Изход"
	},
	errors : {
		errcap : "Грешка",
		nourl : "Няма посочен url адрес",
		norecords: "Няма запис за обработка",
		model : "Модела не съответства на имената!"
	},
	formatter : {
		integer : {thousandsSeparator: " ", defaultValue: '0'},
		number : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0.00'},
		currency : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix:" лв.", defaultValue: '0.00'},
		date : {
			dayNames:   [
				"Нед", "Пон", "Вт", "Ср", "Чет", "Пет", "Съб",
				"Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота"
			],
			monthNames: [
				"Яну", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Нов", "Дек",
				"Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"
			],
			AmPm : ["","","",""],
			S: function (j) {
				if(j==7 || j==8 || j== 27 || j== 28) {
					return 'ми';
				}
				return ['ви', 'ри', 'ти'][Math.min((j - 1) % 10, 2)];
			},
			srcformat: 'Y-m-d',
			newformat: 'd/m/Y',
			masks : {
		        ISO8601Long:"Y-m-d H:i:s",
		        ISO8601Short:"Y-m-d",
		        ShortDate: "n/j/Y",
		        LongDate: "l, F d, Y",
		        FullDateTime: "l, F d, Y g:i:s A",
		        MonthDay: "F d",
		        ShortTime: "g:i A",
		        LongTime: "g:i:s A",
		        SortableDateTime: "Y-m-d\\TH:i:s",
		        UniversalSortableDateTime: "Y-m-d H:i:sO",
		        YearMonth: "F, Y"
		    },
		    reformatAfterEdit : false
		},
		baseLinkUrl: '',
		showAction: '',
		target: '',
		checkbox : {disabled:true},
		idName : 'id'
	}
};
})(jQuery);
<commit_msg>Some Changes	in Bulgarian lang files<commit_after>;(function($){
/**
 * jqGrid Bulgarian Translation
 * Tony Tomov tony@trirand.com
 * http://trirand.com/blog/
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
**/
$.jgrid = {
	defaults : {
		recordtext: "{0} - {1} от {2}",
		emptyrecords: "Няма запис(и)",
		loadtext: "Зареждам...",
		pgtext : "Стр. {0} от {1}"
	},
	search : {
		caption: "Търсене...",
		Find: "Намери",
		Reset: "Изчисти",
		odata : ['равно', 'различно', 'по-малко', 'по-малко или=','по-голямо','по-голямо или =', 'започва с','не започва с','се намира в','не се намира в','завършва с','не завършава с','съдържа', 'не съдържа' ],
	    groupOps: [	{ op: "AND", text: " И " },	{ op: "OR",  text: "ИЛИ" }	],
		matchText: " включи",
		rulesText: " клауза"
	},
	edit : {
		addCaption: "Нов Запис",
		editCaption: "Редакция Запис",
		bSubmit: "Запиши",
		bCancel: "Изход",
		bClose: "Затвори",
		saveData: "Данните са променени! Да съхраня ли промените?",
		bYes : "Да",
		bNo : "Не",
		bExit : "Отказ",
		msg: {
			required:"Полето е задължително",
			number:"Въведете валидно число!",
			minValue:"стойността трябва да е по-голяма или равна от",
			maxValue:"стойността трябва да е по-малка или равна от",
			email: "не е валиден ел. адрес",
			integer: "Въведете валидно цяло число",
			date: "Въведете валидна дата",
			url: "e невалиден URL. Изискава се префикс('http://' или 'https://')"
		}
	},
	view : {
		caption: "Преглед запис",
		bClose: "Затвори"
	},
	del : {
		caption: "Изтриване",
		msg: "Да изтрия ли избраният запис?",
		bSubmit: "Изтрий",
		bCancel: "Отказ"
	},
	nav : {
		edittext: " ",
		edittitle: "Редакция избран запис",
		addtext:" ",
		addtitle: "Добавяне нов запис",
		deltext: " ",
		deltitle: "Изтриване избран запис",
		searchtext: " ",
		searchtitle: "Търсене запис(и)",
		refreshtext: "",
		refreshtitle: "Обнови таблица",
		alertcap: "Предупреждение",
		alerttext: "Моля, изберете запис",
		viewtext: "",
		viewtitle: "Преглед избран запис"
	},
	col : {
		caption: "Колони",
		bSubmit: "Запис",
		bCancel: "Изход"
	},
	errors : {
		errcap : "Грешка",
		nourl : "Няма посочен url адрес",
		norecords: "Няма запис за обработка",
		model : "Модела не съответства на имената!"
	},
	formatter : {
		integer : {thousandsSeparator: " ", defaultValue: '0'},
		number : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0.00'},
		currency : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix:" лв.", defaultValue: '0.00'},
		date : {
			dayNames:   [
				"Нед", "Пон", "Вт", "Ср", "Чет", "Пет", "Съб",
				"Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота"
			],
			monthNames: [
				"Яну", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Нов", "Дек",
				"Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"
			],
			AmPm : ["","","",""],
			S: function (j) {
				if(j==7 || j==8 || j== 27 || j== 28) {
					return 'ми';
				}
				return ['ви', 'ри', 'ти'][Math.min((j - 1) % 10, 2)];
			},
			srcformat: 'Y-m-d',
			newformat: 'd/m/Y',
			masks : {
				ISO8601Long:"Y-m-d H:i:s",
				ISO8601Short:"Y-m-d",
				ShortDate: "n/j/Y",
				LongDate: "l, F d, Y",
				FullDateTime: "l, F d, Y g:i:s A",
				MonthDay: "F d",
				ShortTime: "g:i A",
				LongTime: "g:i:s A",
				SortableDateTime: "Y-m-d\\TH:i:s",
				UniversalSortableDateTime: "Y-m-d H:i:sO",
				YearMonth: "F, Y"
			},
			reformatAfterEdit : false
		},
		baseLinkUrl: '',
		showAction: '',
		target: '',
		checkbox : {disabled:true},
		idName : 'id'
	}
};
})(jQuery);
<|endoftext|>