<commit_before>/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var angular = require('camunda-commons-ui/vendor/angular');

module.exports = [
  '$scope',
  '$q',
  '$location',
  '$interval',
  'search',
  'dataDepend',
  'camAPI',
  function(
    $scope,
    $q,
    $location,
    $interval,
    search,
    dataDepend,
    camAPI
  ) {

    function getPropertyFromLocation(property) {
      var search = $location.search() || {};
      return search[property] || null;
    }

    function updateSilently(params) {
      search.updateSilently(params);
    }

    // init data depend for task list data
    var tasklistData = $scope.tasklistData = dataDepend.create($scope);
    // get current task id from location
    var taskId = getPropertyFromLocation('task');
    var detailsTab = getPropertyFromLocation('detailsTab');

    // resources
    var Filter = camAPI.resource('filter');
    var Task = camAPI.resource('task');

    // current selected filter
    var currentFilter;

    // provide /////////////////////////////////////////////////////////////////////////////////

    /**
     * Provides the list of filters
     */
    tasklistData.provide('filters', [ function() {
      var deferred = $q.defer();

      Filter.list({
        itemCount: false,
        resoureType: 'Task'
      }, function(err, res) {
        if(err) {
          deferred.reject(err);

        }
        else {
          deferred.resolve(res);
        }
      });

      return deferred.promise;
    }]);

    tasklistData.provide('currentFilter', ['filters', function(filters) {

      var focused,
          filterId = getPropertyFromLocation('filter');

      for (var i = 0, filter; (filter = filters[i]); i++) {

        if (filterId === filter.id) {
          focused = filter;
          break;
        }
        // auto focus first filter
        if(!focused || filter.properties.priority < focused.properties.priority) {
          focused = filter;
        }
      }

      if(currentFilter && focused && currentFilter.id !== focused.id) {
        var currentPage = getPropertyFromLocation('page');
        if (currentPage) {
          updateSilently({
            page: '1'
          });
        }
      }

      if(focused && focused.id !== filterId) {
        updateSilently({
          filter: focused.id
        });
      }

      return angular.copy(focused);

    }]);

    tasklistData.provide('searchQuery', {
      processVariables: [],
      taskVariables: [],
      caseInstanceVariables: []
    });

    tasklistData.provide('taskListQuery', ['currentFilter', 'searchQuery', function(currentFilter, searchQuery) {
      if (!currentFilter) {
        return null;
      }

      var taskListQuery = angular.copy(searchQuery);

      var firstResult = ((getPropertyFromLocation('page') || 1) - 1) * 15;
      var sorting = getPropertyFromLocation('sorting');
      try {
        sorting = JSON.parse(sorting);
      }
      catch (err) {
        sorting = [{}];
      }
      sorting = (Array.isArray(sorting) && sorting.length) ? sorting : [{}];
      sorting[0].sortOrder = sorting[0].sortOrder || 'desc';
      sorting[0].sortBy = sorting[0].sortBy || 'created';

      taskListQuery.id = currentFilter.id;
      taskListQuery.firstResult = firstResult;
      taskListQuery.maxResults = 15;
      taskListQuery.sorting = sorting;
      taskListQuery.active = true;

      return taskListQuery;

    }]);

    /**
      * Provide the list of tasks
      */
    tasklistData.provide('taskList', [ 'taskListQuery', function(taskListQuery) {
      var deferred = $q.defer();

      if(!taskListQuery || taskListQuery.id === null) {
        // no filter selected
        deferred.resolve({
          count: 0,
          _embedded : {}
        });
      }
      else {
        // filter selected
        Filter.getTasks(angular.copy(taskListQuery), function(err, res) {
          if(err) {
            deferred.reject(err);
          }
          else {
            deferred.resolve(res);
          }
        });
      }
      return deferred.promise;
    }]);

    /**
     * Provide current task id
     */
    tasklistData.provide('taskId', { 'taskId' : taskId });

    /**
     * Provide the current task or the value 'null' in case no task is selected
     */
    tasklistData.provide('task', ['taskId', function(task) {

      var deferred = $q.defer();

      var taskId = task.taskId;

      if(typeof taskId !== 'string') {
        deferred.resolve(null);
      }
      else {
        Task.get(taskId, function(err, res) {
          if(err) {
            deferred.reject(err);
          }
          else {
            deferred.resolve(res);
          }

        });
      }

      return deferred.promise;
    }]);

    // observe //////////////////////////////////////////////////////////////////////////////

    tasklistData.observe('currentFilter', function(_currentFilter) {
      currentFilter = _currentFilter;
    });

    /*
     * automatically refresh the taskList every 10 seconds so that changes
     * (such as claims) are represented in realtime
     */
    $scope.$on('refresh', function() {
      if (!currentFilter || !currentFilter.properties.refresh) return;
      tasklistData.changed('taskList');
    });

    // routeChanged listener ////////////////////////////////////////////////////////////////

    /**
     * Update task if location changes
     */
    $scope.$on('$routeChanged', function() {
      var oldTaskId = taskId;
      var oldDetailsTab = detailsTab;

      taskId = getPropertyFromLocation('task');
      detailsTab = getPropertyFromLocation('detailsTab');

      if (oldTaskId !== taskId || oldDetailsTab === detailsTab) {
        tasklistData.set('taskId', { 'taskId' : taskId });
      }

      currentFilter = null;
      tasklistData.changed('currentFilter');
    });
  }];
<commit_msg>fix(tasklist): wait for last auto-refresh to complete before sending again<commit_after>/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
 * under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership. Camunda licenses this file to you under the Apache License,
 * Version 2.0; you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var angular = require('camunda-commons-ui/vendor/angular');

