<commit_before>// ==========================================================================
//                      DG.MapDataConfiguration
//
//  Author:   William Finzer
//
//  Copyright ©2014 Concord Consortium
//
//  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.
// ==========================================================================
/* global L */
sc_require('components/graph_map_common/plot_data_configuration');

/** @class  DG.MapDataConfiguration - The object that describes the manner in which attributes are
 assigned to places in a map.

 @extends DG.PlotDataConfiguration
 */
DG.MapDataConfiguration = DG.PlotDataConfiguration.extend(
    /** @scope DG.MapDataConfiguration.prototype */
    {
      /**
       * It is in initialization that we specialize from base class
       */
      init: function () {

        var attributeDescriptions = {
              caption: DG.AttributePlacementDescription.create(),
              x: DG.AttributePlacementDescription.create(),
              y: DG.AttributePlacementDescription.create(),
              legend: DG.AttributePlacementDescription.create(),
              area: DG.AttributePlacementDescription.create()
            },
            tPlace,
            tMapContext,
            tCaptionName, tLatName, tLongName, tAreaName,
            tCaptionAttr, tLatAttr, tLongAttr, tAreaAttr,
            kLatNames = ['latitude', 'lat', 'latitud'],
            kLongNames = ['longitude', 'long', 'lng', 'lon', 'longitud'],
            kAreaNames = ['boundary', 'boundaries', 'polygon', 'polygons'];

        sc_super();

        var configureAttributeDescriptions = function () {

          var configAttrDesc = function (iAttrPrefix, iAttr, iCollectionClient) {
            var attrDesc = attributeDescriptions[iAttrPrefix],
                tType;

            DG.assert(attrDesc);

            // Configure the attribute description for the specified default attribute
            if (iAttr) {
              // Must set collection before attribute for attribute stats to be configured properly
              attrDesc.set('collectionClient', iCollectionClient);
              attrDesc.set('attribute', iAttr);
              switch (iAttrPrefix) {
                case 'x':
                case 'y':
                  tType = DG.Analysis.EAttributeType.eNumeric;
                  break;
                case 'area':
                  tType = DG.Analysis.EAttributeType.eBoundary;
                  break;
                case 'caption':
                  tType = DG.Analysis.EAttributeType.eCategorical;
                  break;
              }
              if (tType)
                attrDesc.setPath('attributeStats.attributeType', tType);
            }
            // Null out the attribute description when no map attribute is specified.
            else {
              attrDesc.set('collectionClient', null);
              attrDesc.removeAllAttributes();
            }

            // We must set these up manually, because the infrastructure isn't in place by
            // the time sc_super() (i.e. SC.Object.init()) is called.
            attrDesc.addObserver('collectionClient', this, iAttrPrefix + 'CollectionDidChange');
            attrDesc.addObserver('collection', this, 'collectionDidChange');
          }.bind(this);

          var tMapCollection, tMapCollectionClient;
          DG.currDocumentController().get('contexts').forEach(function (iContext) {
            iContext.get('collections').forEach(function (iCollection) {
              var tAttrNames = (iCollection && iCollection.getAttributeNames()) || [],
                  // Make a copy, all lower case. We will need the original if we find a match.
                  tLowerCaseNames = tAttrNames.map( function( iAttrName) {
                    return iAttrName.toLowerCase();
                  });

              function pickOutName( iKNames) {
                return tAttrNames.find(function (iAttrName, iIndex) {
                  return iKNames.find(function (iKName) {
                    return (iKName === tLowerCaseNames[ iIndex]);
                  });
                });
              }

              tLatName = pickOutName( kLatNames);
              tLongName = pickOutName( kLongNames);
              tAreaName = pickOutName( kAreaNames);
              if( !tAreaName) {  // Try for an attribute that has a boundary type
                ((iCollection && iCollection.get('attrs')) || []).some( function( iAttr) {
                  if( iAttr.get('type') === 'boundary') {
                    tAreaName = iAttr.get('name');
                    return true;
                  } else {
                    return false;
                  }
                });
              }

              if ((tLatName && tLongName) || tAreaName) {
                tMapContext = iContext;
                tMapCollection = iCollection;
                tMapCollectionClient = iContext.getCollectionByID( iCollection.get('id'));
              }
            });
          });
          if (tMapCollection && tMapCollectionClient) {
            tCaptionName = tMapCollection.getAttributeNames()[0];
            tCaptionAttr = tCaptionName && tMapCollection.getAttributeByName(tCaptionName);
            tLatAttr = tLatName && tMapCollection.getAttributeByName(tLatName);
            tLongAttr = tLongName && tMapCollection.getAttributeByName(tLongName);
            tAreaAttr = tAreaName && tMapCollection.getAttributeByName(tAreaName);

            configAttrDesc('caption', tCaptionAttr, tMapCollectionClient);
            configAttrDesc('x', tLongAttr, tMapCollectionClient);
            configAttrDesc('y', tLatAttr, tMapCollectionClient);
            configAttrDesc('legend', null, tMapCollectionClient);
            configAttrDesc('area', tAreaAttr, tMapCollectionClient);
          }
        }.bind(this);

        // -- main routine of init
        configureAttributeDescriptions();

        // Prepare the attributes array. It has as many elements as there are places,
        //  and, initially, those elements are empty arrays.
        this.attributesByPlace = [];
        for (tPlace = 0; tPlace < DG.GraphTypes.EPlace.eNumPlaces; tPlace++)
          this.attributesByPlace[tPlace] = [];

        // Actually, during this coding transition, we're going to stash the previously
        // initialized attribute descriptions in attributesByPlace.
        this.attributesByPlace[DG.GraphTypes.EPlace.eCaption][0] = attributeDescriptions.caption;
        this.attributesByPlace[DG.GraphTypes.EPlace.eX][0] = attributeDescriptions.x;
        this.attributesByPlace[DG.GraphTypes.EPlace.eY][0] = attributeDescriptions.y;
        this.attributesByPlace[DG.GraphTypes.EPlace.eLegend][0] = attributeDescriptions.legend;
        this.attributesByPlace[DG.GraphTypes.EPlace.eArea][0] = attributeDescriptions.area;
        this.set('dataContext', tMapContext);
      },

      /**
       * Override to return use the source that is used by either the area attribute or one of the lat/long attributes.
       * Note that we are assuming that _either_ the area attribute _or_ the xAttribute have a collection client. If
       * they both have collection clients and those are different, this won't be adequate.
       */
      collectionClient: function () {
        var tKeys = ['areaAttributeDescription', 'xAttributeDescription'],
            tSource;
        tKeys.forEach(function (iAttrKey) {
          var tAttrDesc = this.get(iAttrKey);
          if (tAttrDesc && tAttrDesc.collectionClient)
            tSource = tAttrDesc.collectionClient;
        }.bind(this));
        return tSource;
      }.property(),

      /**
       * @property {SC.Array of DG.Case} All cases, both hidden and visible
       */
      allCases: function () {
        if( this.get('hasAreaAttribute') || this.get('hasLatLongAttributes')) {
          var tSource = this.get('collectionClient');
          return tSource ? tSource.getPath('casesController.arrangedObjects') : null;
        }
      }.property(),

      /**
       @property { DG.AttributePlacementDescription }
       */
      areaAttributeDescription: function (iKey, iValue) {
        return this.attributeDescriptionForPlace(iKey, iValue, DG.GraphTypes.EPlace.eArea);
      }.property(),

      areaCollectionDidChange: function () {
        this.notifyPropertyChange('areaCollectionClient');
      },

      areaCollectionClient: function () {
        return this.getPath('areaAttributeDescription.collectionClient');
      }.property().cacheable(),
      areaCollectionClientDidChange: function() {
        this.notifyPropertyChange('areaCollectionClient');
      }.observes('*areaAttributeDescription.collectionClient'),

      latAttributeID: function () {
        return this.getPath('yAttributeDescription.attributeID');
      }.property(),
      latAttributeIDDidChange: function() {
        this.notifyPropertyChange('latAttributeID');
      }.observes('*yAttributeDescription.attributeID'),

      longAttributeID: function () {
        return this.getPath('xAttributeDescription.attributeID');
      }.property(),
      longAttributeIDDidChange: function() {
        this.notifyPropertyChange('longAttributeID');
      }.observes('*xAttributeDescription.attributeID'),

      // These two bindings help us with API for connecting line model
      xVarIDBinding: '.longAttributeID',
      yVarIDBinding: '.latAttributeID',

      hasLatLongAttributes: function () {
        return !SC.none(this.get('latAttributeID')) && !SC.none(this.get('longAttributeID'));
      }.property('latAttributeID', 'longAttributeID'),

      areaAttributeID: function () {
        return this.getPath('areaAttributeDescription.attributeID');
      }.property(),
      areaAttributeIDDidChange: function() {
        this.notifyPropertyChange('areaAttributeID');
      }.observes('*areaAttributeDescription.attributeID'),

      hasAreaAttribute: function () {
        return !SC.none(this.get('areaAttributeID'));
      }.property('areaAttributeID'),

      /**
       @property { DG.AttributePlacementDescription }
       */
      captionAttributeDescription: function (iKey, iValue) {
        return this.attributeDescriptionForPlace(iKey, iValue, DG.GraphTypes.EPlace.eCaption);
      }.property(),

      captionCollectionDidChange: function () {
        this.notifyPropertyChange('captionCollectionClient');
      },

      captionCollectionClient: function () {
        return this.getPath('captionAttributeDescription.collectionClient');
      }.property(),
      captionCollectionClientDidChange: function() {
        this.notifyPropertyChange('captionCollectionClient');
      }.observes('*captionAttributeDescription.collectionClient'),

      captionAttributeID: function () {
        return this.getPath('captionAttributeDescription.attributeID');
      }.property(),
      captionAttributeIDDidChange: function() {
        this.notifyPropertyChange('captionAttributeID');
      }.observes('*captionAttributeDescription.attributeID'),

      getLatLongBounds: function () {

        var getRationalLongitudeBounds = function () {
              var tLongID = this.getPath('xAttributeDescription.attribute.id'),
                  tLongs = [];
              this.get('cases').forEach(function (iCase) {
                var tLong = iCase.getNumValue(tLongID);
                if (isFinite(tLong))
                  tLongs.push(tLong);
              });
              tLongs.sort(function (iV1, iV2) {
                return iV1 - iV2;
              });
              var tLength = tLongs.length,
                  tMin = tLongs[0],
                  tMax = tLongs[tLength - 1],
                  tMedian;
              while (tMax - tMin > 180) {
                tMin = Math.min(tMin, tLongs[0]);
                tMax = Math.max(tMax, tLongs[tLength - 1]);
                tMedian = tLongs[Math.floor( tLength / 2)];
                if (tMax - tMedian > tMedian - tMin) {
                  tMax -= 360;
                  if (tMax < tLongs[tLength - 2]) {
                    tMin = Math.min( tMin, tMax);
                    tMax = tLongs[ tLength - 2];
                    tLongs.pop();
                  }
                }
                else {
                  tMin += 360;
                  if (tMin > tLongs[1]) {
                    tMax = Math.max( tMax, tMin);
                    tMin = tLongs[1];
                    tLongs.shift();
                  }
                }
                tLength = tLongs.length;
                if( tMax < tMin) {
                  var tTemp = tMax;
                  tMax = tMin;
                  tMin = tTemp;
                }
              }
              return {min: tMin, max: tMax};
            }.bind(this),

            isValid = function (iMinMax) {
              return DG.isFinite(iMinMax.min) && DG.isFinite(iMinMax.max);
            };

        var tLatMinMax = this.getPath('yAttributeDescription.attributeStats.minMax'),
            tLngMinMax = this.getPath('xAttributeDescription.attributeStats.minMax');

        if( tLngMinMax.max - tLngMinMax.min > 180)
          tLngMinMax = getRationalLongitudeBounds();

        var tSouthWest = [tLatMinMax.min, tLngMinMax.min],
            tNorthEast = [tLatMinMax.max, tLngMinMax.max],
            tBounds = (isValid( tLatMinMax) && isValid(tLngMinMax)) ? L.latLngBounds([tSouthWest, tNorthEast]) : null;
        return tBounds;
      },

      /**
       * If there is an area attribute, go through its values, finding the rectangle that encompases all
       * the coordinates.
       * @returns {*[]}
       */
      getAreaBounds: function () {
        var tCases = this.get('cases'),
            tAreaID = this.get('areaAttributeID'),
            tMinWest = 180, tMaxEast = -180, tMinSouth = 90, tMaxNorth = -90;
        if (!tAreaID)
          return null;

        function processArrayOfCoords(iArrayOfCoords) {
          iArrayOfCoords.forEach(function (iPoint) {
            tMinSouth = Math.min(tMinSouth, iPoint[1]);
            tMaxNorth = Math.max(tMaxNorth, iPoint[1]);
            tMinWest = Math.min(tMinWest, iPoint[0]);
            tMaxEast = Math.max(tMaxEast, iPoint[0]);
          });
        }

        tCases.forEach(function (iCase) {
          try {
            var tFeature = JSON.parse(iCase.getValue(tAreaID)),
                tCoords = tFeature.geometry.coordinates,
                tType = tFeature.geometry.type;
            tCoords.forEach(function (iArray) {
              switch (tType) {
                case 'Polygon':
                  processArrayOfCoords(iArray);
                  break;
                case 'MultiPolygon':
                  iArray.forEach(function (iSubArray) {
                    processArrayOfCoords(iSubArray);
                  });
                  break;
              }
            });
          }
          catch (er) {
            console.log(er);
          }
        });

        return L.latLngBounds([[tMinSouth, tMinWest], [tMaxNorth, tMaxEast]]);
      }

    });