module.exports = [
  '$scope',
  '$q',
  '$location',
  '$interval',
  'search',
  'dataDepend',
  'camAPI',
  function(
    $scope,
    $q,
    $location,
    $interval,
    search,
    dataDepend,
    camAPI
  ) {

    function getPropertyFromLocation(property) {
      var search = $location.search() || {};
      return search[property] || null;
    }

    function updateSilently(params) {
      search.updateSilently(params);
    }

    // init data depend for task list data
    var tasklistData = $scope.tasklistData = dataDepend.create($scope);
    // get current task id from location
    var taskId = getPropertyFromLocation('task');
    var detailsTab = getPropertyFromLocation('detailsTab');

    // resources
    var Filter = camAPI.resource('filter');
    var Task = camAPI.resource('task');

    // current selected filter
    var currentFilter;

    // provide /////////////////////////////////////////////////////////////////////////////////

    /**
     * Provides the list of filters
     */
    tasklistData.provide('filters', [ function() {
      var deferred = $q.defer();

      Filter.list({
        itemCount: false,
        resoureType: 'Task'
      }, function(err, res) {
        if(err) {
          deferred.reject(err);

        }
        else {
          deferred.resolve(res);
        }
      });

      return deferred.promise;
    }]);

    tasklistData.provide('currentFilter', ['filters', function(filters) {

      var focused,
          filterId = getPropertyFromLocation('filter');

      for (var i = 0, filter; (filter = filters[i]); i++) {

        if (filterId === filter.id) {
          focused = filter;
          break;
        }
        // auto focus first filter
        if(!focused || filter.properties.priority < focused.properties.priority) {
          focused = filter;
        }
      }

      if(currentFilter && focused && currentFilter.id !== focused.id) {
        var currentPage = getPropertyFromLocation('page');
        if (currentPage) {
          updateSilently({
            page: '1'
          });
        }
      }

      if(focused && focused.id !== filterId) {
        updateSilently({
          filter: focused.id
        });
      }

      return angular.copy(focused);

    }]);

    tasklistData.provide('searchQuery', {
      processVariables: [],
      taskVariables: [],
      caseInstanceVariables: []
    });

    tasklistData.provide('taskListQuery', ['currentFilter', 'searchQuery', function(currentFilter, searchQuery) {
      if (!currentFilter) {
        return null;
      }

      var taskListQuery = angular.copy(searchQuery);

      var firstResult = ((getPropertyFromLocation('page') || 1) - 1) * 15;
      var sorting = getPropertyFromLocation('sorting');
      try {
        sorting = JSON.parse(sorting);
      }
      catch (err) {
        sorting = [{}];
      }
      sorting = (Array.isArray(sorting) && sorting.length) ? sorting : [{}];
      sorting[0].sortOrder = sorting[0].sortOrder || 'desc';
      sorting[0].sortBy = sorting[0].sortBy || 'created';

      taskListQuery.id = currentFilter.id;
      taskListQuery.firstResult = firstResult;
      taskListQuery.maxResults = 15;
      taskListQuery.sorting = sorting;
      taskListQuery.active = true;

      return taskListQuery;

    }]);

    /**
      * Provide the list of tasks
      */

    // Handeling of long-running requests
    // store state of last Request
    var lastRequestIsPending = false;
    var lastRequest;

    tasklistData.provide('taskList', [ 'taskListQuery', function(taskListQuery) {
      if(!lastRequestIsPending) {
        var deferred = $q.defer();
        lastRequest = deferred;
        lastRequestIsPending = true;

        if(!taskListQuery || taskListQuery.id === null) {
          // no filter selected
          lastRequestIsPending = false;
          deferred.resolve({
            count: 0,
            _embedded : {}
          });
        }
        else {
          // filter selected
          Filter.getTasks(angular.copy(taskListQuery), function(err, res) {
            lastRequestIsPending = false;
            if(err) {
              deferred.reject(err);
            }
            else {
              deferred.resolve(res);
            }
          });
        }
        return deferred.promise;
      }
      else {
        return lastRequest.promise;
      }
    }]);

    /**
     * Provide current task id
     */
    tasklistData.provide('taskId', { 'taskId' : taskId });

    /**
     * Provide the current task or the value 'null' in case no task is selected
     */
    tasklistData.provide('task', ['taskId', function(task) {

      var deferred = $q.defer();

      var taskId = task.taskId;

      if(typeof taskId !== 'string') {
        deferred.resolve(null);
      }
      else {
        Task.get(taskId, function(err, res) {
          if(err) {
            deferred.reject(err);
          }
          else {
            deferred.resolve(res);
          }

        });
      }

      return deferred.promise;
    }]);

    // observe //////////////////////////////////////////////////////////////////////////////

    tasklistData.observe('currentFilter', function(_currentFilter) {
      currentFilter = _currentFilter;
    });

    /*
     * automatically refresh the taskList every 10 seconds so that changes
     * (such as claims) are represented in realtime
     */
    $scope.$on('refresh', function() {
      if (!currentFilter || !currentFilter.properties.refresh) return;

      tasklistData.changed('taskList');
    });

    // routeChanged listener ////////////////////////////////////////////////////////////////

    /**
     * Update task if location changes
     */
    $scope.$on('$routeChanged', function() {
      var oldTaskId = taskId;
      var oldDetailsTab = detailsTab;

      taskId = getPropertyFromLocation('task');
      detailsTab = getPropertyFromLocation('detailsTab');

      if (oldTaskId !== taskId || oldDetailsTab === detailsTab) {
        tasklistData.set('taskId', { 'taskId' : taskId });
      }

      currentFilter = null;
      tasklistData.changed('currentFilter');
    });
  }];
<|endoftext|>


<commit_before>/**
 * Represents a cinema visualization. Stores the required info in the model's
 * attributes.
 */
cinema.models.VisualizationModel = Backbone.Model.extend({
    constructor: function (settings) {
        Backbone.Model.apply(this, arguments);

        this.basePath = settings.basePath;
        this.infoFile = settings.infoFile;
        this.url = this.basePath + '/' + this.infoFile;
    },

    defaults: {
    },

    loaded: function () {
        return this.has('metadata');
    },

    url: function () {
        return this.urlRoot + '/' + this.infoFile;
    },

    workbench: function () {
        return false;
    },

    getDataType: function () {
        if(this.loaded()) {
            return this.workbench() ? '' : this.get('metadata').type;
        }
        return 'no-match';
    },

    getFilePattern: function(args, ignoreList) {
        var keySet = args || {},
            result = this.get('name_pattern') || '',
            kp = ['{','}'],
            ignore = ignoreList || [];

        _.each(keySet, function(value, key) {
            if (!_.contains(ignore, key)) {
                result = result.replace(kp.join(key), value);
            }
        });

        return result;
    }
});
<commit_msg>Add more utility functions back into vis model<commit_after>/**
 * Represents a cinema visualization. Stores the required info in the model's
 * attributes.
 */
cinema.models.VisualizationModel = Backbone.Model.extend({
    constructor: function (settings) {
        Backbone.Model.apply(this, arguments);

        this.basePath = settings.basePath;
        this.infoFile = settings.infoFile;
        this.url = this.basePath + '/' + this.infoFile;
    },

    defaults: {
    },

    loaded: function () {
        return this.has('metadata');
    },

    url: function () {
        return this.urlRoot + '/' + this.infoFile;
    },

    workbench: function () {
        return false;
    },

    getDataType: function () {
        if(this.loaded()) {
            return this.workbench() ? '' : this.get('metadata').type;
        }
        return 'no-match';
    },

    getFilePattern: function(args, ignoreList) {
        var keySet = args || {},
            result = this.get('name_pattern') || '',
            kp = ['{','}'],
            ignore = ignoreList || [];

        _.each(keySet, function(value, key) {
            if (!_.contains(ignore, key)) {
                result = result.replace(kp.join(key), value);
            }
        });

        return result;
    },

    lengthPhi: function () {
        if (!_.has(this, '_lengthPhi')) {
            var args = this.get('arguments') || {};
            if (_.has(args, 'phi')) {
                this._lengthPhi = args.phi.values.length - 1;
            } else {
                this._lengthPhi = 0;
            }
        }
        return this._lengthPhi;
    },

    lengthTheta: function () {
        if (!_.has(this, '_lengthTheta')) {
            var args = this.get('arguments') || {};
            if (_.has(args, 'theta')) {
                this._lengthTheta = args.theta.values.length - 1;
            } else {
                this._lengthTheta = 0;
            }
        }
        return this._lengthTheta;
    },

    lengthTime: function () {
        if (!_.has(this, '_lengthTime')) {
            var args = this.get('arguments') || {};
            if (_.has(args, 'time')) {
                this._lengthTime = args.time.values.length - 1;
            } else {
                this._lengthTime = 0;
            }
        }
        return this._lengthTime;
    },

    /**
     * Use the info.json "name_pattern" and "arguments" keys to build an
     * internal ordered list used in mapping ordinals to relative directory
     * paths.
     */
    initializeArgArrays: function () {
        var pattern = this.get('name_pattern') || "";
        var args = this.get('arguments') || {};
        var compList = pattern.split('/');
        var re = /{(.+)}/;
        this._argArrays = [];
        this._argKeys = [];
        this._maxOrdinal = 1;
        var self = this;
        _.each(compList.slice(0, compList.length - 1), function (value, idx, list) {
            var match = re.exec(value);
            self._argKeys.push(match[1]);
            var arr = args[match[1]].values;
            self._argArrays.push(arr);
            self._maxOrdinal *= arr.length;
         });
        this._maxOrdinal -= 1;
    },

    /**
     * Convenience function to return the quotient and remainder from
     * integer division of the arguments
     */
    integerDivide: function (dividend, divisor) {
        return {
            'quo': Math.floor(dividend / divisor),
            'rem': dividend % divisor
        };
    },

    /**
     * Convert an image ordinal to an object containing the correct
     * values for each component in the info.json 'name_pattern', e.g.
     *
     *     {
     *         "time": "0",
     *         "theta": "10.0",
     *         "phi": "170.0"
     *     }
     *
     */
    ordinalToObject: function (ordinal) {
        if (!_.has(this, '_argArrays')) {
            this.initializeArgArrays();
        }

        if (ordinal > this._maxOrdinal) {
            throw "Ordinal " + ordinal + " out of range, max ordinal: " + this._maxOrdinal;
        }

        // Now proceed to integer divide the ordinal by rightmost length, save
        // remainder, then integer divide that quotient by the next
        // length to the left, save the remainder, etc... until you
        // get to the far left.
        var quotient = ordinal;
        var results = {};

        for (var i = this._argArrays.length - 1; i >= 0; i -= 1) {
            var r = this.integerDivide(quotient, this._argArrays[i].length);
            results[this._argKeys[i]] = this._argArrays[i][r.rem];
            quotient = r.quo;
        }

        return results;
    },

    /**
     * Take an object of the form:
     *
     *    {
     *        "time": "0",
     *        "theta": "10.0",
     *        "phi": "170.0"
     *    }
     *
     * And use the internal order list _argKeys to create a relative path string
     * from it.
     *
     */
    objectToPath: function (obj) {
        if (!_.has(this, '_argArrays')) {
            this.initializeArgArrays();
        }

        var result = [];
        _.each(this._argKeys, function (value, idx, list) {
            result.push(obj[value]);
        });

        return result.join('/');
    },

    imageCount: function () {
        return this.lengthPhi() * this.lengthTime() * this.lengthTheta();
    }
});
<|endoftext|>


<commit_before>$(window).load(function() {
    /**
     * tileLayout will simply tile/retile the block/pin container when run. This
     * was put into a function in order to adjust frequently on screen size
     * changes.
     */
    function tileLayout() {
        var blockContainer = $('#pins'),
            blocks = blockContainer.children('.pin'),
            blockMargin = 15,
            blockWidth = 240,
            rowSize = Math.floor(blockContainer.width()/(blockWidth+blockMargin)),
            colHeights = [],
            rowMargins = [],
            marginLeft = 0;

        // Fill our colHeights array with 0 for each row we have
        for (var i=0; i < rowSize; i++) colHeights[i] = 0;

        // Fill out our rowMargins which will be static after this
        for (var i=0; i < rowSize; i++) {
            // Our first item has a special margin to keep things centered
            if (i == 0) rowMargins[0] = (blockContainer.width()-rowSize*(blockWidth+blockMargin))/2;
            else rowMargins[i] = rowMargins[i-1]+(blockWidth+blockMargin);
        }

        // Loop through every block
        for (var b=0; b < blocks.length; b++) {
            // Get the jQuery object of the current block
            block = blocks.eq(b);

            // Position our new pin in the shortest column
            var sCol = 0;
            for (var i=0; i < rowSize; i++) {
                if (colHeights[sCol] > colHeights[i]) sCol = i;
            }

            block.css({
                'margin-left': rowMargins[sCol],
                'margin-top': colHeights[sCol],
                'display': 'block'
            });

            colHeights[sCol] += block.height()+(blockMargin);
        }

            // Delete pin if trash icon clicked
            $('.icon-trash').each(function() {
                var thisPin = $(this);
                $(this).click(function() {
                    $(this).off('click');
                    var promise = deletePinData($(this).data('id'));
                    promise.success(function() {
                        thisPin.parent().parent().parent().remove();
                        tileLayout();
                    });
                });
            });

        $('.spinner').css('display', 'none');
        blockContainer.css('height', colHeights.sort().slice(-1)[0]);
    }

    /**
     * Load our pins using the pins template into our UI, be sure to define a
     * offset outside the function to keep a running tally of your location.
     */
    function loadPins() {
        // Show our loading symbol
        $('.spinner').css('display', 'block');

        // Fetch our pins from the api using our current offset
        var apiUrl = '/api/v1/pin/?format=json&order_by=-id&offset='+String(offset);
        if (tagFilter) apiUrl = apiUrl + '&tag=' + tagFilter;
        $.get(apiUrl, function(pins) {
            // Set which items are editable by the current user
            for (var i=0; i < pins.objects.length; i++)
                pins.objects[i].editable = (pins.objects[i].submitter.username == currentUser.username);

            // Use the fetched pins as our context for our pins template
            var template = Handlebars.compile($('#pins-template').html());
            var html = template({pins: pins.objects});

            // Append the newly compiled data to our container
            $('#pins').append(html);

            // We need to then wait for images to load in and then tile
            $('#pins').ajaxStop(function() {
                $('img').load(function() {
                    tileLayout();
                    lightbox();
                });
            });

            // Up our offset, it's currently defined as 30 in our settings
            offset += apiLimitPerPage;
        });
    }

    // Set offset for loadPins and do our initial load
    var offset = 0;
    loadPins();

    // If our window gets resized keep the tiles looking clean and in our window
    $(window).resize(function() {
        tileLayout();
        lightbox();
    })

    // If we scroll to the bottom of the document load more pins
    $(window).scroll(function() {
         if($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
            loadPins();
         }
     });
});
<commit_msg>Spinner doesn't show if there are no more pins to load<commit_after>$(window).load(function() {
    /**
     * tileLayout will simply tile/retile the block/pin container when run. This
     * was put into a function in order to adjust frequently on screen size
     * changes.
     */
    function tileLayout() {
        var blockContainer = $('#pins'),
            blocks = blockContainer.children('.pin'),
            blockMargin = 15,
            blockWidth = 240,
            rowSize = Math.floor(blockContainer.width()/(blockWidth+blockMargin)),
            colHeights = [],
            rowMargins = [],
            marginLeft = 0;

        // Fill our colHeights array with 0 for each row we have
        for (var i=0; i < rowSize; i++) colHeights[i] = 0;

        // Fill out our rowMargins which will be static after this
        for (var i=0; i < rowSize; i++) {
            // Our first item has a special margin to keep things centered
            if (i == 0) rowMargins[0] = (blockContainer.width()-rowSize*(blockWidth+blockMargin))/2;
            else rowMargins[i] = rowMargins[i-1]+(blockWidth+blockMargin);
        }

        // Loop through every block
        for (var b=0; b < blocks.length; b++) {
            // Get the jQuery object of the current block
            block = blocks.eq(b);

            // Position our new pin in the shortest column
            var sCol = 0;
            for (var i=0; i < rowSize; i++) {
                if (colHeights[sCol] > colHeights[i]) sCol = i;
            }

            block.css({
                'margin-left': rowMargins[sCol],
                'margin-top': colHeights[sCol],
                'display': 'block'
            });

            colHeights[sCol] += block.height()+(blockMargin);
        }

            // Delete pin if trash icon clicked
            $('.icon-trash').each(function() {
                var thisPin = $(this);
                $(this).click(function() {
                    $(this).off('click');
                    var promise = deletePinData($(this).data('id'));
                    promise.success(function() {
                        thisPin.parent().parent().parent().remove();
                        tileLayout();
                    });
                });
            });

        $('.spinner').css('display', 'none');
        blockContainer.css('height', colHeights.sort().slice(-1)[0]);
    }

    /**
     * Load our pins using the pins template into our UI, be sure to define a
     * offset outside the function to keep a running tally of your location.
     */
    function loadPins() {
        // Show our loading symbol
        $('.spinner').css('display', 'block');

        // Fetch our pins from the api using our current offset
        var apiUrl = '/api/v1/pin/?format=json&order_by=-id&offset='+String(offset);
        if (tagFilter) apiUrl = apiUrl + '&tag=' + tagFilter;
        $.get(apiUrl, function(pins) {
            // Set which items are editable by the current user
            for (var i=0; i < pins.objects.length; i++)
                pins.objects[i].editable = (pins.objects[i].submitter.username == currentUser.username);

            // Use the fetched pins as our context for our pins template
            var template = Handlebars.compile($('#pins-template').html());
            var html = template({pins: pins.objects});

            // Append the newly compiled data to our container
            $('#pins').append(html);

            // We need to then wait for images to load in and then tile
            $('#pins').ajaxStop(function() {
                $('img').load(function() {
                    tileLayout();
                    lightbox();
                });
            });

            if (pins.meta.total_count == 0) {
                $('.spinner').css('display', 'none');
            }

            // Up our offset, it's currently defined as 30 in our settings
            offset += apiLimitPerPage;
        });
    }

    // Set offset for loadPins and do our initial load
    var offset = 0;
    loadPins();

    // If our window gets resized keep the tiles looking clean and in our window
    $(window).resize(function() {
        tileLayout();
        lightbox();
    })

    // If we scroll to the bottom of the document load more pins
    $(window).scroll(function() {
         if($(window).scrollTop() + $(window).height() > $(document).height() - 100) {
            loadPins();
         }
     });
});
<|endoftext|>