<commit_msg>[#156765710] Bug fix: Neither boundary data nor point data  appear in maps when not in rightmost collection<commit_after>// ==========================================================================
//                      DG.MapDataConfiguration
//
//  Author:   William Finzer
//
//  Copyright ©2014 Concord Consortium
//
//  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.
// ==========================================================================
/* global L */
sc_require('components/graph_map_common/plot_data_configuration');

/** @class  DG.MapDataConfiguration - The object that describes the manner in which attributes are
 assigned to places in a map.

 @extends DG.PlotDataConfiguration
 */
DG.MapDataConfiguration = DG.PlotDataConfiguration.extend(
    /** @scope DG.MapDataConfiguration.prototype */
    {
      /**
       * It is in initialization that we specialize from base class
       */
      init: function () {

        var attributeDescriptions = {
              caption: DG.AttributePlacementDescription.create(),
              x: DG.AttributePlacementDescription.create(),
              y: DG.AttributePlacementDescription.create(),
              legend: DG.AttributePlacementDescription.create(),
              area: DG.AttributePlacementDescription.create()
            },
            tPlace,
            tMapContext,
            tCaptionName, tLatName, tLongName, tAreaName,
            tCaptionAttr, tLatAttr, tLongAttr, tAreaAttr,
            kLatNames = ['latitude', 'lat', 'latitud'],
            kLongNames = ['longitude', 'long', 'lng', 'lon', 'longitud'],
            kAreaNames = ['boundary', 'boundaries', 'polygon', 'polygons'];

        sc_super();

        var configureAttributeDescriptions = function () {

          var configAttrDesc = function (iAttrPrefix, iAttr, iCollectionClient) {
            var attrDesc = attributeDescriptions[iAttrPrefix],
                tType;

            DG.assert(attrDesc);

            // Configure the attribute description for the specified default attribute
            if (iAttr) {
              // Must set collection before attribute for attribute stats to be configured properly
              attrDesc.set('collectionClient', iCollectionClient);
              attrDesc.set('attribute', iAttr);
              switch (iAttrPrefix) {
                case 'x':
                case 'y':
                  tType = DG.Analysis.EAttributeType.eNumeric;
                  break;
                case 'area':
                  tType = DG.Analysis.EAttributeType.eBoundary;
                  break;
                case 'caption':
                  tType = DG.Analysis.EAttributeType.eCategorical;
                  break;
              }
              if (tType)
                attrDesc.setPath('attributeStats.attributeType', tType);
            }
            // Null out the attribute description when no map attribute is specified.
            else {
              attrDesc.set('collectionClient', null);
              attrDesc.removeAllAttributes();
            }

            // We must set these up manually, because the infrastructure isn't in place by
            // the time sc_super() (i.e. SC.Object.init()) is called.
            attrDesc.addObserver('collectionClient', this, iAttrPrefix + 'CollectionDidChange');
            attrDesc.addObserver('collection', this, 'collectionDidChange');
          }.bind(this);

          var tMapCollection, tMapCollectionClient;
          DG.currDocumentController().get('contexts').forEach(function (iContext) {
            iContext.get('collections').forEach(function (iCollection) {
              // If we've already identified a map context, we're done
              if( tMapContext)
                return;

              var tAttrNames = (iCollection && iCollection.getAttributeNames()) || [],
                  // Make a copy, all lower case. We will need the original if we find a match.
                  tLowerCaseNames = tAttrNames.map( function( iAttrName) {
                    return iAttrName.toLowerCase();
                  });

              function pickOutName( iKNames) {
                return tAttrNames.find(function (iAttrName, iIndex) {
                  return iKNames.find(function (iKName) {
                    return (iKName === tLowerCaseNames[ iIndex]);
                  });
                });
              }

              tLatName = pickOutName( kLatNames);
              tLongName = pickOutName( kLongNames);
              tAreaName = pickOutName( kAreaNames);
              if( !tAreaName) {  // Try for an attribute that has a boundary type
                ((iCollection && iCollection.get('attrs')) || []).some( function( iAttr) {
                  if( iAttr.get('type') === 'boundary') {
                    tAreaName = iAttr.get('name');
                    return true;
                  } else {
                    return false;
                  }
                });
              }

              if ((tLatName && tLongName) || tAreaName) {
                tMapContext = iContext;
                tMapCollection = iCollection;
                tMapCollectionClient = iContext.getCollectionByID( iCollection.get('id'));
              }
            });
          });
          if (tMapCollection && tMapCollectionClient) {
            tCaptionName = tMapCollection.getAttributeNames()[0];
            tCaptionAttr = tCaptionName && tMapCollection.getAttributeByName(tCaptionName);
            tLatAttr = tLatName && tMapCollection.getAttributeByName(tLatName);
            tLongAttr = tLongName && tMapCollection.getAttributeByName(tLongName);
            tAreaAttr = tAreaName && tMapCollection.getAttributeByName(tAreaName);

            configAttrDesc('caption', tCaptionAttr, tMapCollectionClient);
            configAttrDesc('x', tLongAttr, tMapCollectionClient);
            configAttrDesc('y', tLatAttr, tMapCollectionClient);
            configAttrDesc('legend', null, tMapCollectionClient);
            configAttrDesc('area', tAreaAttr, tMapCollectionClient);
          }
        }.bind(this);

        // -- main routine of init
        configureAttributeDescriptions();

        // Prepare the attributes array. It has as many elements as there are places,
        //  and, initially, those elements are empty arrays.
        this.attributesByPlace = [];
        for (tPlace = 0; tPlace < DG.GraphTypes.EPlace.eNumPlaces; tPlace++)
          this.attributesByPlace[tPlace] = [];

        // Actually, during this coding transition, we're going to stash the previously
        // initialized attribute descriptions in attributesByPlace.
        this.attributesByPlace[DG.GraphTypes.EPlace.eCaption][0] = attributeDescriptions.caption;
        this.attributesByPlace[DG.GraphTypes.EPlace.eX][0] = attributeDescriptions.x;
        this.attributesByPlace[DG.GraphTypes.EPlace.eY][0] = attributeDescriptions.y;
        this.attributesByPlace[DG.GraphTypes.EPlace.eLegend][0] = attributeDescriptions.legend;
        this.attributesByPlace[DG.GraphTypes.EPlace.eArea][0] = attributeDescriptions.area;
        this.set('dataContext', tMapContext);
      },

      /**
       * Override to return use the source that is used by either the area attribute or one of the lat/long attributes.
       * Note that we are assuming that _either_ the area attribute _or_ the xAttribute have a collection client. If
       * they both have collection clients and those are different, this won't be adequate.
       */
      collectionClient: function () {
        var tKeys = ['areaAttributeDescription', 'xAttributeDescription'],
            tSource;
        tKeys.forEach(function (iAttrKey) {
          var tAttrDesc = this.get(iAttrKey);
          if (tAttrDesc && tAttrDesc.collectionClient)
            tSource = tAttrDesc.collectionClient;
        }.bind(this));
        return tSource;
      }.property(),

      /**
       * @property {SC.Array of DG.Case} All cases, both hidden and visible
       */
      allCases: function () {
        if( this.get('hasAreaAttribute') || this.get('hasLatLongAttributes')) {
          var tSource = this.get('collectionClient');
          return tSource ? tSource.getPath('casesController.arrangedObjects') : null;
        }
      }.property(),

      /**
       @property { DG.AttributePlacementDescription }
       */
      areaAttributeDescription: function (iKey, iValue) {
        return this.attributeDescriptionForPlace(iKey, iValue, DG.GraphTypes.EPlace.eArea);
      }.property(),

      areaCollectionDidChange: function () {
        this.notifyPropertyChange('areaCollectionClient');
      },

      areaCollectionClient: function () {
        return this.getPath('areaAttributeDescription.collectionClient');
      }.property().cacheable(),
      areaCollectionClientDidChange: function() {
        this.notifyPropertyChange('areaCollectionClient');
      }.observes('*areaAttributeDescription.collectionClient'),

      latAttributeID: function () {
        return this.getPath('yAttributeDescription.attributeID');
      }.property(),
      latAttributeIDDidChange: function() {
        this.notifyPropertyChange('latAttributeID');
      }.observes('*yAttributeDescription.attributeID'),

      longAttributeID: function () {
        return this.getPath('xAttributeDescription.attributeID');
      }.property(),
      longAttributeIDDidChange: function() {
        this.notifyPropertyChange('longAttributeID');
      }.observes('*xAttributeDescription.attributeID'),

      // These two bindings help us with API for connecting line model
      xVarIDBinding: '.longAttributeID',
      yVarIDBinding: '.latAttributeID',

      hasLatLongAttributes: function () {
        return !SC.none(this.get('latAttributeID')) && !SC.none(this.get('longAttributeID'));
      }.property('latAttributeID', 'longAttributeID'),

      areaAttributeID: function () {
        return this.getPath('areaAttributeDescription.attributeID');
      }.property(),
      areaAttributeIDDidChange: function() {
        this.notifyPropertyChange('areaAttributeID');
      }.observes('*areaAttributeDescription.attributeID'),

      hasAreaAttribute: function () {
        return !SC.none(this.get('areaAttributeID'));
      }.property('areaAttributeID'),

      /**
       @property { DG.AttributePlacementDescription }
       */
      captionAttributeDescription: function (iKey, iValue) {
        return this.attributeDescriptionForPlace(iKey, iValue, DG.GraphTypes.EPlace.eCaption);
      }.property(),

      captionCollectionDidChange: function () {
        this.notifyPropertyChange('captionCollectionClient');
      },

      captionCollectionClient: function () {
        return this.getPath('captionAttributeDescription.collectionClient');
      }.property(),
      captionCollectionClientDidChange: function() {
        this.notifyPropertyChange('captionCollectionClient');
      }.observes('*captionAttributeDescription.collectionClient'),

      captionAttributeID: function () {
        return this.getPath('captionAttributeDescription.attributeID');
      }.property(),
      captionAttributeIDDidChange: function() {
        this.notifyPropertyChange('captionAttributeID');
      }.observes('*captionAttributeDescription.attributeID'),

      getLatLongBounds: function () {

        var getRationalLongitudeBounds = function () {
              var tLongID = this.getPath('xAttributeDescription.attribute.id'),
                  tLongs = [];
              this.get('cases').forEach(function (iCase) {
                var tLong = iCase.getNumValue(tLongID);
                if (isFinite(tLong))
                  tLongs.push(tLong);
              });
              tLongs.sort(function (iV1, iV2) {
                return iV1 - iV2;
              });
              var tLength = tLongs.length,
                  tMin = tLongs[0],
                  tMax = tLongs[tLength - 1],
                  tMedian;
              while (tMax - tMin > 180) {
                tMin = Math.min(tMin, tLongs[0]);
                tMax = Math.max(tMax, tLongs[tLength - 1]);
                tMedian = tLongs[Math.floor( tLength / 2)];
                if (tMax - tMedian > tMedian - tMin) {
                  tMax -= 360;
                  if (tMax < tLongs[tLength - 2]) {
                    tMin = Math.min( tMin, tMax);
                    tMax = tLongs[ tLength - 2];
                    tLongs.pop();
                  }
                }
                else {
                  tMin += 360;
                  if (tMin > tLongs[1]) {
                    tMax = Math.max( tMax, tMin);
                    tMin = tLongs[1];
                    tLongs.shift();
                  }
                }
                tLength = tLongs.length;
                if( tMax < tMin) {
                  var tTemp = tMax;
                  tMax = tMin;
                  tMin = tTemp;
                }
              }
              return {min: tMin, max: tMax};
            }.bind(this),

            isValid = function (iMinMax) {
              return DG.isFinite(iMinMax.min) && DG.isFinite(iMinMax.max);
            };

        var tLatMinMax = this.getPath('yAttributeDescription.attributeStats.minMax'),
            tLngMinMax = this.getPath('xAttributeDescription.attributeStats.minMax');

        if( tLngMinMax.max - tLngMinMax.min > 180)
          tLngMinMax = getRationalLongitudeBounds();

        var tSouthWest = [tLatMinMax.min, tLngMinMax.min],
            tNorthEast = [tLatMinMax.max, tLngMinMax.max],
            tBounds = (isValid( tLatMinMax) && isValid(tLngMinMax)) ? L.latLngBounds([tSouthWest, tNorthEast]) : null;
        return tBounds;
      },

      /**
       * If there is an area attribute, go through its values, finding the rectangle that encompases all
       * the coordinates.
       * @returns {*[]}
       */
      getAreaBounds: function () {
        var tCases = this.get('cases'),
            tAreaID = this.get('areaAttributeID'),
            tMinWest = 180, tMaxEast = -180, tMinSouth = 90, tMaxNorth = -90;
        if (!tAreaID)
          return null;

        function processArrayOfCoords(iArrayOfCoords) {
          iArrayOfCoords.forEach(function (iPoint) {
            tMinSouth = Math.min(tMinSouth, iPoint[1]);
            tMaxNorth = Math.max(tMaxNorth, iPoint[1]);
            tMinWest = Math.min(tMinWest, iPoint[0]);
            tMaxEast = Math.max(tMaxEast, iPoint[0]);
          });
        }

        tCases.forEach(function (iCase) {
          try {
            var tFeature = JSON.parse(iCase.getValue(tAreaID)),
                tCoords = tFeature.geometry.coordinates,
                tType = tFeature.geometry.type;
            tCoords.forEach(function (iArray) {
              switch (tType) {
                case 'Polygon':
                  processArrayOfCoords(iArray);
                  break;
                case 'MultiPolygon':
                  iArray.forEach(function (iSubArray) {
                    processArrayOfCoords(iSubArray);
                  });
                  break;
              }
            });
          }
          catch (er) {
            console.log(er);
          }
        });

        return L.latLngBounds([[tMinSouth, tMinWest], [tMaxNorth, tMaxEast]]);
      }

    });

<|endoftext|>


<commit_before>/*
 *
 * (c) Copyright Ascensio System Limited 2010-2018
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

/**
 *  Links.js
 *
 *  Created by Julia Radzhabova on 22.12.2017
 *  Copyright (c) 2018 Ascensio System SIA. All rights reserved.
 *
 */

define([
    'core',
    'documenteditor/main/app/view/Links',
    'documenteditor/main/app/view/NoteSettingsDialog',
    'documenteditor/main/app/view/HyperlinkSettingsDialog',
    'documenteditor/main/app/view/TableOfContentsSettings',
    'documenteditor/main/app/view/BookmarksDialog'
], function () {
    'use strict';

    DE.Controllers.Links = Backbone.Controller.extend(_.extend({
        models : [],
        collections : [
        ],
        views : [
            'Links'
        ],
        sdkViewName : '#id_main',

        initialize: function () {

            this.addListeners({
                'Links': {
                    'links:contents': this.onTableContents,
                    'links:update': this.onTableContentsUpdate,
                    'links:notes': this.onNotesClick,
                    'links:hyperlink': this.onHyperlinkClick,
                    'links:bookmarks': this.onBookmarksClick
                },
                'DocumentHolder': {
                    'links:contents': this.onTableContents,
                    'links:update': this.onTableContentsUpdate
                }
            });
        },
        onLaunch: function () {
            this._state = {
                prcontrolsdisable:undefined
            };
        },

        setApi: function (api) {
            if (api) {
                this.api = api;
                this.api.asc_registerCallback('asc_onFocusObject', this.onApiFocusObject.bind(this));
                this.api.asc_registerCallback('asc_onCanAddHyperlink',      _.bind(this.onApiCanAddHyperlink, this));
                this.api.asc_registerCallback('asc_onCoAuthoringDisconnect',_.bind(this.onCoAuthoringDisconnect, this));
                Common.NotificationCenter.on('api:disconnect', _.bind(this.onCoAuthoringDisconnect, this));
                this.api.asc_registerCallback('asc_onShowContentControlsActions',_.bind(this.onShowContentControlsActions, this));
                this.api.asc_registerCallback('asc_onHideContentControlsActions',_.bind(this.onHideContentControlsActions, this));

            }
            return this;
        },

        setConfig: function(config) {
            this.toolbar = config.toolbar;
            this.view = this.createView('Links', {
                toolbar: this.toolbar.toolbar
            });
        },

        SetDisabled: function(state) {
            this.view && this.view.SetDisabled(state);
        },

        getView: function(name) {
            return !name && this.view ?
                this.view : Backbone.Controller.prototype.getView.call(this, name);
        },

        onCoAuthoringDisconnect: function() {
            this.SetDisabled(true);
        },

        onApiFocusObject: function(selectedObjects) {
            if (!this.toolbar.editMode) return;

            var pr, i = -1, type,
                paragraph_locked = false,
                header_locked = false,
                in_header = false,
                in_equation = false,
                in_image = false;

            while (++i < selectedObjects.length) {
                type = selectedObjects[i].get_ObjectType();
                pr   = selectedObjects[i].get_ObjectValue();

                if (type === Asc.c_oAscTypeSelectElement.Paragraph) {
                    paragraph_locked = pr.get_Locked();
                } else if (type === Asc.c_oAscTypeSelectElement.Header) {
                    header_locked = pr.get_Locked();
                    in_header = true;
                } else if (type === Asc.c_oAscTypeSelectElement.Image) {
                    in_image = true;
                } else if (type === Asc.c_oAscTypeSelectElement.Math) {
                    in_equation = true;
                }
            }

            this._state.prcontrolsdisable = paragraph_locked || header_locked;

            var control_props = this.api.asc_IsContentControl() ? this.api.asc_GetContentControlProperties() : null,
                control_plain = (control_props) ? (control_props.get_ContentControlType()==Asc.c_oAscSdtLevelType.Inline) : false;

            var need_disable = paragraph_locked || in_equation || in_image || in_header || control_plain;
            _.each (this.view.btnsNotes, function(item){
                item.setDisabled(need_disable);
            }, this);

            need_disable = paragraph_locked || header_locked || control_plain;
            this.view.btnBookmarks.setDisabled(need_disable);
        },

        onApiCanAddHyperlink: function(value) {
            var need_disable = !value || this._state.prcontrolsdisable;

            if ( this.toolbar.editMode ) {
                _.each (this.view.btnsHyperlink, function(item){
                    item.setDisabled(need_disable);
                }, this);
            }
        },

        onHyperlinkClick: function(btn) {
            var me = this,
                win, props, text;

            if (me.api){

                var handlerDlg = function(dlg, result) {
                    if (result == 'ok') {
                        props = dlg.getSettings();
                        (text!==false)
                            ? me.api.add_Hyperlink(props)
                            : me.api.change_Hyperlink(props);
                    }

                    Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                };

                text = me.api.can_AddHyperlink();

                if (text !== false) {
                    win = new DE.Views.HyperlinkSettingsDialog({
                        api: me.api,
                        handler: handlerDlg
                    });

                    props = new Asc.CHyperlinkProperty();
                    props.put_Text(text);

                    win.show();
                    win.setSettings(props);
                } else {
                    var selectedElements = me.api.getSelectedElements();
                    if (selectedElements && _.isArray(selectedElements)){
                        _.each(selectedElements, function(el, i) {
                            if (selectedElements[i].get_ObjectType() == Asc.c_oAscTypeSelectElement.Hyperlink)
                                props = selectedElements[i].get_ObjectValue();
                        });
                    }
                    if (props) {
                        win = new DE.Views.HyperlinkSettingsDialog({
                            api: me.api,
                            handler: handlerDlg
                        });
                        win.show();
                        win.setSettings(props);
                    }
                }
            }

            Common.component.Analytics.trackEvent('ToolBar', 'Add Hyperlink');
        },

        onTableContents: function(type, currentTOC){
            currentTOC = !!currentTOC;
            var props = this.api.asc_GetTableOfContentsPr(currentTOC);
            switch (type) {
                case 0:
                    if (!props) {
                        props = new Asc.CTableOfContentsPr();
                        props.put_OutlineRange(1, 9);
                    }
                    props.put_Hyperlink(true);
                    props.put_ShowPageNumbers(true);
                    props.put_RightAlignTab(true);
                    props.put_TabLeader( Asc.c_oAscTabLeader.Dot);
                    (currentTOC) ? this.api.asc_SetTableOfContentsPr(props) : this.api.asc_AddTableOfContents(null, props);
                    break;
                case 1:
                    if (!props) {
                        props = new Asc.CTableOfContentsPr();
                        props.put_OutlineRange(1, 9);
                    }
                    props.put_Hyperlink(true);
                    props.put_ShowPageNumbers(false);
                    props.put_TabLeader( Asc.c_oAscTabLeader.None);
                    (currentTOC) ? this.api.asc_SetTableOfContentsPr(props) : this.api.asc_AddTableOfContents(null, props);
                    break;
                case 'settings':
                    var me = this,
                        win = new DE.Views.TableOfContentsSettings({
                        api: this.api,
                        props: props,
                        handler: function(result, value) {
                            if (result == 'ok') {
                                (props) ? me.api.asc_SetTableOfContentsPr(value) : me.api.asc_AddTableOfContents(null, value);
                            }
                            Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                        }
                    });
                    win.show();
                    break;
                case 'remove':
                    currentTOC = (currentTOC && props) ? props.get_InternalClass() : undefined;
                    this.api.asc_RemoveTableOfContents(currentTOC);
                    break;
            }
            Common.NotificationCenter.trigger('edit:complete', this.toolbar);
        },

        onTableContentsUpdate: function(type, currentTOC){
            var props = this.api.asc_GetTableOfContentsPr(currentTOC);
            if (props) {
                if (currentTOC && props)
                    currentTOC = props.get_InternalClass();
                this.api.asc_UpdateTableOfContents(type == 'pages', currentTOC);
            }
            Common.NotificationCenter.trigger('edit:complete', this.toolbar);
        },

        onNotesClick: function(type) {
            var me = this;
            switch (type) {
                case 'ins_footnote':
                    this.api.asc_AddFootnote();
                    break;
                case 'delele':
                    Common.UI.warning({
                        msg: this.view.confirmDeleteFootnotes,
                        buttons: ['yes', 'no'],
                        primary: 'yes',
                        callback: _.bind(function (btn) {
                            if (btn == 'yes') {
                                this.api.asc_RemoveAllFootnotes();
                            }
                            Common.NotificationCenter.trigger('edit:complete', this.toolbar);
                        }, this)
                    });
                    break;
                case 'settings':
                    (new DE.Views.NoteSettingsDialog({
                        api: me.api,
                        handler: function (result, settings) {
                            if (settings) {
                                me.api.asc_SetFootnoteProps(settings.props, settings.applyToAll);
                                if (result == 'insert')
                                    me.api.asc_AddFootnote(settings.custom);
                            }
                            Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                        },
                        props: me.api.asc_GetFootnoteProps()
                    })).show();
                    break;
                case 'prev':
                    this.api.asc_GotoFootnote(false);
                    setTimeout(function() {
                        Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                    }, 50);
                    break;
                case 'next':
                    this.api.asc_GotoFootnote(true);
                    setTimeout(function() {
                        Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                    }, 50);
                    break;
            }
        },

        onBookmarksClick: function(btn) {
            var me = this;
            (new DE.Views.BookmarksDialog({
                api: me.api,
                props: me.api.asc_GetBookmarksManager(),
                handler: function (result, settings) {
                    if (settings) {
                    }
                    Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                }
            })).show();
        },

        onShowContentControlsActions: function(action, x, y) {
            var menu = (action==1) ? this.view.contentsUpdateMenu : this.view.contentsMenu,
                documentHolderView  = this.getApplication().getController('DocumentHolder').documentHolder,
                menuContainer = documentHolderView.cmpEl.find(Common.Utils.String.format('#menu-container-{0}', menu.id)),
                me = this;

            if (!menu) return;
            this._fromShowContentControls = true;
            Common.UI.Menu.Manager.hideAll();

            if (!menu.rendered) {
                // Prepare menu container
                if (menuContainer.length < 1) {
                    menuContainer = $(Common.Utils.String.format('

', menu.id));
                    documentHolderView.cmpEl.append(menuContainer);
                }

                menu.render(menuContainer);
                menu.cmpEl.attr({tabindex: "-1"});
                menu.on('hide:after', function(){
                    if (!me._fromShowContentControls)
                        me.api.asc_UncheckContentControlButtons();
                });
            }

            menuContainer.css({left: x, top : y});
            menuContainer.attr('data-value', 'prevent-canvas-click');
            documentHolderView._preventClick = true;
            menu.show();

            menu.alignPosition();
            _.delay(function() {
                menu.cmpEl.focus();
            }, 10);
            this._fromShowContentControls = false;
        },

        onHideContentControlsActions: function() {
            this.view.contentsMenu && this.view.contentsMenu.hide();
            this.view.contentsUpdateMenu && this.view.contentsUpdateMenu.hide();
        }

    }, DE.Controllers.Links || {}));
});<commit_msg>[DE] Fix Bug 38957<commit_after>/*
 *
 * (c) Copyright Ascensio System Limited 2010-2018
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia,
 * EU, LV-1021.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

/**
 *  Links.js
 *
 *  Created by Julia Radzhabova on 22.12.2017
 *  Copyright (c) 2018 Ascensio System SIA. All rights reserved.
 *
 */

define([
    'core',
    'documenteditor/main/app/view/Links',
    'documenteditor/main/app/view/NoteSettingsDialog',
    'documenteditor/main/app/view/HyperlinkSettingsDialog',
    'documenteditor/main/app/view/TableOfContentsSettings',
    'documenteditor/main/app/view/BookmarksDialog'
], function () {
    'use strict';

    DE.Controllers.Links = Backbone.Controller.extend(_.extend({
        models : [],
        collections : [
        ],
        views : [
            'Links'
        ],
        sdkViewName : '#id_main',

        initialize: function () {

            this.addListeners({
                'Links': {
                    'links:contents': this.onTableContents,
                    'links:update': this.onTableContentsUpdate,
                    'links:notes': this.onNotesClick,
                    'links:hyperlink': this.onHyperlinkClick,
                    'links:bookmarks': this.onBookmarksClick
                },
                'DocumentHolder': {
                    'links:contents': this.onTableContents,
                    'links:update': this.onTableContentsUpdate
                }
            });
        },
        onLaunch: function () {
            this._state = {
                prcontrolsdisable:undefined
            };
        },

        setApi: function (api) {
            if (api) {
                this.api = api;
                this.api.asc_registerCallback('asc_onFocusObject', this.onApiFocusObject.bind(this));
                this.api.asc_registerCallback('asc_onCanAddHyperlink',      _.bind(this.onApiCanAddHyperlink, this));
                this.api.asc_registerCallback('asc_onCoAuthoringDisconnect',_.bind(this.onCoAuthoringDisconnect, this));
                Common.NotificationCenter.on('api:disconnect', _.bind(this.onCoAuthoringDisconnect, this));
                this.api.asc_registerCallback('asc_onShowContentControlsActions',_.bind(this.onShowContentControlsActions, this));
                this.api.asc_registerCallback('asc_onHideContentControlsActions',_.bind(this.onHideContentControlsActions, this));

            }
            return this;
        },

        setConfig: function(config) {
            this.toolbar = config.toolbar;
            this.view = this.createView('Links', {
                toolbar: this.toolbar.toolbar
            });
        },

        SetDisabled: function(state) {
            this.view && this.view.SetDisabled(state);
        },

        getView: function(name) {
            return !name && this.view ?
                this.view : Backbone.Controller.prototype.getView.call(this, name);
        },

        onCoAuthoringDisconnect: function() {
            this.SetDisabled(true);
        },

        onApiFocusObject: function(selectedObjects) {
            if (!this.toolbar.editMode) return;

            var pr, i = -1, type,
                paragraph_locked = false,
                header_locked = false,
                in_header = false,
                in_equation = false,
                in_image = false;

            while (++i < selectedObjects.length) {
                type = selectedObjects[i].get_ObjectType();
                pr   = selectedObjects[i].get_ObjectValue();

                if (type === Asc.c_oAscTypeSelectElement.Paragraph) {
                    paragraph_locked = pr.get_Locked();
                } else if (type === Asc.c_oAscTypeSelectElement.Header) {
                    header_locked = pr.get_Locked();
                    in_header = true;
                } else if (type === Asc.c_oAscTypeSelectElement.Image) {
                    in_image = true;
                } else if (type === Asc.c_oAscTypeSelectElement.Math) {
                    in_equation = true;
                }
            }

            this._state.prcontrolsdisable = paragraph_locked || header_locked;

            var control_props = this.api.asc_IsContentControl() ? this.api.asc_GetContentControlProperties() : null,
                control_plain = (control_props) ? (control_props.get_ContentControlType()==Asc.c_oAscSdtLevelType.Inline) : false;

            var need_disable = paragraph_locked || in_equation || in_image || in_header || control_plain;
            _.each (this.view.btnsNotes, function(item){
                item.setDisabled(need_disable);
            }, this);

            need_disable = paragraph_locked || header_locked || in_header || control_plain;
            this.view.btnBookmarks.setDisabled(need_disable);
        },

        onApiCanAddHyperlink: function(value) {
            var need_disable = !value || this._state.prcontrolsdisable;

            if ( this.toolbar.editMode ) {
                _.each (this.view.btnsHyperlink, function(item){
                    item.setDisabled(need_disable);
                }, this);
            }
        },

        onHyperlinkClick: function(btn) {
            var me = this,
                win, props, text;

            if (me.api){

                var handlerDlg = function(dlg, result) {
                    if (result == 'ok') {
                        props = dlg.getSettings();
                        (text!==false)
                            ? me.api.add_Hyperlink(props)
                            : me.api.change_Hyperlink(props);
                    }

                    Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                };

                text = me.api.can_AddHyperlink();

                if (text !== false) {
                    win = new DE.Views.HyperlinkSettingsDialog({
                        api: me.api,
                        handler: handlerDlg
                    });

                    props = new Asc.CHyperlinkProperty();
                    props.put_Text(text);

                    win.show();
                    win.setSettings(props);
                } else {
                    var selectedElements = me.api.getSelectedElements();
                    if (selectedElements && _.isArray(selectedElements)){
                        _.each(selectedElements, function(el, i) {
                            if (selectedElements[i].get_ObjectType() == Asc.c_oAscTypeSelectElement.Hyperlink)
                                props = selectedElements[i].get_ObjectValue();
                        });
                    }
                    if (props) {
                        win = new DE.Views.HyperlinkSettingsDialog({
                            api: me.api,
                            handler: handlerDlg
                        });
                        win.show();
                        win.setSettings(props);
                    }
                }
            }

            Common.component.Analytics.trackEvent('ToolBar', 'Add Hyperlink');
        },

        onTableContents: function(type, currentTOC){
            currentTOC = !!currentTOC;
            var props = this.api.asc_GetTableOfContentsPr(currentTOC);
            switch (type) {
                case 0:
                    if (!props) {
                        props = new Asc.CTableOfContentsPr();
                        props.put_OutlineRange(1, 9);
                    }
                    props.put_Hyperlink(true);
                    props.put_ShowPageNumbers(true);
                    props.put_RightAlignTab(true);
                    props.put_TabLeader( Asc.c_oAscTabLeader.Dot);
                    (currentTOC) ? this.api.asc_SetTableOfContentsPr(props) : this.api.asc_AddTableOfContents(null, props);
                    break;
                case 1:
                    if (!props) {
                        props = new Asc.CTableOfContentsPr();
                        props.put_OutlineRange(1, 9);
                    }
                    props.put_Hyperlink(true);
                    props.put_ShowPageNumbers(false);
                    props.put_TabLeader( Asc.c_oAscTabLeader.None);
                    (currentTOC) ? this.api.asc_SetTableOfContentsPr(props) : this.api.asc_AddTableOfContents(null, props);
                    break;
                case 'settings':
                    var me = this,
                        win = new DE.Views.TableOfContentsSettings({
                        api: this.api,
                        props: props,
                        handler: function(result, value) {
                            if (result == 'ok') {
                                (props) ? me.api.asc_SetTableOfContentsPr(value) : me.api.asc_AddTableOfContents(null, value);
                            }
                            Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                        }
                    });
                    win.show();
                    break;
                case 'remove':
                    currentTOC = (currentTOC && props) ? props.get_InternalClass() : undefined;
                    this.api.asc_RemoveTableOfContents(currentTOC);
                    break;
            }
            Common.NotificationCenter.trigger('edit:complete', this.toolbar);
        },

        onTableContentsUpdate: function(type, currentTOC){
            var props = this.api.asc_GetTableOfContentsPr(currentTOC);
            if (props) {
                if (currentTOC && props)
                    currentTOC = props.get_InternalClass();
                this.api.asc_UpdateTableOfContents(type == 'pages', currentTOC);
            }
            Common.NotificationCenter.trigger('edit:complete', this.toolbar);
        },

        onNotesClick: function(type) {
            var me = this;
            switch (type) {
                case 'ins_footnote':
                    this.api.asc_AddFootnote();
                    break;
                case 'delele':
                    Common.UI.warning({
                        msg: this.view.confirmDeleteFootnotes,
                        buttons: ['yes', 'no'],
                        primary: 'yes',
                        callback: _.bind(function (btn) {
                            if (btn == 'yes') {
                                this.api.asc_RemoveAllFootnotes();
                            }
                            Common.NotificationCenter.trigger('edit:complete', this.toolbar);
                        }, this)
                    });
                    break;
                case 'settings':
                    (new DE.Views.NoteSettingsDialog({
                        api: me.api,
                        handler: function (result, settings) {
                            if (settings) {
                                me.api.asc_SetFootnoteProps(settings.props, settings.applyToAll);
                                if (result == 'insert')
                                    me.api.asc_AddFootnote(settings.custom);
                            }
                            Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                        },
                        props: me.api.asc_GetFootnoteProps()
                    })).show();
                    break;
                case 'prev':
                    this.api.asc_GotoFootnote(false);
                    setTimeout(function() {
                        Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                    }, 50);
                    break;
                case 'next':
                    this.api.asc_GotoFootnote(true);
                    setTimeout(function() {
                        Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                    }, 50);
                    break;
            }
        },

        onBookmarksClick: function(btn) {
            var me = this;
            (new DE.Views.BookmarksDialog({
                api: me.api,
                props: me.api.asc_GetBookmarksManager(),
                handler: function (result, settings) {
                    if (settings) {
                    }
                    Common.NotificationCenter.trigger('edit:complete', me.toolbar);
                }
            })).show();
        },

        onShowContentControlsActions: function(action, x, y) {
            var menu = (action==1) ? this.view.contentsUpdateMenu : this.view.contentsMenu,
                documentHolderView  = this.getApplication().getController('DocumentHolder').documentHolder,
                menuContainer = documentHolderView.cmpEl.find(Common.Utils.String.format('#menu-container-{0}', menu.id)),
                me = this;

            if (!menu) return;
            this._fromShowContentControls = true;
            Common.UI.Menu.Manager.hideAll();

            if (!menu.rendered) {
                // Prepare menu container
                if (menuContainer.length < 1) {
                    menuContainer = $(Common.Utils.String.format('

', menu.id));
                    documentHolderView.cmpEl.append(menuContainer);
                }

                menu.render(menuContainer);
                menu.cmpEl.attr({tabindex: "-1"});
                menu.on('hide:after', function(){
                    if (!me._fromShowContentControls)
                        me.api.asc_UncheckContentControlButtons();
                });
            }

            menuContainer.css({left: x, top : y});
            menuContainer.attr('data-value', 'prevent-canvas-click');
            documentHolderView._preventClick = true;
            menu.show();

            menu.alignPosition();
            _.delay(function() {
                menu.cmpEl.focus();
            }, 10);
            this._fromShowContentControls = false;
        },

        onHideContentControlsActions: function() {
            this.view.contentsMenu && this.view.contentsMenu.hide();
            this.view.contentsUpdateMenu && this.view.contentsUpdateMenu.hide();
        }

    }, DE.Controllers.Links || {}));
});<|endoftext|>


<commit_before>define([
    'jquery',
    'underscore',
    'knockout',
    'moment',
    'views/base-manager',
    'viewmodels/alert',
    'models/graph',
    'models/report',
    'viewmodels/card',
    'viewmodels/provisional-tile',
    'arches',
    'resource-editor-data',
    'report-templates',
    'bindings/resizable-sidepanel',
    'bindings/sortable',
    'widgets',
    'card-components',
    'views/resource/related-resources-manager'
], function($, _, ko, moment, BaseManagerView, AlertViewModel, GraphModel, ReportModel, CardViewModel, ProvisionalTileViewModel, arches, data, reportLookup) {
    var handlers = {
        'after-update': [],
        'tile-reset': []
    };
    var tiles = data.tiles;
    var filter = ko.observable('');
    var loading = ko.observable(false);
    var selection = ko.observable('root');
    var scrollTo = ko.observable();
    var displayname = ko.observable(data.displayname);
    var resourceId = ko.observable(data.resourceid);
    var appliedFunctions = ko.observable(data['appliedFunctions']);
    var selectedTile = ko.computed(function() {
        var item = selection();
        if (item && typeof item !== 'string') {
            if (item.tileid) {
                return item;
            }
            return item.getNewTile();
        }
    });
    var addableCards = ko.computed(function() {
        var items = [];
        if (selectedTile()) {
            _.each(selectedTile().cards, function(card) {
                if (card && card.canAdd()) {
                    items.push(card);
                }
            });
            return items;
        }
    });
    var provisionalTileViewModel = new ProvisionalTileViewModel({tile: selectedTile, reviewer: data.user_is_reviewer});

    var flattenTree = function(parents, flatList) {
        _.each(ko.unwrap(parents), function(parent) {
            flatList.push(parent);
            var childrenKey = parent.tiles ? 'tiles' : 'cards';
            flattenTree(
                ko.unwrap(parent[childrenKey]),
                flatList
            );
        });
        return flatList;
    };

    var toggleAll = function(state) {
        var nodes = flattenTree(vm.topCards, []);
        _.each(nodes, function(node) {
            node.expanded(state);
        });
        if (state) {
            vm.rootExpanded(true);
        }
    };
    var createLookup = function(list, idKey) {
        return _.reduce(list, function(lookup, item) {
            lookup[item[idKey]] = item;
            return lookup;
        }, {});
    };

    var graphModel = new GraphModel({
        data: {nodes: data.nodes, nodegroups: data.nodegroups, edges: []},
        datatypes: data.datatypes
    });

    var topCards = _.filter(data.cards, function(card) {
        var nodegroup = _.find(data.nodegroups, function(group) {
            return group.nodegroupid === card.nodegroup_id;
        });
        return !nodegroup || !nodegroup.parentnodegroup_id;
    }).map(function(card) {
        return new CardViewModel({
            card: card,
            graphModel: graphModel,
            tile: null,
            resourceId: resourceId,
            displayname: displayname,
            handlers: handlers,
            cards: data.cards,
            tiles: tiles,
            appliedFunctions: appliedFunctions(),
            selection: selection,
            scrollTo: scrollTo,
            loading: loading,
            filter: filter,
            provisionalTileViewModel: provisionalTileViewModel,
            cardwidgets: data.cardwidgets,
            userisreviewer: data.userisreviewer
        });
    });

    topCards.forEach(function(topCard) {
        topCard.topCards = topCards;
    });

    var vm = {
        loading: loading,
        scrollTo: scrollTo,
        filterEnterKeyHandler: function(context, e) {
            if (e.keyCode === 13) {
                var highlightedItems = _.filter(flattenTree(vm.topCards, []), function(item) {
                    return item.highlight && item.highlight();
                });
                var previousItem = scrollTo();
                scrollTo(null);
                if (highlightedItems.length > 0) {
                    var scrollIndex = 0;
                    var previousIndex = highlightedItems.indexOf(previousItem);
                    if (previousItem && highlightedItems[previousIndex+1]) {
                        scrollIndex = previousIndex + 1;
                    }
                    scrollTo(highlightedItems[scrollIndex]);
                }
                return false;
            }
            return true;
        },
        widgetLookup: createLookup(data.widgets, 'widgetid'),
        cardComponentLookup: createLookup(data.cardComponents, 'componentid'),
        nodeLookup: createLookup(graphModel.get('nodes')(), 'nodeid'),
        graphid: data.graphid,
        graphname: data.graphname,
        issystemsettings: data.issystemsettings,
        reviewer: data.userisreviewer,
        graphiconclass: data.graphiconclass,
        relationship_types: data.relationship_types,
        // appliedFunctions: appliedFunctions(),
        graph: {
            graphid: data.graphid,
            name: data.graphname,
            iconclass: data.graphiconclass,
            ontologyclass: data.ontologyclass
        },
        displayname: displayname,
        expandAll: function() {
            toggleAll(true);
        },
        collapseAll: function() {
            toggleAll(false);
        },
        rootExpanded: ko.observable(true),
        topCards: topCards,
        selection: selection,
        selectedTile: selectedTile,
        selectedCard: ko.computed(function() {
            var item = selection();
            if (item && typeof item !== 'string') {
                if (item.tileid) {
                    return item.parent;
                }
                return item;
            }
        }),
        addableCards: ko.computed(function() {
            var tile = selectedTile();
            return _.filter(tile ? tile.cards : [], function(card) {
                return card.canAdd();
            });
        }),
        provisionalTileViewModel: provisionalTileViewModel,
        filter: filter,
        on: function(eventName, handler) {
            if (handlers[eventName]) {
                handlers[eventName].push(handler);
            }
        },
        resourceId: resourceId,
        reportLookup: reportLookup,
        copyResource: function() {
            if (resourceId()) {
                vm.menuActive(false);
                loading(true);
                $.ajax({
                    type: "GET",
                    url: arches.urls.resource_copy.replace('//', '/' + resourceId() + '/'),
                    success: function() {
                        vm.alert(new AlertViewModel('ep-alert-blue', arches.resourceCopySuccess.title, '', null, function(){}));
                    },
                    error: function() {
                        vm.alert(new AlertViewModel('ep-alert-red', arches.resourceCopyFailed.title, arches.resourceCopyFailed.text, null, function(){}));
                    },
                    complete: function() {
                        loading(false);
                    },
                });
            }
        },
        deleteResource: function() {
            if (resourceId()) {
                vm.menuActive(false);
                vm.alert(new AlertViewModel('ep-alert-red', arches.confirmResourceDelete.title, arches.confirmResourceDelete.text, function() {
                    return;
                }, function(){
                    loading(true);
                    $.ajax({
                        type: "DELETE",
                        url: arches.urls.resource_editor + resourceId(),
                        error: function(err) {
                            vm.alert(new AlertViewModel('ep-alert-red', err.responseJSON.title, err.responseJSON.message, null, function(){}));
                        },
                        complete: function(request, status) {
                            loading(false);
                            if (status === 'success') {
                                vm.navigate(arches.urls.resource);
                            }
                        },
                    });
                }));
            }
        },
        deleteTile: function(tile) {
            tile.deleteTile(function(response) {
                vm.alert(new AlertViewModel('ep-alert-red', response.responseJSON.message[0], response.responseJSON.message[1], null, function(){}));
            });
        },
        saveTile: function(tile, callback) {
            tile.save(function(response) {
                console.log(response);
                vm.alert(new AlertViewModel('ep-alert-red', response.responseJSON.message[0], response.responseJSON.message[1], null, function(){}));
            }, callback);
        },
        viewEditHistory: function() {
            if (resourceId()) {
                vm.menuActive(false);
                vm.navigate(arches.urls.get_resource_edit_log(resourceId()));
            }
        },
        viewReport: function(print) {
            if (resourceId()) {
                var url = arches.urls.resource_report + resourceId();
                if (print) {
                    url = url + '?print';
                }
                vm.menuActive(false);
                window.open(url, "_blank");
            }
        }
    };

    vm.selectedTile.subscribe(function() {
        $('.main-panel')[0].scrollTop = 0;
    });

    vm.report = null;
    vm.report = new ReportModel(_.extend(data, {graphModel: graphModel, cards: vm.topCards}));

    vm.resourceId.subscribe(function(){
        //switches the url from 'create-resource' once the resource id is available
        history.pushState({}, '', arches.urls.resource_editor + resourceId());
    });

    vm.showRelatedResourcesManager = function(){
        if (vm.graph.domain_connections == undefined) {
            $.ajax({
                url: arches.urls.relatable_resources,
                data: {graphid: vm.graphid}
            }).done(function(relatable){
                vm.graph.relatable_resources = relatable;
                $.ajax({
                    url: arches.urls.get_domain_connections(vm.graphid),
                    data: {"ontology_class": vm.graph.ontologyclass}
                }).done(function(data){
                    vm.graph.domain_connections = data;
                    vm.relatedResourcesManagerObj = {
                        searchResultsVm: undefined,
                        resourceEditorContext: true,
                        editing_instance_id: vm.resourceId(),
                        relationship_types: vm.relationship_types,
                        graph: vm.graph
                    };
                    vm.selection('related-resources');
                });
            });

        } else {
            vm.selection('related-resources');
        }
    };

    vm.selectionBreadcrumbs = ko.computed(function() {
        var item = vm.selectedTile();
        var crumbs = [];
        if (item) {
            while (item.parent) {
                item = item.parent;
                crumbs.unshift(item);
            }
        }
        return crumbs;
    });

    return new BaseManagerView({
        viewModel: vm
    });
});
<commit_msg>removes console statement in new-editor.js, re #4937<commit_after>define([
    'jquery',
    'underscore',
    'knockout',
    'moment',
    'views/base-manager',
    'viewmodels/alert',
    'models/graph',
    'models/report',
    'viewmodels/card',
    'viewmodels/provisional-tile',
    'arches',
    'resource-editor-data',
    'report-templates',
    'bindings/resizable-sidepanel',
    'bindings/sortable',
    'widgets',
    'card-components',
    'views/resource/related-resources-manager'
], function($, _, ko, moment, BaseManagerView, AlertViewModel, GraphModel, ReportModel, CardViewModel, ProvisionalTileViewModel, arches, data, reportLookup) {
    var handlers = {
        'after-update': [],
        'tile-reset': []
    };
    var tiles = data.tiles;
    var filter = ko.observable('');
    var loading = ko.observable(false);
    var selection = ko.observable('root');
    var scrollTo = ko.observable();
    var displayname = ko.observable(data.displayname);
    var resourceId = ko.observable(data.resourceid);
    var appliedFunctions = ko.observable(data['appliedFunctions']);
    var selectedTile = ko.computed(function() {
        var item = selection();
        if (item && typeof item !== 'string') {
            if (item.tileid) {
                return item;
            }
            return item.getNewTile();
        }
    });
    var addableCards = ko.computed(function() {
        var items = [];
        if (selectedTile()) {
            _.each(selectedTile().cards, function(card) {
                if (card && card.canAdd()) {
                    items.push(card);
                }
            });
            return items;
        }
    });
    var provisionalTileViewModel = new ProvisionalTileViewModel({tile: selectedTile, reviewer: data.user_is_reviewer});

    var flattenTree = function(parents, flatList) {
        _.each(ko.unwrap(parents), function(parent) {
            flatList.push(parent);
            var childrenKey = parent.tiles ? 'tiles' : 'cards';
            flattenTree(
                ko.unwrap(parent[childrenKey]),
                flatList
            );
        });
        return flatList;
    };

    var toggleAll = function(state) {
        var nodes = flattenTree(vm.topCards, []);
        _.each(nodes, function(node) {
            node.expanded(state);
        });
        if (state) {
            vm.rootExpanded(true);
        }
    };
    var createLookup = function(list, idKey) {
        return _.reduce(list, function(lookup, item) {
            lookup[item[idKey]] = item;
            return lookup;
        }, {});
    };

    var graphModel = new GraphModel({
        data: {nodes: data.nodes, nodegroups: data.nodegroups, edges: []},
        datatypes: data.datatypes
    });

    var topCards = _.filter(data.cards, function(card) {
        var nodegroup = _.find(data.nodegroups, function(group) {
            return group.nodegroupid === card.nodegroup_id;
        });
        return !nodegroup || !nodegroup.parentnodegroup_id;
    }).map(function(card) {
        return new CardViewModel({
            card: card,
            graphModel: graphModel,
            tile: null,
            resourceId: resourceId,
            displayname: displayname,
            handlers: handlers,
            cards: data.cards,
            tiles: tiles,
            appliedFunctions: appliedFunctions(),
            selection: selection,
            scrollTo: scrollTo,
            loading: loading,
            filter: filter,
            provisionalTileViewModel: provisionalTileViewModel,
            cardwidgets: data.cardwidgets,
            userisreviewer: data.userisreviewer
        });
    });

    topCards.forEach(function(topCard) {
        topCard.topCards = topCards;
    });

    var vm = {
        loading: loading,
        scrollTo: scrollTo,
        filterEnterKeyHandler: function(context, e) {
            if (e.keyCode === 13) {
                var highlightedItems = _.filter(flattenTree(vm.topCards, []), function(item) {
                    return item.highlight && item.highlight();
                });
                var previousItem = scrollTo();
                scrollTo(null);
                if (highlightedItems.length > 0) {
                    var scrollIndex = 0;
                    var previousIndex = highlightedItems.indexOf(previousItem);
                    if (previousItem && highlightedItems[previousIndex+1]) {
                        scrollIndex = previousIndex + 1;
                    }
                    scrollTo(highlightedItems[scrollIndex]);
                }
                return false;
            }
            return true;
        },
        widgetLookup: createLookup(data.widgets, 'widgetid'),
        cardComponentLookup: createLookup(data.cardComponents, 'componentid'),
        nodeLookup: createLookup(graphModel.get('nodes')(), 'nodeid'),
        graphid: data.graphid,
        graphname: data.graphname,
        issystemsettings: data.issystemsettings,
        reviewer: data.userisreviewer,
        graphiconclass: data.graphiconclass,
        relationship_types: data.relationship_types,
        // appliedFunctions: appliedFunctions(),
        graph: {
            graphid: data.graphid,
            name: data.graphname,
            iconclass: data.graphiconclass,
            ontologyclass: data.ontologyclass
        },
        displayname: displayname,
        expandAll: function() {
            toggleAll(true);
        },
        collapseAll: function() {
            toggleAll(false);
        },
        rootExpanded: ko.observable(true),
        topCards: topCards,
        selection: selection,
        selectedTile: selectedTile,
        selectedCard: ko.computed(function() {
            var item = selection();
            if (item && typeof item !== 'string') {
                if (item.tileid) {
                    return item.parent;
                }
                return item;
            }
        }),
        addableCards: ko.computed(function() {
            var tile = selectedTile();
            return _.filter(tile ? tile.cards : [], function(card) {
                return card.canAdd();
            });
        }),
        provisionalTileViewModel: provisionalTileViewModel,
        filter: filter,
        on: function(eventName, handler) {
            if (handlers[eventName]) {
                handlers[eventName].push(handler);
            }
        },
        resourceId: resourceId,
        reportLookup: reportLookup,
        copyResource: function() {
            if (resourceId()) {
                vm.menuActive(false);
                loading(true);
                $.ajax({
                    type: "GET",
                    url: arches.urls.resource_copy.replace('//', '/' + resourceId() + '/'),
                    success: function() {
                        vm.alert(new AlertViewModel('ep-alert-blue', arches.resourceCopySuccess.title, '', null, function(){}));
                    },
                    error: function() {
                        vm.alert(new AlertViewModel('ep-alert-red', arches.resourceCopyFailed.title, arches.resourceCopyFailed.text, null, function(){}));
                    },
                    complete: function() {
                        loading(false);
                    },
                });
            }
        },
        deleteResource: function() {
            if (resourceId()) {
                vm.menuActive(false);
                vm.alert(new AlertViewModel('ep-alert-red', arches.confirmResourceDelete.title, arches.confirmResourceDelete.text, function() {
                    return;
                }, function(){
                    loading(true);
                    $.ajax({
                        type: "DELETE",
                        url: arches.urls.resource_editor + resourceId(),
                        error: function(err) {
                            vm.alert(new AlertViewModel('ep-alert-red', err.responseJSON.title, err.responseJSON.message, null, function(){}));
                        },
                        complete: function(request, status) {
                            loading(false);
                            if (status === 'success') {
                                vm.navigate(arches.urls.resource);
                            }
                        },
                    });
                }));
            }
        },
        deleteTile: function(tile) {
            tile.deleteTile(function(response) {
                vm.alert(new AlertViewModel('ep-alert-red', response.responseJSON.message[0], response.responseJSON.message[1], null, function(){}));
            });
        },
        saveTile: function(tile, callback) {
            tile.save(function(response) {
                vm.alert(new AlertViewModel('ep-alert-red', response.responseJSON.message[0], response.responseJSON.message[1], null, function(){}));
            }, callback);
        },
        viewEditHistory: function() {
            if (resourceId()) {
                vm.menuActive(false);
                vm.navigate(arches.urls.get_resource_edit_log(resourceId()));
            }
        },
        viewReport: function(print) {
            if (resourceId()) {
                var url = arches.urls.resource_report + resourceId();
                if (print) {
                    url = url + '?print';
                }
                vm.menuActive(false);
                window.open(url, "_blank");
            }
        }
    };

    vm.selectedTile.subscribe(function() {
        $('.main-panel')[0].scrollTop = 0;
    });

    vm.report = null;
    vm.report = new ReportModel(_.extend(data, {graphModel: graphModel, cards: vm.topCards}));

    vm.resourceId.subscribe(function(){
        //switches the url from 'create-resource' once the resource id is available
        history.pushState({}, '', arches.urls.resource_editor + resourceId());
    });

    vm.showRelatedResourcesManager = function(){
        if (vm.graph.domain_connections == undefined) {
            $.ajax({
                url: arches.urls.relatable_resources,
                data: {graphid: vm.graphid}
            }).done(function(relatable){
                vm.graph.relatable_resources = relatable;
                $.ajax({
                    url: arches.urls.get_domain_connections(vm.graphid),
                    data: {"ontology_class": vm.graph.ontologyclass}
                }).done(function(data){
                    vm.graph.domain_connections = data;
                    vm.relatedResourcesManagerObj = {
                        searchResultsVm: undefined,
                        resourceEditorContext: true,
                        editing_instance_id: vm.resourceId(),
                        relationship_types: vm.relationship_types,
                        graph: vm.graph
                    };
                    vm.selection('related-resources');
                });
            });

        } else {
            vm.selection('related-resources');
        }
    };

    vm.selectionBreadcrumbs = ko.computed(function() {
        var item = vm.selectedTile();
        var crumbs = [];
        if (item) {
            while (item.parent) {
                item = item.parent;
                crumbs.unshift(item);
            }
        }
        return crumbs;
    });

    return new BaseManagerView({
        viewModel: vm
    });
});
<|endoftext|>


<commit_before>/**
 * Copyright 2017 The AMP HTML 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.
 */

class FeedReader {

  constructor() {

  }

  fetch(category, attempts = 0) {

    let rssUrl = shadowReader.backend.getRSSUrl(category);
    let feedUrl = '/feed?q=' + rssUrl;

    return fetch(feedUrl)
      .then(response => response.json() )
      .then(json => {

        // sadly, the Guardian's RSS feeds seem to be having intermittent failures right now,
        // so rerequest if that happens
        if(!json.rss && attempts < 10) {
          return this.fetch(category, (attempts || 0) + 1);
        }

        var entries = (json.rss.channel && json.rss.channel.item) || [];

        return entries.map(entry => {
          return {
            title: shadowReader.backend.getRSSTitle(entry),
            description: shadowReader.backend.getRSSDescription(entry),
            link: entry.link,
            image: shadowReader.backend.getRSSImage(entry)
          };
        });

      });

  }

}<commit_msg>Limit the number of article cards loaded<commit_after>/**
 * Copyright 2017 The AMP HTML 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.
 */

class FeedReader {

  constructor() {
    this.cardLimit = 15; // load no more than this many article cards at once
  }

  fetch(category, attempts = 0) {

    let rssUrl = shadowReader.backend.getRSSUrl(category);
    let feedUrl = '/feed?q=' + rssUrl;

    return fetch(feedUrl)
      .then(response => response.json() )
      .then(json => {

        // sadly, the Guardian's RSS feeds seem to be having intermittent failures right now,
        // so rerequest if that happens
        if(!json.rss && attempts < 10) {
          return this.fetch(category, (attempts || 0) + 1);
        }

        var entries = (json.rss.channel && json.rss.channel.item) || [];
        entries = entries.slice(0, this.cardLimit);

        return entries.map(entry => {
          return {
            title: shadowReader.backend.getRSSTitle(entry),
            description: shadowReader.backend.getRSSDescription(entry),
            link: entry.link,
            image: shadowReader.backend.getRSSImage(entry)
          };
        });

      });

  }

}<|endoftext|>