<commit_before>var _ = require('lodash');

var commentsFinder = require('./commentsFinder');
var kssCommentsParser = require('./kssCommentsParser');
var assembleHandlbarsHelpers = [
    require('./handlebars-helpers/styleguideHelpers.js'),
    require('./handlebars-helpers/markupHelpers.js'),
    require('./handlebars-helpers/teaserHelpers.js')
];

/**
 * sort sections object by weight
 * @param dict: sections object
 * @return sorted sections object
 * */
function sortOnWeight(dict) {

    var tempDict = {};
    var sorted = [];
    var weight = 0;

    for (var key in dict) {

        var section = dict[key];
        var sectionWeight = 0;

        if (typeof(section) === 'object' && section.hasOwnProperty('level')) {

            if (section.hasOwnProperty('properties') && section.properties.hasOwnProperty('weight')) {
                sectionWeight = section.properties.weight[0];
            }
            section = sortOnWeight(section);

            sorted[sorted.length] = {s: section, k: key, w: sectionWeight};
        } else {
            //sorted[sorted.length] = {s: section, w: sectionWeight};
            tempDict[key] = dict[key];
        }
    }
    // sort array by weight
    sorted.sort(function (a, b) {
        return a.w - b.w;
    });

    for (var i = 0; i < sorted.length; i++) {
        tempDict[sorted[i].k] = sorted[i].s;
    }

    return tempDict;
}

module.exports = function (params, callback) {

    var assemble = params.assemble;
    var grunt = params.grunt;
    var pages = assemble.options.pages;
    var options = assemble.options.kss || {};

    //register the required handlebars helpers
    var handlebars = assemble.engine.engine['handlebars'];
    assembleHandlbarsHelpers.forEach(function (helper) {
        helper.register(handlebars);
    });

    //load the options and set defaults
    var rootSrcPath = options.src || 'src/scss';
    var srcMask = options.src_mask;

    options.src_mask = options.src_mask || /.scss/;

    var overviewMarkdownFile = options.overviewMarkdownFile || rootSrcPath+"/documentation/styleguide.md";

    // If the mask is a string, convert it into a RegExp.
    if (!(options.src_mask instanceof RegExp)) {
        options.src_mask = new RegExp(
            '(?:' + options.src_mask.replace(/\./g, '\\.').replace(/\*/g, '.*') + ')$'
        );
    }

    //load each scss file and put the comments string into the styleComments
    var foundComments = commentsFinder.findCommentsInDirectory(rootSrcPath, srcMask, grunt);

    var sections = {};
    //loop through all of the styleComments and create style sections and style-elements
    var pageSections = kssCommentsParser.convertKccCommentsToSectionObjects(foundComments,grunt);

    // in addition to the pages generated from the KSS comments create an index / overview page
    var indexPage = {
        sectionName: "index",
        sectionTitle: "Overview",
        description: "No overview Markdown file found",
        level: 1
    };

    if (grunt.file.exists(overviewMarkdownFile)) {
        indexPage.description = grunt.file.read(overviewMarkdownFile);
        indexPage.srcPath = overviewMarkdownFile;
    }
    // add overview as first
    sections["index"] = indexPage;

    // sort pageSections by weight
    pageSections = sortOnWeight(pageSections);
    // and append to sections (overview on top)
    _.extend(sections, pageSections);

    //using the sections array create a set of pages that use the template
    for (var sectionKey in sections) {
        var section = sections[sectionKey];
        if (typeof(section) === 'object') {

            var indexPageTemplateName = options.template;
            var currentPage = {
                data: {
                    layout: indexPageTemplateName
                },
                dest: options.dest + "/" + sectionKey + ".html"
            };

            //merge the page data into the context
            currentPage.data = _.merge(currentPage.data, section);
            currentPage.data.sections = sections;
            pages.push(currentPage);
        }
    }

    callback();

};

module.exports.options = {
    stage: 'assemble:post:pages'
};
<commit_msg>ensure that all output html files have lowercase names<commit_after>var _ = require('lodash');

var commentsFinder = require('./commentsFinder');
var kssCommentsParser = require('./kssCommentsParser');
var assembleHandlbarsHelpers = [
    require('./handlebars-helpers/styleguideHelpers.js'),
    require('./handlebars-helpers/markupHelpers.js'),
    require('./handlebars-helpers/teaserHelpers.js')
];

/**
 * sort sections object by weight
 * @param dict: sections object
 * @return sorted sections object
 * */
function sortOnWeight(dict) {

    var tempDict = {};
    var sorted = [];
    var weight = 0;

    for (var key in dict) {

        var section = dict[key];
        var sectionWeight = 0;

        if (typeof(section) === 'object' && section.hasOwnProperty('level')) {

            if (section.hasOwnProperty('properties') && section.properties.hasOwnProperty('weight')) {
                sectionWeight = section.properties.weight[0];
            }
            section = sortOnWeight(section);

            sorted[sorted.length] = {s: section, k: key, w: sectionWeight};
        } else {
            //sorted[sorted.length] = {s: section, w: sectionWeight};
            tempDict[key] = dict[key];
        }
    }
    // sort array by weight
    sorted.sort(function (a, b) {
        return a.w - b.w;
    });

    for (var i = 0; i < sorted.length; i++) {
        tempDict[sorted[i].k] = sorted[i].s;
    }

    return tempDict;
}

module.exports = function (params, callback) {

    var assemble = params.assemble;
    var grunt = params.grunt;
    var pages = assemble.options.pages;
    var options = assemble.options.kss || {};

    //register the required handlebars helpers
    var handlebars = assemble.engine.engine['handlebars'];
    assembleHandlbarsHelpers.forEach(function (helper) {
        helper.register(handlebars);
    });

    //load the options and set defaults
    var rootSrcPath = options.src || 'src/scss';
    var srcMask = options.src_mask;

    options.src_mask = options.src_mask || /.scss/;

    var overviewMarkdownFile = options.overviewMarkdownFile || rootSrcPath+"/documentation/styleguide.md";

    // If the mask is a string, convert it into a RegExp.
    if (!(options.src_mask instanceof RegExp)) {
        options.src_mask = new RegExp(
            '(?:' + options.src_mask.replace(/\./g, '\\.').replace(/\*/g, '.*') + ')$'
        );
    }

    //load each scss file and put the comments string into the styleComments
    var foundComments = commentsFinder.findCommentsInDirectory(rootSrcPath, srcMask, grunt);

    var sections = {};
    //loop through all of the styleComments and create style sections and style-elements
    var pageSections = kssCommentsParser.convertKccCommentsToSectionObjects(foundComments,grunt);

    // in addition to the pages generated from the KSS comments create an index / overview page
    var indexPage = {
        sectionName: "index",
        sectionTitle: "Overview",
        description: "No overview Markdown file found",
        level: 1
    };

    if (grunt.file.exists(overviewMarkdownFile)) {
        indexPage.description = grunt.file.read(overviewMarkdownFile);
        indexPage.srcPath = overviewMarkdownFile;
    }
    // add overview as first
    sections["index"] = indexPage;

    // sort pageSections by weight
    pageSections = sortOnWeight(pageSections);
    // and append to sections (overview on top)
    _.extend(sections, pageSections);

    //using the sections array create a set of pages that use the template
    for (var sectionKey in sections) {
        var section = sections[sectionKey];
        if (typeof(section) === 'object') {

            var indexPageTemplateName = options.template;
            var currentPage = {
                data: {
                    layout: indexPageTemplateName
                },
                dest: options.dest + "/" + sectionKey.toLowerCase() + ".html"
            };

            //merge the page data into the context
            currentPage.data = _.merge(currentPage.data, section);
            currentPage.data.sections = sections;
            pages.push(currentPage);
        }
    }

    callback();

};

module.exports.options = {
    stage: 'assemble:post:pages'
};
<|endoftext|>


<commit_before>;(function(root) {
'use strict';

var Backbone = root.Backbone || require('backbone');
var Cord = Backbone.Cord;
var extendObj = Cord.extendObj;
var isPlainObj = Cord.isPlainObj;
var extendProto = Cord.extendProto;
var ForceValue = Cord.ForceValue;

var convertToString = Cord.convertToString;
var encodeValue = Cord.encodeValue;
var decodeValue = Cord.decodeValue;
var randomGUID = Cord.randomGUID;
var setImmediate = Cord.setImmediate;
var regex = Cord.regex;

var react = root.preact || root.react || require('preact') || require('react');
var Component = react.Component;
var options = react.options;
var __setState = Component.prototype.setState;

var _currentComponent = null;
var _currentBinding = null;
var _DATA_BINDING_ATTR = 'data-binding-guid';
var _DATA_FEEDBACK_ATTR = 'data-binding-feedback';

function _bindGUID(uid, proxy) {
	var guid = null;
	if(uid) {
		guid = _currentComponent._boundGUIDs[uid] || (_currentComponent._boundGUIDs[uid] = randomGUID());
		_currentComponent._boundGUIDProxies[guid] = proxy;
	}
	return guid;
}

var bind = Cord.bind = function(key, guid) {
	if(!_currentComponent)
		return;
	var boundKey = guid || key;
	if(!_currentComponent._boundKeys[boundKey]) {
		_currentComponent.observe(key, function(key, value) {
			// Don't this.forceUpdate() because it is synchronous, instead call setState with no updates to enqueue a rerender
			if(guid && this._boundGUIDProxies[guid])
				this._boundGUIDProxies[guid].call(this, guid, key, value);
			else
				__setState.call(this, {});
		});
		_currentComponent._boundKeys[boundKey] = true;
	}
	return _currentComponent.getValueForKey(key);
};

var computed = Cord.computed = function(func) {
	var args = Cord.getFunctionArgs(func);
	if(!args.length)
		return func;
	var compFunc = function(compKey) {
		var i, values = [], state = {};
		compKey = compKey.split('@')[0];
		for(i = 0; i < args.length; ++i)
			values.push(this.getValueForKey(args[i]));
		state[compKey] = func.apply(this, values);
		this.setState(state);
	};
	compFunc._args = args;
	compFunc._computed = true;
	return compFunc;
};

// Add the default scope and have it mirror this.state
Cord.scopes.this = {
	observe: function() {},
	unobserve: function() {},
	getValue: function(key) {
		return this.state[key];
	},
	setValue: function(key, value) {
		var change = {};
		// For compatibility for some older mixins with readonly BB properties
		if(typeof value === 'object' && Object.getPrototypeOf(value) === ForceValue.prototype)
			value = value.value;
		change[key] = value;
		this.setState(change);
	}
};

function _normalizeMixin(mixin) {
	var methods = {};
	var state = {};
	var attr, value;
	var lifecycleMap = {
		initialize: 'componentWillMount',
		remove: 'componentWillUnmount'
	};
	if(mixin.properties) {
		for(attr in mixin.properties) {
			value = mixin.properties[attr];
			if(typeof value === 'function')
				state[attr] = computed(value);
			else if(isPlainObj(value))
				state[attr] = value.get ? computed(value.get) : value.value;
			else state[attr] = value;
		}
	}
	for(attr in mixin) {
		value = mixin[attr];
		if(typeof value === 'function')
			methods[(lifecycleMap[attr] || attr)] = value;
	}
	mixin.methods = methods;
	mixin.state = state;
	mixin._normalized = true;
}

// Inherit from react's Component so render can be wrapped and mixins applied
Cord.Component = function() {
	Component.apply(this, arguments);

	// Init observers
	this._observers = {};

	// Keys is true/false is a bound key is used in the render pipeline
	// GUIDs is mapping of vnode uids to GUIDs
	// Proxies maps GUIDs to a callback function
	this._boundKeys = {};
	this._boundGUIDs = {};
	this._boundGUIDProxies = {};

	// Apply any mixins
	var proto = Object.getPrototypeOf(this);
	if(proto.mixins && !proto._mixinsApplied) {
		var i, mixin, state = {};
		for(i = 0; i < proto.mixins.length; ++i) {
			mixin = Cord.mixins[proto.mixins[i]];
			if(!mixin._normalized)
				_normalizeMixin(mixin);
			extendProto(proto, mixin.methods);
			extendObj(state, mixin.state);
		}
		proto._mixinState = state;
		proto._mixinsApplied = true;
	}

	// Wrap render to allow bind() function to work without a context
	var __render = this.render;
	this.render = function() {
		var ret, prevComponent = _currentComponent;
		_currentComponent = this;
		try {
			ret = __render.apply(this, arguments);
		}
		finally {
			_currentComponent = prevComponent;
		}
		return ret;
	};

	var __componentWillMount = this.componentWillMount;
	this.componentWillMount = function() {
		var i, attr, args, value, prevComponent, guid, state = this.state;
		// Initialize computed state and any initial state from mixins
		if(this._mixinState)
			extendObj(state, this._mixinState);
		for(attr in state) {
			value = state[attr];
			if(typeof value === 'function' && value._computed) {
				args = value._args;
				prevComponent = _currentComponent;
				_currentComponent = this;
				try {
					for(i = 0; i < args.length; ++i) {
						// Setup as proxy setting the name of the (property @ arg number) as the guid for each bound argument
						guid = attr + '@' + i;
						this._boundGUIDProxies[guid] = value;
						bind(args[i], guid);
					}
					// Run once to set the initial state
					value.call(this, attr);
				}
				finally {
					_currentComponent = prevComponent;
				}
			}
		}
		if(__componentWillMount)
			__componentWillMount.apply(this, arguments);
	};
};
Cord.Component.prototype = Object.create(Component.prototype);
Cord.Component.prototype.constructor = Cord.Component;

Cord.Component.prototype.setState = function(state, callback) {
	if(typeof state === 'function')
		state = state(this.state, this.props);
	for(var key in state) {
		if(this._hasObservers('this', key))
			this._invokeObservers('this', key, state[key]);
	}
	__setState.call(this, state, callback);
};

// Extend Component with data binding methods without backwards compatibility Event functions (bind/unbind)
extendObj(Cord.Component.prototype, Cord.Binding, Backbone.Events);
delete Cord.Component.prototype.bind;
delete Cord.Component.prototype.unbind;

// Cleanup code for all components
var __beforeUnmount = options.beforeUnmount;
options.beforeUnmount = function(component) {
	// Observer cleanup
	if(component.stopObserving)
		component.stopObserving();
	// BB event cleanup
	if(component.stopListening)
		component.stopListening();
	if(__beforeUnmount)
		__beforeUnmount.apply(this, arguments);
};

function _testBindingFeedback(el) {
	// Prevent two-way data binding from creating an infinite feedback loop through dispatching events
	if(!el.hasAttribute(_DATA_FEEDBACK_ATTR))
		return false;
	var bindingUid = el.getAttribute(_DATA_BINDING_ATTR);
	if(bindingUid && bindingUid === _currentBinding) {
		_currentBinding = null;
		return true;
	}
	else {
		_currentBinding = bindingUid;
		return false;
	}
}

function _bindingProxy(guid, key, value) {
	var el = document.body.querySelector('[' + _DATA_BINDING_ATTR + '="' + guid + '"]');
	if(!el || _testBindingFeedback(el))
		return;
	encodeValue(el, value);
}

function _createValueListener(key, wrapped) {
	return function(e) {
		var el = e.currentTarget;
		if(_testBindingFeedback(el))
			return;
		this.setValueForKey(key, decodeValue(el));
		if(wrapped)
			wrapped(e);
	}.bind(_currentComponent);
}

var __vnode = options.vnode;
options.vnode = function(vnode) {
	// vnode contains: nodeName, children (one item or array), attributes, key
	var i, j, child, strings, matches, spliceArgs;
	var children = Array.isArray(vnode.children) ? vnode.children : [vnode.children];
	var attrs = vnode.attributes || {};

	if(!attrs.raw) {
		for(i = children.length - 1; i >= 0; --i) {
			child = children[i];
			if(typeof child === 'string') {
				strings = child.split(regex.variableSearch);
				if(strings.length > 1) {
					spliceArgs = [i, 1];
					matches = child.match(regex.variableSearch);
					for(j = 0; j < matches.length; ++j) {
						if(strings[j].length)
							spliceArgs.push(strings[j]);
						// Force to a string because it is what preact expects
						spliceArgs.push(convertToString(bind(regex.variableValue.exec(matches[j])[1])));
					}
					if(strings[j].length)
						spliceArgs.push(strings[j]);
					Array.prototype.splice.apply(children, spliceArgs);
				}
			}
		}
		vnode.children = children;
	}

	// The attr bind is shorthand for both observe and change
	if(attrs.bind) {
		attrs.observe = attrs.bind;
		attrs.change = attrs.bind;
		delete attrs.bind;
	}

	if(attrs.observe || attrs.change || attrs.input) {
		var guid = _bindGUID(vnode.key || attrs.name, _bindingProxy);

		if(!guid) {
			delete attrs.observe;
			delete attrs.change;
			delete attrs.input;
		}
		else {
			attrs[_DATA_BINDING_ATTR] = guid;

			// Observer binding to set the value
			if(attrs.observe) {
				var value = bind(attrs.observe, guid);
				delete attrs.observe;
				// Set the initial value on a delayed callback
				setImmediate(_bindingProxy.bind(null, guid, null, value));
				// If two-way this element is sensitive to binding feedback
				if(attrs.observe === attrs.change || attrs.observe === attrs.input)
					attrs[_DATA_FEEDBACK_ATTR] = true;
			}

			// Reverse binding on change or input events to listen to changes in the value
			if(attrs.change) {
				attrs.onChange = _createValueListener(attrs.change, attrs.onChange || attrs.onchange);
				delete attrs.change;
			}
			if(attrs.input) {
				attrs.onInput = _createValueListener(attrs.input, attrs.onInput || attrs.oninput);
				delete attrs.input;
			}
		}
	}

	if(__vnode)
		__vnode.apply(this, arguments);
};

// Export onto the react object
if(!root.cordCompatibilityMode) {
	react.bind = bind;
	react.computed = computed;
	react.Component = Component;
	react.Cord = Cord;
}

if(typeof exports === 'object')
	module.exports = Cord;

})(((typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global)));
<commit_msg>Debugged a problem with remembering current context with react lifecycle.<commit_after>;(function(root) {
'use strict';

var Backbone = root.Backbone || require('backbone');
var Cord = Backbone.Cord;
var extendObj = Cord.extendObj;
var isPlainObj = Cord.isPlainObj;
var extendProto = Cord.extendProto;
var ForceValue = Cord.ForceValue;

var convertToString = Cord.convertToString;
var encodeValue = Cord.encodeValue;
var decodeValue = Cord.decodeValue;
var randomGUID = Cord.randomGUID;
var setImmediate = Cord.setImmediate;
var regex = Cord.regex;

var react = root.preact || root.react || require('preact') || require('react');
var Component = react.Component;
var options = react.options;
var __setState = Component.prototype.setState;

var _currentComponent = null;
var _currentBinding = null;
var _DATA_BINDING_ATTR = 'data-binding-guid';
var _DATA_FEEDBACK_ATTR = 'data-binding-feedback';

function _bindGUID(uid, proxy) {
	var guid = null;
	if(uid) {
		guid = _currentComponent._boundGUIDs[uid] || (_currentComponent._boundGUIDs[uid] = randomGUID());
		_currentComponent._boundGUIDProxies[guid] = proxy;
	}
	return guid;
}

var bind = Cord.bind = function(key, guid) {
	if(!_currentComponent)
		return;
	var boundKey = guid || key;
	if(!_currentComponent._boundKeys[boundKey]) {
		_currentComponent.observe(key, function(key, value) {
			// Don't this.forceUpdate() because it is synchronous, instead call setState with no updates to enqueue a rerender
			if(guid && this._boundGUIDProxies[guid])
				this._boundGUIDProxies[guid].call(this, guid, key, value);
			else
				__setState.call(this, {});
		});
		_currentComponent._boundKeys[boundKey] = true;
	}
	return _currentComponent.getValueForKey(key);
};

var computed = Cord.computed = function(func) {
	var args = Cord.getFunctionArgs(func);
	if(!args.length)
		return func;
	var compFunc = function(compKey) {
		var i, values = [], state = {};
		compKey = compKey.split('@')[0];
		for(i = 0; i < args.length; ++i)
			values.push(this.getValueForKey(args[i]));
		state[compKey] = func.apply(this, values);
		this.setState(state);
	};
	compFunc._args = args;
	compFunc._computed = true;
	return compFunc;
};

// Add the default scope and have it mirror this.state
Cord.scopes.this = {
	observe: function() {},
	unobserve: function() {},
	getValue: function(key) {
		return this.state[key];
	},
	setValue: function(key, value) {
		var change = {};
		// For compatibility for some older mixins with readonly BB properties
		if(typeof value === 'object' && Object.getPrototypeOf(value) === ForceValue.prototype)
			value = value.value;
		change[key] = value;
		this.setState(change);
	}
};

function _normalizeMixin(mixin) {
	var methods = {};
	var state = {};
	var attr, value;
	var lifecycleMap = {
		initialize: 'componentWillMount',
		remove: 'componentWillUnmount'
	};
	if(mixin.properties) {
		for(attr in mixin.properties) {
			value = mixin.properties[attr];
			if(typeof value === 'function')
				state[attr] = computed(value);
			else if(isPlainObj(value))
				state[attr] = value.get ? computed(value.get) : value.value;
			else state[attr] = value;
		}
	}
	for(attr in mixin) {
		value = mixin[attr];
		if(typeof value === 'function')
			methods[(lifecycleMap[attr] || attr)] = value;
	}
	mixin.methods = methods;
	mixin.state = state;
	mixin._normalized = true;
}

// Inherit from react's Component so render can be wrapped and mixins applied
Cord.Component = function() {
	Component.apply(this, arguments);

	// Init observers
	this._observers = {};

	// Keys is true/false is a bound key is used in the render pipeline
	// GUIDs is mapping of vnode uids to GUIDs
	// Proxies maps GUIDs to a callback function
	this._boundKeys = {};
	this._boundGUIDs = {};
	this._boundGUIDProxies = {};

	// Apply any mixins
	var proto = Object.getPrototypeOf(this);
	if(proto.mixins && !proto._mixinsApplied) {
		var i, mixin, state = {};
		for(i = 0; i < proto.mixins.length; ++i) {
			mixin = Cord.mixins[proto.mixins[i]];
			if(!mixin._normalized)
				_normalizeMixin(mixin);
			extendProto(proto, mixin.methods);
			extendObj(state, mixin.state);
		}
		proto._mixinState = state;
		proto._mixinsApplied = true;
	}

	// Wrap render, componentDidMount, and componentDidUpdate to allow bind() function to work without a context
	// Wrapping just render is not enough because child vnodes that are functions are not called until after render
	var __render = this.render;
	var __componentDidMount = this.componentDidMount;
	var __componentDidUpdate = this.componentDidUpdate;
	this.render = function() {
		var ret;
		this._prevComponent = _currentComponent;
		_currentComponent = this;
		try {
			ret = __render.apply(this, arguments);
		}
		catch(err) {
			_currentComponent = this._prevComponent;
			throw err;
		}
		return ret;
	};
	this.componentDidMount = function() {
		_currentComponent = this._prevComponent;
		if(__componentDidMount)
			__componentDidMount.apply(this, arguments);
	};
	this.componentDidUpdate = function() {
		_currentComponent = this._prevComponent;
		if(__componentDidUpdate)
			__componentDidUpdate.apply(this, arguments);
	};

	var __componentWillMount = this.componentWillMount;
	this.componentWillMount = function() {
		var i, attr, args, value, prevComponent, guid, state = this.state;
		// Initialize computed state and any initial state from mixins
		if(this._mixinState)
			extendObj(state, this._mixinState);
		for(attr in state) {
			value = state[attr];
			if(typeof value === 'function' && value._computed) {
				args = value._args;
				prevComponent = _currentComponent;
				_currentComponent = this;
				try {
					for(i = 0; i < args.length; ++i) {
						// Setup as proxy setting the name of the (property @ arg number) as the guid for each bound argument
						guid = attr + '@' + i;
						this._boundGUIDProxies[guid] = value;
						bind(args[i], guid);
					}
					// Run once to set the initial state
					value.call(this, attr);
				}
				finally {
					_currentComponent = prevComponent;
				}
			}
		}
		if(__componentWillMount)
			__componentWillMount.apply(this, arguments);
	};
};
Cord.Component.prototype = Object.create(Component.prototype);
Cord.Component.prototype.constructor = Cord.Component;

Cord.Component.prototype.setState = function(state, callback) {
	if(typeof state === 'function')
		state = state(this.state, this.props);
	for(var key in state) {
		if(this._hasObservers('this', key))
			this._invokeObservers('this', key, state[key]);
	}
	__setState.call(this, state, callback);
};

// Extend Component with data binding methods without backwards compatibility Event functions (bind/unbind)
extendObj(Cord.Component.prototype, Cord.Binding, Backbone.Events);
delete Cord.Component.prototype.bind;
delete Cord.Component.prototype.unbind;

// Cleanup code for all components
var __beforeUnmount = options.beforeUnmount;
options.beforeUnmount = function(component) {
	// Observer cleanup
	if(component.stopObserving)
		component.stopObserving();
	// BB event cleanup
	if(component.stopListening)
		component.stopListening();
	if(__beforeUnmount)
		__beforeUnmount.apply(this, arguments);
};

function _testBindingFeedback(el) {
	// Prevent two-way data binding from creating an infinite feedback loop through dispatching events
	if(!el.hasAttribute(_DATA_FEEDBACK_ATTR))
		return false;
	var bindingUid = el.getAttribute(_DATA_BINDING_ATTR);
	if(bindingUid && bindingUid === _currentBinding) {
		_currentBinding = null;
		return true;
	}
	else {
		_currentBinding = bindingUid;
		return false;
	}
}

function _bindingProxy(guid, key, value) {
	var el = document.body.querySelector('[' + _DATA_BINDING_ATTR + '="' + guid + '"]');
	if(!el || _testBindingFeedback(el))
		return;
	encodeValue(el, value);
}

function _createValueListener(key, wrapped) {
	return function(e) {
		var el = e.currentTarget;
		if(_testBindingFeedback(el))
			return;
		this.setValueForKey(key, decodeValue(el));
		if(wrapped)
			wrapped(e);
	}.bind(_currentComponent);
}

var __vnode = options.vnode;
options.vnode = function(vnode) {
	// vnode contains: nodeName, children (one item or array), attributes, key
	var i, j, child, strings, matches, spliceArgs;
	var children = Array.isArray(vnode.children) ? vnode.children : [vnode.children];
	var attrs = vnode.attributes || {};

	if(!attrs.raw) {
		for(i = children.length - 1; i >= 0; --i) {
			child = children[i];
			if(typeof child === 'string') {
				strings = child.split(regex.variableSearch);
				if(strings.length > 1) {
					spliceArgs = [i, 1];
					matches = child.match(regex.variableSearch);
					for(j = 0; j < matches.length; ++j) {
						if(strings[j].length)
							spliceArgs.push(strings[j]);
						// Force to a string because it is what preact expects
						spliceArgs.push(convertToString(bind(regex.variableValue.exec(matches[j])[1])));
					}
					if(strings[j].length)
						spliceArgs.push(strings[j]);
					Array.prototype.splice.apply(children, spliceArgs);
				}
			}
		}
		vnode.children = children;
	}

	// The attr bind is shorthand for both observe and change
	if(attrs.bind) {
		attrs.observe = attrs.bind;
		attrs.change = attrs.bind;
		delete attrs.bind;
	}

	if(attrs.observe || attrs.change || attrs.input) {
		var guid = _bindGUID(vnode.key || attrs.name, _bindingProxy);

		if(!guid) {
			delete attrs.observe;
			delete attrs.change;
			delete attrs.input;
		}
		else {
			attrs[_DATA_BINDING_ATTR] = guid;

			// Observer binding to set the value
			if(attrs.observe) {
				var value = bind(attrs.observe, guid);
				delete attrs.observe;
				// Set the initial value on a delayed callback
				setImmediate(_bindingProxy.bind(null, guid, null, value));
				// If two-way this element is sensitive to binding feedback
				if(attrs.observe === attrs.change || attrs.observe === attrs.input)
					attrs[_DATA_FEEDBACK_ATTR] = true;
			}

			// Reverse binding on change or input events to listen to changes in the value
			if(attrs.change) {
				attrs.onChange = _createValueListener(attrs.change, attrs.onChange || attrs.onchange);
				delete attrs.change;
			}
			if(attrs.input) {
				attrs.onInput = _createValueListener(attrs.input, attrs.onInput || attrs.oninput);
				delete attrs.input;
			}
		}
	}

	if(__vnode)
		__vnode.apply(this, arguments);
};

// Export onto the react object
if(!root.cordCompatibilityMode) {
	react.bind = bind;
	react.computed = computed;
	react.Component = Component;
	react.Cord = Cord;
}

if(typeof exports === 'object')
	module.exports = Cord;

})(((typeof self === 'object' && self.self === self && self) || (typeof global === 'object' && global.global === global && global)));
<|endoftext|>


<commit_before>/**
 * @class Echo.StreamServer.Controls.Stream.Item.Plugins.CommunityFlag
 * Adds extra buttons Flag/Unflag to each item in the Echo Stream control for the authenticated users. The item will receive the CommunityFlagged state as soon as it is flagged by a certain number of users. By default this number is 3, but it may be updated by contacting Echo Solutions team at solutions@aboutecho.com. The plugin also shows the number of flags already set for the item next to the Flag/Unflag control.
 *
 *     new Echo.StreamServer.Controls.Stream({
 *         "target": document.getElementById("echo-stream"),
 *         "appkey": "test.echoenabled.com",
 *         "plugins": [{
 *             "name": "CommunityFlag"
 *         }]
 *     });
 *
 * @extends Echo.Plugin
 */
var plugin = Echo.Plugin.manifest("CommunityFlag", "Echo.StreamServer.Controls.Stream.Item");

plugin.init = function() {
	this.extendTemplate("insertAsLastChild", "data", plugin.template);
	this.component.addButtonSpec("CommunityFlag", this._assembleButton("Flag"));
	this.component.addButtonSpec("CommunityFlag", this._assembleButton("Unflag"));
};

plugin.config = {
	/**
	 * @cfg {Boolean} showUsers
	 * Specifies the visibility of list of users who flagged particular item. Note that the list is only visible for the users with the administrative privileges.
	 */
	"showUsers": true
};

plugin.template = '

';

plugin.labels = {
	/**
	 * @echo_label
	 */
	"flaggedThis": " flagged this.",
	/**
	 * @echo_label
	 */
	"flagControl": "Flag",
	/**
	 * @echo_label
	 */
	"unflagControl": "Unflag",
	/**
	 * @echo_label
	 */
	"flagProcessing": "Flagging...",
	/**
	 * @echo_label
	 */
	"unflagProcessing": "Unflagging..."
};

/**
 * @echo_renderer
 */
plugin.renderers.flaggedBy = function(element) {
	var plugin = this, item = this.component;
	var flags = item.get("data.object.flags", []);
	if (!flags.length || !item.user.is("admin") || !plugin.config.get("showUserList")) {
		return element.hide();
	}
	var flagsPerPage = 5;
	var visibleUsersCount = plugin.get("facepile")
		? plugin.get("facepile").getVisibleUsersCount()
		: flagsPerPage;
	var config = plugin.config.assemble({
		"target": element,
		"data": {
			"entries": flags,
			"itemsPerPage": flagsPerPage
		},
		"initialUsersCount": visibleUsersCount,
		"suffixText": plugin.labels.get("flaggedThis")
	});
	plugin.set("facepile", new Echo.StreamServer.Controls.FacePile(config));
	return element.show();
};

plugin.methods._assembleButton = function(name) {
	var plugin = this, item = this.component;
	var callback = function() {
		var item = this;
		item.get("buttons." + plugin.name + "." + name + ".element")
			.empty()
			.append(plugin.labels.get(name.toLowerCase() + "Processing"));
		var activity = {
			"verbs": ["http://activitystrea.ms/schema/1.0/" + name.toLowerCase()],
			"targets": [{"id": item.get("data.object.id")}]
		};
		var request = Echo.StreamServer.API.request({
			"endpoint": "submit",
			"submissionProxyURL": plugin.config.get("submissionProxyURL", "", true),
			"data": {
				"content": activity,
				"appkey": item.config.get("appkey"),
				"sessionID": item.user.get("sessionID"),
				"target-query": item.config.get("parent.query")
			},
			"onData": function() {
				/**
				 * @event onFlagComplete
				 * @echo_event Echo.StreamServer.Controls.Stream.Plugins.CommunityFlag.onFlagComplete
				 * Triggered if flag operation was completed.
				 */
				/**
				 * @event onUnFlagComplete
				 * @echo_event Echo.StreamServer.Controls.Stream.Plugins.CommunityFlag.onUnFlagComplete
				 * Triggered if reverse flag operation was completed.
				 */
				plugin.events.publish({
					"topic": "on" + name + "Complete",
					"data": {
						"item": {
							"data": item.get("data"),
							"target": item.config.get("target")
						}
					}
				});
				plugin.requestDataRefresh();
			}
		})
		request.send();
	};
	return function() {
		var item = this;
		var flags = item.get("data.object.flags");
		var label = plugin.labels.get(name.toLowerCase() + "Control");
		var action = plugin._myFlags(flags).length > 0 ? "Unflag" : "Flag";
		return {
			"name": name,
			"label": '' + label + '' +
				(item.user.is("admin") && flags.length ? " (" + flags.length + ")" : ""),
			"visible": item.user.is("logged") && action == name,
			"once": true,
			"callback": callback
		};
	};
};

plugin.methods._myFlags = function(flags) {
	var item = this.component;
	return $.map(flags, function(entry) {
		if (item.user.has("identity", entry.actor.id)) return entry;
	});
};

plugin.css = '.{plugin.class:flaggedBy} { background: url(' + Echo.Loader.getURL("sdk/images/curation/status/communityflagged.png") + ') no-repeat 0px 4px; padding: 0px 0px 4px 21px; }';

Echo.Plugin.create(plugin);
<commit_msg>CommunityFlag plugin - minor CR updates<commit_after>(function() {
/**
 * @class Echo.StreamServer.Controls.Stream.Item.Plugins.CommunityFlag
 * Adds extra buttons Flag/Unflag to each item in the Echo Stream control for the authenticated users. The item will receive the CommunityFlagged state as soon as it is flagged by a certain number of users. By default this number is 3, but it may be updated by contacting Echo Solutions team at solutions@aboutecho.com. The plugin also shows the number of flags already set for the item next to the Flag/Unflag control.
 *
 *     new Echo.StreamServer.Controls.Stream({
 *         "target": document.getElementById("echo-stream"),
 *         "appkey": "test.echoenabled.com",
 *         "plugins": [{
 *             "name": "CommunityFlag"
 *         }]
 *     });
 *
 * @extends Echo.Plugin
 */
var plugin = Echo.Plugin.manifest("CommunityFlag", "Echo.StreamServer.Controls.Stream.Item");

plugin.init = function() {
	this.extendTemplate("insertAsLastChild", "data", plugin.template);
	this.component.addButtonSpec("CommunityFlag", this._assembleButton("Flag"));
	this.component.addButtonSpec("CommunityFlag", this._assembleButton("Unflag"));
};

plugin.config = {
	/**
	 * @cfg {Boolean} showUsers
	 * Specifies the visibility of list of users who flagged particular item. Note that the list is only visible for the users with the administrative privileges.
	 */
	"showUsers": true
};

plugin.labels = {
	/**
	 * @echo_label
	 */
	"flaggedThis": " flagged this.",
	/**
	 * @echo_label
	 */
	"flagControl": "Flag",
	/**
	 * @echo_label
	 */
	"unflagControl": "Unflag",
	/**
	 * @echo_label
	 */
	"flagProcessing": "Flagging...",
	/**
	 * @echo_label
	 */
	"unflagProcessing": "Unflagging..."
};

plugin.template = '

';

/**
 * @echo_renderer
 */
plugin.renderers.flaggedBy = function(element) {
	var plugin = this, item = this.component;
	var flags = item.get("data.object.flags", []);
	if (!flags.length || !item.user.is("admin") || !plugin.config.get("showUserList")) {
		return element.hide();
	}
	var flagsPerPage = 5;
	var visibleUsersCount = plugin.get("facepile")
		? plugin.get("facepile").getVisibleUsersCount()
		: flagsPerPage;
	var config = plugin.config.assemble({
		"target": element,
		"data": {
			"entries": flags,
			"itemsPerPage": flagsPerPage
		},
		"initialUsersCount": visibleUsersCount,
		"suffixText": plugin.labels.get("flaggedThis")
	});
	plugin.set("facepile", new Echo.StreamServer.Controls.FacePile(config));
	return element.show();
};

plugin.methods._assembleButton = function(name) {
	var plugin = this, item = this.component;
	var callback = function() {
		var item = this;
		item.get("buttons." + plugin.name + "." + name + ".element")
			.empty()
			.append(plugin.labels.get(name.toLowerCase() + "Processing"));
		var activity = {
			"verbs": ["http://activitystrea.ms/schema/1.0/" + name.toLowerCase()],
			"targets": [{"id": item.get("data.object.id")}]
		};
		var request = Echo.StreamServer.API.request({
			"endpoint": "submit",
			"submissionProxyURL": plugin.config.get("submissionProxyURL", "", true),
			"data": {
				"content": activity,
				"appkey": item.config.get("appkey"),
				"sessionID": item.user.get("sessionID"),
				"target-query": item.config.get("parent.query")
			},
			"onData": function() {
				/**
				 * @event onFlagComplete
				 * @echo_event Echo.StreamServer.Controls.Stream.Plugins.CommunityFlag.onFlagComplete
				 * Triggered if flag operation was completed.
				 */
				/**
				 * @event onUnFlagComplete
				 * @echo_event Echo.StreamServer.Controls.Stream.Plugins.CommunityFlag.onUnFlagComplete
				 * Triggered if reverse flag operation was completed.
				 */
				plugin.events.publish({
					"topic": "on" + name + "Complete",
					"data": {
						"item": {
							"data": item.get("data"),
							"target": item.config.get("target")
						}
					}
				});
				plugin.requestDataRefresh();
			}
		});
		request.send();
	};
	return function() {
		var item = this;
		var flags = item.get("data.object.flags");
		var label = plugin.labels.get(name.toLowerCase() + "Control");
		var action = plugin._myFlags(flags).length > 0 ? "Unflag" : "Flag";
		return {
			"name": name,
			"label": '' + label + '' +
				(item.user.is("admin") && flags.length ? " (" + flags.length + ")" : ""),
			"visible": item.user.is("logged") && action == name,
			"once": true,
			"callback": callback
		};
	};
};

plugin.methods._myFlags = function(flags) {
	var item = this.component;
	return $.map(flags, function(entry) {
		if (item.user.has("identity", entry.actor.id)) return entry;
	});
};

plugin.css = '.{plugin.class:flaggedBy} { background: url(' + Echo.Loader.getURL("sdk/images/curation/status/communityflagged.png") + ') no-repeat 0px 4px; padding: 0px 0px 4px 21px; }';

Echo.Plugin.create(plugin);

})();<|endoftext|>