<commit_before>define([
    'app',
    'underscore',
    '../../util/strings.js'
  ],
  function(app, _, strings) {
    'use strict';

    app.controller('MeetingsCtrl', ['$http', '$scope', '$locale', 'meetings', 'lists', 'paginator',
      function($http, $scope, $locale, Meetings, Lists, paginator) {
        // default timeframe for meetings
        $scope.timeframe = 'upcoming';

        var filters = {
          country: undefined,
          year: undefined
        };

        function resetFilters() {
          filters.country = filters.year = undefined;
          setSelectedTitle('country', 'All');
          setSelectedTitle('year', 'All');
        }

        $scope.setFilterParam = function(paramName, value) {
          var country,
            isCountry = paramName === 'country',
            // normalize
            filterValue = (/[a|A]ll/.test(value)) ? null : value;

          if (!angular.equals(filters[paramName], filterValue)) {
            if (isCountry) {
              country = _.findWhere($scope.memberCountries, {
                countryCode: value
              });
            }
            setSelectedTitle(paramName, isCountry ? country.name : value);
            filterValue = isCountry && filterValue ? value : filterValue;

            filters[paramName] = filterValue;

            updateMeetingData(filterMeetings(filters));
          }
        };

        function filterMeetings(filters) {
          var filtered,
            meetings = meetingsCache[$scope.timeframe];

          if (!filters.country && !filters.year) return meetings;

          if (filters.country) {
            filtered = meetings.filter(function(meeting) {
              return meeting.countryCode === filters.country;
            });
          }
          if (filters.year) {
            if (!filtered) filtered = meetings;
            filtered = filtered.filter(function(meeting) {
              return meeting.startYear === filters.year;
            });
          }
          return filtered;
        }

        $scope.setPage = function(page, countryCode, year) {
          paginator.setPage(page);
          updateMeetingData();
        };

        $scope.setTimeframe = function(timeframe) {
          resetFilters();
          $scope.timeframe = timeframe;
          fetchMeetings(timeframe);
        };

        function setSelectedTitle(paramName, title) {
          $scope['selected' + strings.capitalise(paramName)] = title;
        }

        function generateCountryList(meetings) {
          Lists.getCountries(function(json) {
            var countryNames = [];
            meetings.forEach(function(meeting) {
              countryNames.push(_.findWhere(json, {
                countryCode: meeting.countryCode
              }));
            });
            $scope.memberCountries = _.sortBy(_.uniq(countryNames), function(country) {
              return country && country.name;
            });
            // set the default option to be all meetings
            // see below in setSelectedCountry for handling of special case.
            $scope.memberCountries.unshift({
              name: 'All',
              countryCode: 'All'
            });
            setSelectedTitle('country', $scope.memberCountries[0].name);
          });
        }

        function generateYearList(meetings) {
          var yearList = meetings.map(function(meeting) {
            return meeting.startYear;
          });
          yearList.unshift('All');
          $scope.yearList = _.uniq(yearList);
        }

        function computeOptionLists (meetingSet) {
          generateCountryList(meetingSet);
          generateYearList(meetingSet);
        }

        function updateMeetingData(meetings) {
          if (meetings) paginator.resetCollection(meetings);
          var page = paginator.getCurrentPage();
          $scope.meetings = page.data;
          $scope.pagination = page.pagination;
        }

        var meetingsCache = {};

        function fetchMeetings(timeframe, country, year) {
          // we're looking only for EBSA meetings
          var title = '*EBSA*';

          if (timeframe && meetingsCache[timeframe]) {
            computeOptionLists(meetingsCache[timeframe]);
            return updateMeetingData(meetingsCache[timeframe]);
          }

          Meetings.getMeetingsPage({
            timeframe: timeframe,
            title: title,
            countryCode: country,
            year: year
          })
          .then(function(meetingSet) {
            // cache the results since we ask the backend for all rows.
            meetingsCache[timeframe] = meetingSet;
            updateMeetingData(meetingSet);
            computeOptionLists(meetingSet);
          });
        }

        fetchMeetings('upcoming', filters.country, filters.year);
      }
    ]);

  });<commit_msg>formatting<commit_after>define([
    'app',
    'underscore',
    '../../util/strings.js'
  ],
  function(app, _, strings) {
    'use strict';

    app.controller('MeetingsCtrl', ['$http', '$scope', '$locale', 'meetings', 'lists', 'paginator',
      function($http, $scope, $locale, Meetings, Lists, paginator) {
        // default timeframe for meetings
        $scope.timeframe = 'upcoming';

        var filters = {
          country: undefined,
          year: undefined
        };

        function resetFilters() {
          filters.country = filters.year = undefined;
          setSelectedTitle('country', 'All');
          setSelectedTitle('year', 'All');
        }

        $scope.setFilterParam = function(paramName, value) {
          var country,
            isCountry = paramName === 'country',
            // normalize
            filterValue = (/[a|A]ll/.test(value)) ? null : value;

          if (!angular.equals(filters[paramName], filterValue)) {
            if (isCountry) {
              country = _.findWhere($scope.memberCountries, {
                countryCode: value
              });
            }
            setSelectedTitle(paramName, isCountry ? country.name : value);
            filterValue = isCountry && filterValue ? value : filterValue;

            filters[paramName] = filterValue;

            updateMeetingData(filterMeetings(filters));
          }
        };

        function filterMeetings(filters) {
          var filtered,
            meetings = meetingsCache[$scope.timeframe];

          if (!filters.country && !filters.year) return meetings;

          if (filters.country) {
            filtered = meetings.filter(function(meeting) {
              return meeting.countryCode === filters.country;
            });
          }
          if (filters.year) {
            if (!filtered) filtered = meetings;
            filtered = filtered.filter(function(meeting) {
              return meeting.startYear === filters.year;
            });
          }
          return filtered;
        }

        $scope.setPage = function(page, countryCode, year) {
          paginator.setPage(page);
          updateMeetingData();
        };

        $scope.setTimeframe = function(timeframe) {
          resetFilters();
          $scope.timeframe = timeframe;
          fetchMeetings(timeframe);
        };

        function setSelectedTitle(paramName, title) {
          $scope['selected' + strings.capitalise(paramName)] = title;
        }

        function generateCountryList(meetings) {
          Lists.getCountries(function(json) {
            var countryNames = [];
            meetings.forEach(function(meeting) {
              countryNames.push(_.findWhere(json, {
                countryCode: meeting.countryCode
              }));
            });
            $scope.memberCountries = _.sortBy(_.uniq(countryNames), function(country) {
              return country && country.name;
            });
            // set the default option to be all meetings
            // see below in setSelectedCountry for handling of special case.
            $scope.memberCountries.unshift({
              name: 'All',
              countryCode: 'All'
            });
            setSelectedTitle('country', $scope.memberCountries[0].name);
          });
        }

        function generateYearList(meetings) {
          var yearList = meetings.map(function(meeting) {
            return meeting.startYear;
          });
          yearList.unshift('All');
          $scope.yearList = _.uniq(yearList);
        }

        function computeOptionLists(meetingSet) {
          generateCountryList(meetingSet);
          generateYearList(meetingSet);
        }

        function updateMeetingData(meetings) {
          if (meetings) paginator.resetCollection(meetings);
          var page = paginator.getCurrentPage();
          $scope.meetings = page.data;
          $scope.pagination = page.pagination;
        }

        var meetingsCache = {};

        function fetchMeetings(timeframe, country, year) {
          // we're looking only for EBSA meetings
          var title = '*EBSA*';

          if (timeframe && meetingsCache[timeframe]) {
            computeOptionLists(meetingsCache[timeframe]);
            return updateMeetingData(meetingsCache[timeframe]);
          }

          Meetings.getMeetingsPage({
            timeframe: timeframe,
            title: title,
            countryCode: country,
            year: year
          })
          .then(function(meetingSet) {
            // cache the results since we ask the backend for all rows.
            meetingsCache[timeframe] = meetingSet;
            updateMeetingData(meetingSet);
            computeOptionLists(meetingSet);
          });
        }

        fetchMeetings('upcoming', filters.country, filters.year);
      }
    ]);

  });<|endoftext|>


<commit_before>module.exports = function(db){
	return function(req, res){
		var id = req.headers.id;
		var type = req.headers.type;

		// if id or type are missing
		if(!id || !type){
			res.status(400).send(
				util.format("Device ID and type Required. Got %s:%s",
					id, type)
			);
			return;
		}

		db.settings.update(id, type, req.body, function(err, data){
			if (err){
				console.log(err);
				return res.status(500).send(err);
			}
			return res.status(200).send(data);
		});
	};
}<commit_msg>Fixed settings update error output error<commit_after>module.exports = function(db){
	// library import
	var util = require('util');

	return function(req, res){
		var id = req.headers.id;
		var type = req.headers.type;

		// if id or type are missing
		if(!id || !type){
			res.status(400).send(
				util.format("Device ID and type Required. Got %s:%s",
					id, type)
			);
			return;
		}

		db.settings.update(id, type, req.body, function(err, data){
			if (err){
				console.log(err);
				return res.status(500).send(err);
			}
			return res.status(200).send(data);
		});
	};
}<|endoftext|>


<commit_before>jest.dontMock('../MarathonTaskDetailsList');
/* eslint-disable no-unused-vars */
var React = require('react');
/* eslint-enable no-unused-vars */
var ReactDOM = require('react-dom');
var TestUtils = require('react-addons-test-utils');

let MarathonStore = require('../../stores/MarathonStore');
let MarathonTaskDetailsList = require('../MarathonTaskDetailsList');
let Service = require('../../structs/Service');

describe('MarathonTaskDetailsList', function () {

  beforeEach(function () {
    this.getServiceFromTaskID = MarathonStore.getServiceFromTaskID;
    MarathonStore.getServiceFromTaskID = function () {
      return new Service();
    };
    this.container = document.createElement('div');
  });

  afterEach(function () {
    MarathonStore.getServiceFromTaskID = this.getServiceFromTaskID;
    ReactDOM.unmountComponentAtNode(this.container);
  });

  describe('#getTaskEndpoints', function () {

    it('returns None if ipAddresses, ports and host is not set', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({})).toEqual('None');
    });

    it('returns a list of ipAddresses if ports is not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({
        ipAddresses: [
          new Service({ipAddress: 'foo'}),
          new Service({ipAddress: 'bar'})
        ]
      })).toEqual('foo, bar');
    });

    it('returns host if ports and ipAddresses are not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({host: 'foo'}))
        .toEqual('foo');
    });

    it('returns host with ports if ipAddresses are not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:1');
    });

    it('uses service ports if available', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(1);
      expect(result[0].props.children).toEqual('foo:3');
    });

    it('defaults to ipAddresses and service ports', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({
        host: 'foo',
        ipAddresses: [
          new Service({ipAddress: 'foo'}),
          new Service({ipAddress: 'bar'})
        ],
        ports: [1, 2]
      });

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:3');
      expect(result[1].props.children).toEqual('bar:3');
    });

  })

});
<commit_msg>Correcting struct mistake<commit_after>jest.dontMock('../MarathonTaskDetailsList');
/* eslint-disable no-unused-vars */
var React = require('react');
/* eslint-enable no-unused-vars */
var ReactDOM = require('react-dom');
var TestUtils = require('react-addons-test-utils');

let MarathonStore = require('../../stores/MarathonStore');
let MarathonTaskDetailsList = require('../MarathonTaskDetailsList');
let Service = require('../../structs/Service');

describe('MarathonTaskDetailsList', function () {

  beforeEach(function () {
    this.getServiceFromTaskID = MarathonStore.getServiceFromTaskID;
    MarathonStore.getServiceFromTaskID = function () {
      return new Service();
    };
    this.container = document.createElement('div');
  });

  afterEach(function () {
    MarathonStore.getServiceFromTaskID = this.getServiceFromTaskID;
    ReactDOM.unmountComponentAtNode(this.container);
  });

  describe('#getTaskEndpoints', function () {

    it('returns None if ipAddresses, ports and host is not set', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({})).toEqual('None');
    });

    it('returns a list of ipAddresses if ports is not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({
        ipAddresses: [{ipAddress: 'foo'}, {ipAddress: 'bar'}]
      })).toEqual('foo, bar');
    });

    it('returns host if ports and ipAddresses are not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({host: 'foo'}))
        .toEqual('foo');
    });

    it('returns host with ports if ipAddresses are not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:1');
    });

    it('uses service ports if available', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(1);
      expect(result[0].props.children).toEqual('foo:3');
    });

    it('defaults to ipAddresses and service ports', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({
        host: 'foo',
        ipAddresses: [{ipAddress: 'foo'}, {ipAddress: 'bar'}],
        ports: [1, 2]
      });

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:3');
      expect(result[1].props.children).toEqual('bar:3');
    });

  })

});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, brackets, window, $, Mustache */

define(function (require, exports, module) {
    "use strict";

    // Brackets modules
    var ProjectManager          = brackets.getModule("project/ProjectManager"),
        SidebarView             = brackets.getModule("project/SidebarView"),
        PreferencesManager      = brackets.getModule("preferences/PreferencesManager"),
        Commands                = brackets.getModule("command/Commands"),
        CommandManager          = brackets.getModule("command/CommandManager"),
        KeyBindingManager       = brackets.getModule("command/KeyBindingManager"),
        Menus                   = brackets.getModule("command/Menus"),
        EditorManager           = brackets.getModule("editor/EditorManager"),
        ExtensionUtils          = brackets.getModule("utils/ExtensionUtils"),
        FileSystem              = brackets.getModule("filesystem/FileSystem"),
        AppInit                 = brackets.getModule("utils/AppInit"),
        KeyEvent                = brackets.getModule("utils/KeyEvent"),
        FileUtils               = brackets.getModule("file/FileUtils"),
        PopUpManager            = brackets.getModule("widgets/PopUpManager"),
        Strings                 = brackets.getModule("strings"),
        ProjectsMenuTemplate    = require("text!htmlContent/projects-menu.html");

    var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));

    var prefs = PreferencesManager.getPreferenceStorage(module);

    /** @const {string} Recent Projects commands ID */
    var TOGGLE_DROPDOWN = "recentProjects.toggle";

    /** @const {number} Maximum number of displayed recent projects */
    var MAX_PROJECTS = 20;

    /** @type {$.Element} jQuery elements used for the dropdown menu */
    var $dropdownItem,
        $dropdownToggle,
        $dropdown,
        $links;

    /**
     * Get the stored list of recent projects, fixing up paths as appropriate.
     * Warning: unlike most paths in Brackets, these lack a trailing "/"
     */
    function getRecentProjects() {
        var recentProjects = prefs.getValue("recentProjects") || [],
            i;

        for (i = 0; i < recentProjects.length; i++) {
            // We have to canonicalize & then de-canonicalize the path here, since our pref format uses no trailing "/"
            recentProjects[i] = FileUtils.stripTrailingSlash(ProjectManager.updateWelcomeProjectPath(recentProjects[i] + "/"));
        }
        return recentProjects;
    }

    /**
     * Add a project to the stored list of recent projects, up to MAX_PROJECTS.
     */
    function add() {
        var root = FileUtils.stripTrailingSlash(ProjectManager.getProjectRoot().fullPath),
            recentProjects = getRecentProjects(),
            index = recentProjects.indexOf(root);

        if (index !== -1) {
            recentProjects.splice(index, 1);
        }
        recentProjects.unshift(root);
        if (recentProjects.length > MAX_PROJECTS) {
            recentProjects = recentProjects.slice(0, MAX_PROJECTS);
        }
        prefs.setValue("recentProjects", recentProjects);
    }

    /**
     * Check the list of items to see if any of them are hovered, and if so trigger a mouseenter.
     * Normally the mouseenter event handles this, but when a previous item is deleted and the next
     * item moves up to be underneath the mouse, we don't get a mouseenter event for that item.
     */
    function checkHovers(pageX, pageY) {
        $dropdown.children().each(function () {
            var offset = $(this).offset(),
                width  = $(this).outerWidth(),
                height = $(this).outerHeight();

            if (pageX >= offset.left && pageX <= offset.left + width &&
                    pageY >= offset.top && pageY <= offset.top + height) {
                $(".recent-folder-link", this).triggerHandler("mouseenter");
            }
        });
    }

    /**
     * Create the "delete" button that shows up when you hover over a project.
     */
    function renderDelete() {
        return $("

")
            .mouseup(function (e) {
                // Don't let the click bubble upward.
                e.stopPropagation();

                // Remove the project from the preferences.
                var recentProjects = getRecentProjects(),
                    index = recentProjects.indexOf($(this).parent().data("path")),
                    newProjects = [],
                    i;
                for (i = 0; i < recentProjects.length; i++) {
                    if (i !== index) {
                        newProjects.push(recentProjects[i]);
                    }
                }
                prefs.setValue("recentProjects", newProjects);
                $(this).closest("li").remove();
                checkHovers(e.pageX, e.pageY);

                if (newProjects.length === 1) {
                    $dropdown.find(".divider").remove();
                }
            });
    }

    /**
     * Hide the delete button.
     */
    function removeDeleteButton() {
        $("#recent-folder-delete").remove();
    }

    /**
     * Show the delete button over a given target.
     */
    function addDeleteButton($target) {
        removeDeleteButton();
        renderDelete()
            .css("top", $target.position().top + 6)
            .appendTo($target);
    }

    /**
     * Selects the next or previous item in the list
     * @param {number} direction  +1 for next, -1 for prev
     */
    function selectNextItem(direction) {
        var $links   = $dropdown.find("a"),
            index    = $dropdownItem ? $links.index($dropdownItem) : (direction > 0 ? -1 : 0),
            $newItem = $links.eq((index + direction) % $links.length);

        if ($dropdownItem) {
            $dropdownItem.removeClass("selected");
        }
        $newItem.addClass("selected");

        $dropdownItem = $newItem;
        removeDeleteButton();
    }

    /**
     * Deletes the selected item and
     * move the focus to next item in list.
     *
     * @return {boolean} TRUE if project is removed
     */
    function removeSelectedItem(e) {
        var recentProjects = getRecentProjects(),
            $cacheItem = $dropdownItem,
            index = recentProjects.indexOf($cacheItem.data("path"));

        // When focus is not on project item
        if (index === -1) {
            return false;
        }

        // remove project
        recentProjects.splice(index, 1);
        prefs.setValue("recentProjects", recentProjects);
        checkHovers(e.pageX, e.pageY);

        if (recentProjects.length === 1) {
            $dropdown.find(".divider").remove();
        }
        selectNextItem(+1);
        $cacheItem.closest("li").remove();
        return true;
    }

    /**
     * Handles the Key Down events
     * @param {KeyboardEvent} event
     * @return {boolean} True if the key was handled
     */
    function keydownHook(event) {
        var keyHandled = false;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_UP:
            selectNextItem(-1);
            keyHandled = true;
            break;
        case KeyEvent.DOM_VK_DOWN:
            selectNextItem(+1);
            keyHandled = true;
            break;
        case KeyEvent.DOM_VK_ENTER:
        case KeyEvent.DOM_VK_RETURN:
            if ($dropdownItem) {
                $dropdownItem.trigger("click");
            }
            keyHandled = true;
            break;
        case KeyEvent.DOM_VK_BACK_SPACE:
        case KeyEvent.DOM_VK_DELETE:
            if ($dropdownItem) {
                removeSelectedItem(event);
                keyHandled = true;
            }
            break;
        }

        if (keyHandled) {
            event.stopImmediatePropagation();
            event.preventDefault();
        }
        return keyHandled;
    }

    /**
     * Close the dropdown.
     */
    function closeDropdown() {
        // Since we passed "true" for autoRemove to addPopUp(), this will
        // automatically remove the dropdown from the DOM. Also, PopUpManager
        // will call cleanupDropdown().
        if ($dropdown) {
            PopUpManager.removePopUp($dropdown);
        }
    }

    /**
     * Remove the various event handlers that close the dropdown. This is called by the
     * PopUpManager when the dropdown is closed.
     */
    function cleanupDropdown() {
        $("html").off("click", closeDropdown);
        $("#project-files-container").off("scroll", closeDropdown);
        $(SidebarView).off("hide", closeDropdown);
        $("#titlebar .nav").off("click", closeDropdown);
        $dropdown = null;

        EditorManager.focusEditor();
        $(window).off("keydown", keydownHook);
    }

    /**
     * Adds the click and mouse enter/leave events to the dropdown
     */
    function _handleListEvents() {
        $dropdown
            .on("click", "a", function (e) {
                var $link       = $(e.target).closest("a"),
                    id          = $link.attr("id"),
                    path        = $link.data("path");

                if (path) {
                    ProjectManager.openProject(path)
                        .fail(function () {
                            // Remove the project from the list only if it does not exist on disk
                            var recentProjects = getRecentProjects(),
                                index = recentProjects.indexOf(path);
                            if (index !== -1) {
                                FileSystem.resolve(path, function (err, item) {
                                    if (err) {
                                        recentProjects.splice(index, 1);
                                    }
                                });
                            }
                        });
                    closeDropdown();

                } else if (id === "open-folder-link") {
                    CommandManager.execute(Commands.FILE_OPEN_FOLDER);
                }

            })
            .on("mouseenter", "a", function () {
                if ($dropdownItem) {
                    $dropdownItem.removeClass("selected");
                }
                $dropdownItem = $(this).addClass("selected");

                if ($dropdownItem.hasClass("recent-folder-link")) {
                    // Note: we can't depend on the event here because this can be triggered
                    // manually from checkHovers().
                    addDeleteButton($(this));
                }
            })
            .on("mouseleave", "a", function () {
                var $link = $(this).removeClass("selected");

                if ($link.get(0) === $dropdownItem.get(0)) {
                    $dropdownItem = null;
                }
                if ($link.hasClass("recent-folder-link")) {
                    removeDeleteButton();
                }
            });
    }

    /**
     * Parses the path and returns an object with the full path, the folder name and the path without the folder.
     * @param {string} path The full path to the folder.
     * @return {{path: string, folder: string, rest: string}}
     */
    function parsePath(path) {
        var lastSlash = path.lastIndexOf("/"), folder, rest;
        if (lastSlash === path.length - 1) {
            lastSlash = path.slice(0, path.length - 1).lastIndexOf("/");
        }
        if (lastSlash >= 0) {
            rest = " - " + (lastSlash ? path.slice(0, lastSlash) : "/");
            folder = path.slice(lastSlash + 1);
        } else {
            rest = "/";
            folder = path;
        }

        return {path: path, folder: folder, rest: rest};
    }

    /**
     * Create the list of projects in the dropdown menu.
     * @return {string} The html content
     */
    function renderList() {
        var recentProjects = getRecentProjects(),
            currentProject = FileUtils.stripTrailingSlash(ProjectManager.getProjectRoot().fullPath),
            templateVars   = {
                projectList : [],
                Strings     : Strings
            };

        recentProjects.forEach(function (root) {
            if (root.path !== currentProject) {
                templateVars.projectList.push(parsePath(root));
            }
        });

        return Mustache.render(ProjectsMenuTemplate, templateVars);
    }

    /**
     * Show or hide the recent projects dropdown.
     */
    function showDropdown() {
        // If the dropdown is already visible, just return (so the root click handler on html
        // will close it).
        if ($dropdown) {
            return;
        }

        Menus.closeAll();

        $dropdown = $(renderList());

        var toggleOffset = $dropdownToggle.offset();
        $dropdown
            .css({
                left: toggleOffset.left,
                top: toggleOffset.top + $dropdownToggle.outerHeight()
            })
            .appendTo($("body"));

        PopUpManager.addPopUp($dropdown, cleanupDropdown, true);

        // TODO: should use capture, otherwise clicking on the menus doesn't close it. More fallout
        // from the fact that we can't use the Boostrap (1.4) dropdowns.
        $("html").on("click", closeDropdown);

        // Hide the menu if the user scrolls in the project tree. Otherwise the Lion scrollbar
        // overlaps it.
        // TODO: This duplicates logic that's already in ProjectManager (which calls Menus.close()).
        // We should fix this when the popup handling is centralized in PopupManager, as well
        // as making Esc close the dropdown. See issue #1381.
        $("#project-files-container").on("scroll", closeDropdown);

        // Hide the menu if the sidebar is hidden.
        // TODO: Is there some more general way we could handle this for dropdowns?
        $(SidebarView).on("hide", closeDropdown);

        // Hacky: if we detect a click in the menubar, close ourselves.
        // TODO: again, we should have centralized popup management.
        $("#titlebar .nav").on("click", closeDropdown);

        _handleListEvents();
        $(window).on("keydown", keydownHook);
    }

    /**
     * Show or hide the recent projects dropdown.
     * @param {$.Event} e The event object that triggered the toggling.
     */
    function handleMouseEvent(e) {
        // TODO: Can't just use Bootstrap 1.4 dropdowns for this since they're hard-coded to
        // assume that the dropdown is inside a top-level menubar created using
s.
        // Have to do this stopProp to avoid the html click handler from firing when this returns.
        e.stopPropagation();

        if ($dropdown) {
            closeDropdown(); //close if it's already visible
        } else {
            showDropdown();
        }
    }

    /**
     * Show or hide the recent projects dropdown from the toogle command.
     */
    function handleKeyEvent() {
        if (!$dropdown) {
            if (!SidebarView.isVisible()) {
                SidebarView.show();
            }

            showDropdown();
            $dropdown.focus();
            $links = $dropdown.find("a");
            // By default, select the most recent project (which is at the top of the list underneath Open Folder),
            // but if there are none, select Open Folder instead.
            $dropdownItem = $links.eq($links.length > 1 ? 1 : 0);
            $dropdownItem.addClass("selected");

            // If focusing the dropdown caused a modal bar to close, we need to refocus the dropdown
            window.setTimeout(function () {
                $dropdown.focus();
            }, 0);
        }
    }

    // Register command handlers
    CommandManager.register(Strings.CMD_TOGGLE_RECENT_PROJECTS, TOGGLE_DROPDOWN, handleKeyEvent);
    KeyBindingManager.addBinding(TOGGLE_DROPDOWN, KeyboardPrefs.recentProjects);

    // Initialize extension
    AppInit.appReady(function () {
        ExtensionUtils.loadStyleSheet(module, "styles/styles.css");

        $(ProjectManager).on("projectOpen", add);
        $(ProjectManager).on("beforeProjectClose", add);
    });

    AppInit.htmlReady(function () {
        $("#project-title")
            .wrap("

")
            .after("");

        $dropdownToggle = $("#project-dropdown-toggle").click(handleMouseEvent);
    });
});
<commit_msg>Fix regression in RecentProjects: current project was getting listed at top of menu instead of being excluded.<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, brackets, window, $, Mustache */

define(function (require, exports, module) {
    "use strict";

    // Brackets modules
    var ProjectManager          = brackets.getModule("project/ProjectManager"),
        SidebarView             = brackets.getModule("project/SidebarView"),
        PreferencesManager      = brackets.getModule("preferences/PreferencesManager"),
        Commands                = brackets.getModule("command/Commands"),
        CommandManager          = brackets.getModule("command/CommandManager"),
        KeyBindingManager       = brackets.getModule("command/KeyBindingManager"),
        Menus                   = brackets.getModule("command/Menus"),
        EditorManager           = brackets.getModule("editor/EditorManager"),
        ExtensionUtils          = brackets.getModule("utils/ExtensionUtils"),
        FileSystem              = brackets.getModule("filesystem/FileSystem"),
        AppInit                 = brackets.getModule("utils/AppInit"),
        KeyEvent                = brackets.getModule("utils/KeyEvent"),
        FileUtils               = brackets.getModule("file/FileUtils"),
        PopUpManager            = brackets.getModule("widgets/PopUpManager"),
        Strings                 = brackets.getModule("strings"),
        ProjectsMenuTemplate    = require("text!htmlContent/projects-menu.html");

    var KeyboardPrefs = JSON.parse(require("text!keyboard.json"));

    var prefs = PreferencesManager.getPreferenceStorage(module);

    /** @const {string} Recent Projects commands ID */
    var TOGGLE_DROPDOWN = "recentProjects.toggle";

    /** @const {number} Maximum number of displayed recent projects */
    var MAX_PROJECTS = 20;

    /** @type {$.Element} jQuery elements used for the dropdown menu */
    var $dropdownItem,
        $dropdownToggle,
        $dropdown,
        $links;

    /**
     * Get the stored list of recent projects, fixing up paths as appropriate.
     * Warning: unlike most paths in Brackets, these lack a trailing "/"
     */
    function getRecentProjects() {
        var recentProjects = prefs.getValue("recentProjects") || [],
            i;

        for (i = 0; i < recentProjects.length; i++) {
            // We have to canonicalize & then de-canonicalize the path here, since our pref format uses no trailing "/"
            recentProjects[i] = FileUtils.stripTrailingSlash(ProjectManager.updateWelcomeProjectPath(recentProjects[i] + "/"));
        }
        return recentProjects;
    }

    /**
     * Add a project to the stored list of recent projects, up to MAX_PROJECTS.
     */
    function add() {
        var root = FileUtils.stripTrailingSlash(ProjectManager.getProjectRoot().fullPath),
            recentProjects = getRecentProjects(),
            index = recentProjects.indexOf(root);

        if (index !== -1) {
            recentProjects.splice(index, 1);
        }
        recentProjects.unshift(root);
        if (recentProjects.length > MAX_PROJECTS) {
            recentProjects = recentProjects.slice(0, MAX_PROJECTS);
        }
        prefs.setValue("recentProjects", recentProjects);
    }

    /**
     * Check the list of items to see if any of them are hovered, and if so trigger a mouseenter.
     * Normally the mouseenter event handles this, but when a previous item is deleted and the next
     * item moves up to be underneath the mouse, we don't get a mouseenter event for that item.
     */
    function checkHovers(pageX, pageY) {
        $dropdown.children().each(function () {
            var offset = $(this).offset(),
                width  = $(this).outerWidth(),
                height = $(this).outerHeight();

            if (pageX >= offset.left && pageX <= offset.left + width &&
                    pageY >= offset.top && pageY <= offset.top + height) {
                $(".recent-folder-link", this).triggerHandler("mouseenter");
            }
        });
    }

    /**
     * Create the "delete" button that shows up when you hover over a project.
     */
    function renderDelete() {
        return $("

")
            .mouseup(function (e) {
                // Don't let the click bubble upward.
                e.stopPropagation();

                // Remove the project from the preferences.
                var recentProjects = getRecentProjects(),
                    index = recentProjects.indexOf($(this).parent().data("path")),
                    newProjects = [],
                    i;
                for (i = 0; i < recentProjects.length; i++) {
                    if (i !== index) {
                        newProjects.push(recentProjects[i]);
                    }
                }
                prefs.setValue("recentProjects", newProjects);
                $(this).closest("li").remove();
                checkHovers(e.pageX, e.pageY);

                if (newProjects.length === 1) {
                    $dropdown.find(".divider").remove();
                }
            });
    }

    /**
     * Hide the delete button.
     */
    function removeDeleteButton() {
        $("#recent-folder-delete").remove();
    }

    /**
     * Show the delete button over a given target.
     */
    function addDeleteButton($target) {
        removeDeleteButton();
        renderDelete()
            .css("top", $target.position().top + 6)
            .appendTo($target);
    }

    /**
     * Selects the next or previous item in the list
     * @param {number} direction  +1 for next, -1 for prev
     */
    function selectNextItem(direction) {
        var $links   = $dropdown.find("a"),
            index    = $dropdownItem ? $links.index($dropdownItem) : (direction > 0 ? -1 : 0),
            $newItem = $links.eq((index + direction) % $links.length);

        if ($dropdownItem) {
            $dropdownItem.removeClass("selected");
        }
        $newItem.addClass("selected");

        $dropdownItem = $newItem;
        removeDeleteButton();
    }

    /**
     * Deletes the selected item and
     * move the focus to next item in list.
     *
     * @return {boolean} TRUE if project is removed
     */
    function removeSelectedItem(e) {
        var recentProjects = getRecentProjects(),
            $cacheItem = $dropdownItem,
            index = recentProjects.indexOf($cacheItem.data("path"));

        // When focus is not on project item
        if (index === -1) {
            return false;
        }

        // remove project
        recentProjects.splice(index, 1);
        prefs.setValue("recentProjects", recentProjects);
        checkHovers(e.pageX, e.pageY);

        if (recentProjects.length === 1) {
            $dropdown.find(".divider").remove();
        }
        selectNextItem(+1);
        $cacheItem.closest("li").remove();
        return true;
    }

    /**
     * Handles the Key Down events
     * @param {KeyboardEvent} event
     * @return {boolean} True if the key was handled
     */
    function keydownHook(event) {
        var keyHandled = false;

        switch (event.keyCode) {
        case KeyEvent.DOM_VK_UP:
            selectNextItem(-1);
            keyHandled = true;
            break;
        case KeyEvent.DOM_VK_DOWN:
            selectNextItem(+1);
            keyHandled = true;
            break;
        case KeyEvent.DOM_VK_ENTER:
        case KeyEvent.DOM_VK_RETURN:
            if ($dropdownItem) {
                $dropdownItem.trigger("click");
            }
            keyHandled = true;
            break;
        case KeyEvent.DOM_VK_BACK_SPACE:
        case KeyEvent.DOM_VK_DELETE:
            if ($dropdownItem) {
                removeSelectedItem(event);
                keyHandled = true;
            }
            break;
        }

        if (keyHandled) {
            event.stopImmediatePropagation();
            event.preventDefault();
        }
        return keyHandled;
    }

    /**
     * Close the dropdown.
     */
    function closeDropdown() {
        // Since we passed "true" for autoRemove to addPopUp(), this will
        // automatically remove the dropdown from the DOM. Also, PopUpManager
        // will call cleanupDropdown().
        if ($dropdown) {
            PopUpManager.removePopUp($dropdown);
        }
    }

    /**
     * Remove the various event handlers that close the dropdown. This is called by the
     * PopUpManager when the dropdown is closed.
     */
    function cleanupDropdown() {
        $("html").off("click", closeDropdown);
        $("#project-files-container").off("scroll", closeDropdown);
        $(SidebarView).off("hide", closeDropdown);
        $("#titlebar .nav").off("click", closeDropdown);
        $dropdown = null;

        EditorManager.focusEditor();
        $(window).off("keydown", keydownHook);
    }

    /**
     * Adds the click and mouse enter/leave events to the dropdown
     */
    function _handleListEvents() {
        $dropdown
            .on("click", "a", function (e) {
                var $link       = $(e.target).closest("a"),
                    id          = $link.attr("id"),
                    path        = $link.data("path");

                if (path) {
                    ProjectManager.openProject(path)
                        .fail(function () {
                            // Remove the project from the list only if it does not exist on disk
                            var recentProjects = getRecentProjects(),
                                index = recentProjects.indexOf(path);
                            if (index !== -1) {
                                FileSystem.resolve(path, function (err, item) {
                                    if (err) {
                                        recentProjects.splice(index, 1);
                                    }
                                });
                            }
                        });
                    closeDropdown();

                } else if (id === "open-folder-link") {
                    CommandManager.execute(Commands.FILE_OPEN_FOLDER);
                }

            })
            .on("mouseenter", "a", function () {
                if ($dropdownItem) {
                    $dropdownItem.removeClass("selected");
                }
                $dropdownItem = $(this).addClass("selected");

                if ($dropdownItem.hasClass("recent-folder-link")) {
                    // Note: we can't depend on the event here because this can be triggered
                    // manually from checkHovers().
                    addDeleteButton($(this));
                }
            })
            .on("mouseleave", "a", function () {
                var $link = $(this).removeClass("selected");

                if ($link.get(0) === $dropdownItem.get(0)) {
                    $dropdownItem = null;
                }
                if ($link.hasClass("recent-folder-link")) {
                    removeDeleteButton();
                }
            });
    }

    /**
     * Parses the path and returns an object with the full path, the folder name and the path without the folder.
     * @param {string} path The full path to the folder.
     * @return {{path: string, folder: string, rest: string}}
     */
    function parsePath(path) {
        var lastSlash = path.lastIndexOf("/"), folder, rest;
        if (lastSlash === path.length - 1) {
            lastSlash = path.slice(0, path.length - 1).lastIndexOf("/");
        }
        if (lastSlash >= 0) {
            rest = " - " + (lastSlash ? path.slice(0, lastSlash) : "/");
            folder = path.slice(lastSlash + 1);
        } else {
            rest = "/";
            folder = path;
        }

        return {path: path, folder: folder, rest: rest};
    }

    /**
     * Create the list of projects in the dropdown menu.
     * @return {string} The html content
     */
    function renderList() {
        var recentProjects = getRecentProjects(),
            currentProject = FileUtils.stripTrailingSlash(ProjectManager.getProjectRoot().fullPath),
            templateVars   = {
                projectList : [],
                Strings     : Strings
            };

        recentProjects.forEach(function (root) {
            if (root !== currentProject) {
                templateVars.projectList.push(parsePath(root));
            }
        });

        return Mustache.render(ProjectsMenuTemplate, templateVars);
    }

    /**
     * Show or hide the recent projects dropdown.
     */
    function showDropdown() {
        // If the dropdown is already visible, just return (so the root click handler on html
        // will close it).
        if ($dropdown) {
            return;
        }

        Menus.closeAll();

        $dropdown = $(renderList());

        var toggleOffset = $dropdownToggle.offset();
        $dropdown
            .css({
                left: toggleOffset.left,
                top: toggleOffset.top + $dropdownToggle.outerHeight()
            })
            .appendTo($("body"));

        PopUpManager.addPopUp($dropdown, cleanupDropdown, true);

        // TODO: should use capture, otherwise clicking on the menus doesn't close it. More fallout
        // from the fact that we can't use the Boostrap (1.4) dropdowns.
        $("html").on("click", closeDropdown);

        // Hide the menu if the user scrolls in the project tree. Otherwise the Lion scrollbar
        // overlaps it.
        // TODO: This duplicates logic that's already in ProjectManager (which calls Menus.close()).
        // We should fix this when the popup handling is centralized in PopupManager, as well
        // as making Esc close the dropdown. See issue #1381.
        $("#project-files-container").on("scroll", closeDropdown);

        // Hide the menu if the sidebar is hidden.
        // TODO: Is there some more general way we could handle this for dropdowns?
        $(SidebarView).on("hide", closeDropdown);

        // Hacky: if we detect a click in the menubar, close ourselves.
        // TODO: again, we should have centralized popup management.
        $("#titlebar .nav").on("click", closeDropdown);

        _handleListEvents();
        $(window).on("keydown", keydownHook);
    }

    /**
     * Show or hide the recent projects dropdown.
     * @param {$.Event} e The event object that triggered the toggling.
     */
    function handleMouseEvent(e) {
        // TODO: Can't just use Bootstrap 1.4 dropdowns for this since they're hard-coded to
        // assume that the dropdown is inside a top-level menubar created using
s.
        // Have to do this stopProp to avoid the html click handler from firing when this returns.
        e.stopPropagation();

        if ($dropdown) {
            closeDropdown(); //close if it's already visible
        } else {
            showDropdown();
        }
    }

    /**
     * Show or hide the recent projects dropdown from the toogle command.
     */
    function handleKeyEvent() {
        if (!$dropdown) {
            if (!SidebarView.isVisible()) {
                SidebarView.show();
            }

            showDropdown();
            $dropdown.focus();
            $links = $dropdown.find("a");
            // By default, select the most recent project (which is at the top of the list underneath Open Folder),
            // but if there are none, select Open Folder instead.
            $dropdownItem = $links.eq($links.length > 1 ? 1 : 0);
            $dropdownItem.addClass("selected");

            // If focusing the dropdown caused a modal bar to close, we need to refocus the dropdown
            window.setTimeout(function () {
                $dropdown.focus();
            }, 0);
        }
    }

    // Register command handlers
    CommandManager.register(Strings.CMD_TOGGLE_RECENT_PROJECTS, TOGGLE_DROPDOWN, handleKeyEvent);
    KeyBindingManager.addBinding(TOGGLE_DROPDOWN, KeyboardPrefs.recentProjects);

    // Initialize extension
    AppInit.appReady(function () {
        ExtensionUtils.loadStyleSheet(module, "styles/styles.css");

        $(ProjectManager).on("projectOpen", add);
        $(ProjectManager).on("beforeProjectClose", add);
    });

    AppInit.htmlReady(function () {
        $("#project-title")
            .wrap("

")
            .after("");

        $dropdownToggle = $("#project-dropdown-toggle").click(handleMouseEvent);
    });
});
<|endoftext|>


<commit_before>/**
 *  Jelly. a sweet unobtrusive javascript framework
 *  for jQuery and Rails
 *
 *  version 0.7.4
 *
 * Copyright (c) 2009 Pivotal Labs
 * Licensed under the MIT license.
 *
 *  * Date: 2009-07-20 9:50:50 (Mon, 20 Jul 2009)
 *
 */

if (!window.Jelly) Jelly = new Object();
if (!Function.prototype.bind) {
  Function.prototype.bind = function(object) {
    var self = this;
    return function() {
      return self.apply(object, arguments);
    }
  }
}
Jelly.init = function() {
  this.components = [];
  this.observers = [];
  this.notifyObservers = this.Observers.notify;
  this.Components.initCalled = false;
  this.Pages.init();
  var self = this;
  $(document).ready(function() {
    self.Components.init();
  });
};

Jelly.attach = function() {
  for (var i = 0; i < arguments.length; i++) {
    var definition = arguments[i];
    var component = (typeof definition.component == "string") ?
                    eval(definition.component) :
                    definition.component;
    var evaluatedDefinition = {
      component: component,
      arguments: definition.arguments
    };
    this.components.push(evaluatedDefinition);
    if (Jelly.Components.initCalled) {
      Jelly.Components.initComponentFromDefinition(evaluatedDefinition);
    }
  }
};

Jelly.Components = {
  init: function() {
    for (var i = 0; i < Jelly.components.length; i++) {
      this.initComponentFromDefinition(Jelly.components[i]);
    }
    this.initCalled = true;
  },
  initComponentFromDefinition: function(definition) {
    var observer;
    if (definition.component.init) {
      observer = definition.component.init.apply(definition.component, definition.arguments);
    }
    Jelly.observers.push(observer ? observer : definition.component);
  }
};

Jelly.Observers = {
  notify: function(params) {
    if (this == Jelly) return Jelly.Observers.notify.call(this.observers, params);
    var observers = this.slice(0);

    // Deprecate 'on' in favor of making each page action a Component.
    if (params.on) {
      var additionalObserver = eval(params.on);
      if (observers.indexOf(additionalObserver) == -1) {
        observers.push(additionalObserver);
      }
    }

    for (var i = 0; i < observers.length; i++) {
      var observer = observers[i];
      if (observer[params.method]) {
        if (observer.detach && observer.detach()) {
          Jelly.Observers.garbageCollectObserver.call(this, observer);
        } else {
          observer[params.method].apply(observer, params.arguments);
        }
      }
    }

    if (params.attach) {
      Jelly.attach.apply(Jelly, params.attach);
    }
  },

  garbageCollectObserver: function(observer) {
    var index = this.indexOf(observer);
    if (index > -1) {
      Jelly.Observers.remove.call(this, index, index + 1);
    }
  },

  remove: function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  }
};

Jelly.Pages = {
  init: function() {
    this.all = {};
    Jelly.all = this.all; // Deprecated
  },

  add: function(name) {
    var page = new Jelly.Page.Constructor(name);
    for (var i = 1; i < arguments.length; i++) {
      $.extend(page, arguments[i]);
    }
    return page;
  }
};
Jelly.add = Jelly.Pages.add; // Deprecated

Jelly.Page = {
  init: function(controllerName, actionName) {
    var page = Jelly.Pages.all[controllerName] || new Jelly.Page.Constructor(controllerName);
    window.page = page;
    if (page.all) page.all();
    if (page[actionName]) page[actionName].call(page);
    page.loaded = true;
    return page;
  },
  Constructor: function(name) {
    this.loaded = false;
    this.documentHref = Jelly.Location.documentHref;

    this.name = name;
    Jelly.Pages.all[name] = this;
  }
};

Jelly.Location = {
  init: function() {
  },

  on_redirect: function(location) {
    top.location.href = location;
  }
};

Jelly.init();
<commit_msg>Changed version in comment.<commit_after>/**
 *  Jelly. a sweet unobtrusive javascript framework
 *  for jQuery and Rails
 *
 *  version 0.7.5
 *
 * Copyright (c) 2009 Pivotal Labs
 * Licensed under the MIT license.
 *
 *  * Date: 2009-07-20 9:50:50 (Mon, 20 Jul 2009)
 *
 */

if (!window.Jelly) Jelly = new Object();
if (!Function.prototype.bind) {
  Function.prototype.bind = function(object) {
    var self = this;
    return function() {
      return self.apply(object, arguments);
    }
  }
}
Jelly.init = function() {
  this.components = [];
  this.observers = [];
  this.notifyObservers = this.Observers.notify;
  this.Components.initCalled = false;
  this.Pages.init();
  var self = this;
  $(document).ready(function() {
    self.Components.init();
  });
};

Jelly.attach = function() {
  for (var i = 0; i < arguments.length; i++) {
    var definition = arguments[i];
    var component = (typeof definition.component == "string") ?
                    eval(definition.component) :
                    definition.component;
    var evaluatedDefinition = {
      component: component,
      arguments: definition.arguments
    };
    this.components.push(evaluatedDefinition);
    if (Jelly.Components.initCalled) {
      Jelly.Components.initComponentFromDefinition(evaluatedDefinition);
    }
  }
};

Jelly.Components = {
  init: function() {
    for (var i = 0; i < Jelly.components.length; i++) {
      this.initComponentFromDefinition(Jelly.components[i]);
    }
    this.initCalled = true;
  },
  initComponentFromDefinition: function(definition) {
    var observer;
    if (definition.component.init) {
      observer = definition.component.init.apply(definition.component, definition.arguments);
    }
    Jelly.observers.push(observer ? observer : definition.component);
  }
};

Jelly.Observers = {
  notify: function(params) {
    if (this == Jelly) return Jelly.Observers.notify.call(this.observers, params);
    var observers = this.slice(0);

    // Deprecate 'on' in favor of making each page action a Component.
    if (params.on) {
      var additionalObserver = eval(params.on);
      if (observers.indexOf(additionalObserver) == -1) {
        observers.push(additionalObserver);
      }
    }

    for (var i = 0; i < observers.length; i++) {
      var observer = observers[i];
      if (observer[params.method]) {
        if (observer.detach && observer.detach()) {
          Jelly.Observers.garbageCollectObserver.call(this, observer);
        } else {
          observer[params.method].apply(observer, params.arguments);
        }
      }
    }

    if (params.attach) {
      Jelly.attach.apply(Jelly, params.attach);
    }
  },

  garbageCollectObserver: function(observer) {
    var index = this.indexOf(observer);
    if (index > -1) {
      Jelly.Observers.remove.call(this, index, index + 1);
    }
  },

  remove: function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
  }
};

Jelly.Pages = {
  init: function() {
    this.all = {};
    Jelly.all = this.all; // Deprecated
  },

  add: function(name) {
    var page = new Jelly.Page.Constructor(name);
    for (var i = 1; i < arguments.length; i++) {
      $.extend(page, arguments[i]);
    }
    return page;
  }
};
Jelly.add = Jelly.Pages.add; // Deprecated

Jelly.Page = {
  init: function(controllerName, actionName) {
    var page = Jelly.Pages.all[controllerName] || new Jelly.Page.Constructor(controllerName);
    window.page = page;
    if (page.all) page.all();
    if (page[actionName]) page[actionName].call(page);
    page.loaded = true;
    return page;
  },
  Constructor: function(name) {
    this.loaded = false;
    this.documentHref = Jelly.Location.documentHref;

    this.name = name;
    Jelly.Pages.all[name] = this;
  }
};

Jelly.Location = {
  init: function() {
  },

  on_redirect: function(location) {
    top.location.href = location;
  }
};

Jelly.init();
<|endoftext|>


<commit_before>import React from 'react';
import { props, t, skinnable } from '../utils';

export const Props = {
  content: t.String,
  paragraphSpacing: t.union([t.String, t.Number]),
  id: t.maybe(t.String),
  className: t.maybe(t.String),
  style: t.maybe(t.Object)
};

/** A component that correctly formats multiple "\n"
 * @param content - The content of the paragraph
 * @param paragraphSpacing - The space occupied by an empty line
 */
@skinnable()
@props(Props)
export default class FormattedParagraph extends React.Component {

  Paragraph = ({ children, marginTop }) => {
    const style = {
      marginTop,
      marginLeft: 0,
      marginBottom: 0,
      marginRight: 0
    };

    const _children = children.split('\n').map((p, i) => i > 0 ? [
, p] : p); // handle single breaklines ("\n") inside paragraph

    return (

        {_children}

    );
  }

  getLocals({ content, paragraphSpacing, ...props }) {
    const { Paragraph } = this;

    const seriesOfNewParagraphs = content.match(/\n\n+/g);
    const paragraphs = content.split(/\n\n+/);

    const children = paragraphs.map((paragraph, i) => (
      <Paragraph marginTop={i !== 0 ? paragraphSpacing * (seriesOfNewParagraphs[i - 1].length - 1) : 0}>
        {paragraph}
      </Paragraph>
    ));

    return {
      ...props,
      children
    };
  }

  template(locals) {
    return (

    );
  }

}
<commit_msg>simplify FormattedParagraph: use only
<commit_after>import React from 'react';
import { props, t, skinnable } from '../utils';
import flattenDeep from 'lodash/flattenDeep';

export const Props = {
  content: t.String,
  paragraphSpacing: t.union([t.String, t.Number]),
  id: t.maybe(t.String),
  className: t.maybe(t.String),
  style: t.maybe(t.Object)
};

/** A component that correctly formats multiple "\n"
 * @param content - The content of the paragraph
 * @param paragraphSpacing - The space occupied by an empty line
 */
@skinnable()
@props(Props)
export default class FormattedParagraph extends React.Component {

  replaceBreaklinesWithBR(children) {
    return flattenDeep([].concat(children).map((child) => {
      if (t.String.is(child)) {
        const paragraphs = child.split(/\n/g);

        return paragraphs.map((paragraph, i) => {
          if (paragraph.length === 0) {
            return
;
          } else if (i < paragraphs.length - 1) {
            return [paragraph,
];
          } else {
            return paragraph;
          }
        });
      } else {
        return child;
      }
    }));
  }

  getLocals({ content, ...props }) {
    return {
      ...props,
      children: this.replaceBreaklinesWithBR(content)
    };
  }

  template(locals) {
    return (

    );
  }

}
<|endoftext|>


<commit_before>/**
  上一个例子的微信墙没有获得用户头像、名字等信息
  这些信息要通过另一类微信API，也就是由服务器主动调用微信获得
  这一类API的安全机制不同于之前，不再通过简单的TOKEN校验
  而需要通过appID、appSecret获得access_token，然后再用
  access_token获取相应的数据

  可以先看以下代码：
  lib/config.js - appID和appSecret配置
  lib/token.js  - 获得有效token
  lib/user.js   - 获得用户信息
  lib/reply.js  - 回复微信的模板
  lib/ws.js     - 简单的websocket
*/

var PORT = require('./lib/config').wxPort;
var getUserInfo = require('./lib/user').getUserInfo;
var replyText = require('./lib/reply').replyText;

var wss = require('./lib/ws.js').wss;

// var PORT = 9529;
var http = require('http');
var qs = require('qs');
var crypto =require('crypto');
var url=require('url');
var xml2js =require('xml2js');
var tmpl = require('tmpl');
var socketio=require('socket.io');
var TOKEN = 'rinesnow';

var express = require('express');
app = express();
app.use(express.static(__dirname));
app.listen(require('./lib/config').indexPort);

function checkSignature(params, token){
  //1. 将token、timestamp、nonce三个参数进行字典序排序
  //2. 将三个参数字符串拼接成一个字符串进行sha1加密
  //3. 开发者获得加密后的字符串可与signature对比，标识该请求来源于微信

  var key = [token, params.timestamp, params.nonce].sort().join('');
  var sha1 = require('crypto').createHash('sha1');
  sha1.update(key);

  return  sha1.digest('hex') == params.signature;
}

var server = http.createServer(function (request, response) {

  //解析URL中的query部分，用qs模块(npm install qs)将query解析成json
  var query = require('url').parse(request.url).query;
  var params = qs.parse(query);
  if(!checkSignature(params, TOKEN)){
    //如果签名不对，结束请求并返回
    response.end('signature fail');
    return;
  }
  if(request.method == "GET"){
    //如果请求是GET，返回echostr用于通过服务器有效校验
    response.end(params.echostr);
  }else{
    //否则是微信给开发者服务器的POST请求
    var postdata = "";

    request.addListener("data",function(postchunk){
        postdata += postchunk;
    });

    //获取到了POST数据
    request.addListener("end",function(){
      var parseString = xml2js.parseString;

      parseString(postdata, function (err, result) {
         // console.log('err'+result);

        if(!err){
                console.log('if'+err);
        }
        if(result.xml.MsgType[0] === 'text'){
              getUserInfo(result.xml.FromUserName[0],function (userInfo) {
                    result.user = userInfo;
                    socket.broadcast.emit('newUserInfo',result);
              });
        }
      });
    });
  }
});

var messages = [];
messages.push('newClient');

var io = require('socket.io').listen(server);
io.sockets.on('connection',function(socket){
  socket.emit('connected');
  console.log('connected');

  socket.broadcast.emit('newClient',new Date());

  socket.on('getAllMessages',function(){
      socket.emit('allMessages',messages);
  });

  socket.on('addMessage',function(message){
      messages.unshift(message);
      io.sockets.emit('newMessage',message);
  });
});

server.listen(PORT);

console.log("Weixin server runing at port: " + PORT + ".");<commit_msg>socketio<commit_after>/**
  上一个例子的微信墙没有获得用户头像、名字等信息
  这些信息要通过另一类微信API，也就是由服务器主动调用微信获得
  这一类API的安全机制不同于之前，不再通过简单的TOKEN校验
  而需要通过appID、appSecret获得access_token，然后再用
  access_token获取相应的数据

  可以先看以下代码：
  lib/config.js - appID和appSecret配置
  lib/token.js  - 获得有效token
  lib/user.js   - 获得用户信息
  lib/reply.js  - 回复微信的模板
  lib/ws.js     - 简单的websocket
*/

var PORT = require('./lib/config').wxPort;
var getUserInfo = require('./lib/user').getUserInfo;
var replyText = require('./lib/reply').replyText;

var wss = require('./lib/ws.js').wss;

// var PORT = 9529;
var http = require('http');
var qs = require('qs');
var crypto =require('crypto');
var url=require('url');
var xml2js =require('xml2js');
var tmpl = require('tmpl');
var TOKEN = 'rinesnow';

var express = require('express');
app = express();
app.use(express.static(__dirname));
app.listen(require('./lib/config').indexPort);

function checkSignature(params, token){
  //1. 将token、timestamp、nonce三个参数进行字典序排序
  //2. 将三个参数字符串拼接成一个字符串进行sha1加密
  //3. 开发者获得加密后的字符串可与signature对比，标识该请求来源于微信

  var key = [token, params.timestamp, params.nonce].sort().join('');
  var sha1 = require('crypto').createHash('sha1');
  sha1.update(key);

  return  sha1.digest('hex') == params.signature;
}

var server = http.createServer(function (request, response) {

  //解析URL中的query部分，用qs模块(npm install qs)将query解析成json
  var query = require('url').parse(request.url).query;
  var params = qs.parse(query);
  if(!checkSignature(params, TOKEN)){
    //如果签名不对，结束请求并返回
    response.end('signature fail');
    return;
  }
  if(request.method == "GET"){
    //如果请求是GET，返回echostr用于通过服务器有效校验
    response.end(params.echostr);
  }else{
    //否则是微信给开发者服务器的POST请求
    var postdata = "";

    request.addListener("data",function(postchunk){
        postdata += postchunk;
    });

    //获取到了POST数据
    request.addListener("end",function(){
      var parseString = xml2js.parseString;

      parseString(postdata, function (err, result) {
         console.log('err'+result);

        if(!err){

          if(result.xml.MsgType[0] === 'text'){
              getUserInfo(result.xml.FromUserName[0],function (userInfo) {
                    result.user = userInfo;
                     var res = replyText(result, '消息推送成功！');
                     console.log(res);
                    socket.broadcast.emit('newUserInfo',result);
              });
          }
        }

      });
    });
  }
});

var messages = [];
messages.push('newClient');

var io = require('socket.io').listen(server);
io.sockets.on('connection',function(socket){
  socket.emit('connected');
  console.log('connected');

  socket.broadcast.emit('newClient',new Date());

  socket.on('getAllMessages',function(){
      socket.emit('allMessages',messages);
  });

  socket.on('addMessage',function(message){
      messages.unshift(message);
      io.sockets.emit('newMessage',message);
  });
});

server.listen(PORT);

console.log("Weixin server runing at port: " + PORT + ".");<|endoftext|>


<commit_before>/**
* Copyright 2016 PT Inovação e Sistemas SA
* Copyright 2016 INESC-ID
* Copyright 2016 QUOBIS NETWORKS SL
* Copyright 2016 FRAUNHOFER-GESELLSCHAFT ZUR FOERDERUNG DER ANGEWANDTEN FORSCHUNG E.V
* Copyright 2016 ORANGE SA
* Copyright 2016 Deutsche Telekom AG
* Copyright 2016 Apizee
* Copyright 2016 TECHNISCHE UNIVERSITAT BERLIN
*
* 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.
**/

// Service Framework
import IdentityManager from 'service-framework/dist/IdentityManager';
import Discovery from 'service-framework/dist/Discovery';
import {Syncher} from 'service-framework/dist/Syncher';

// Utils
import {divideURL} from '../utils/utils';
import Search from '../utils/Search';

// Internals
import { communicationObject, CommunicationStatus } from './communication';
import ChatController from './ChatController';

/**
* Hyperty Group Chat Manager API (HypertyChat)
* @author Vitor Silva [vitor-t-silva@telecom.pt]
* @version 0.1.0
*/
class GroupChatManager {

  constructor(hypertyURL, bus, configuration) {
    if (!hypertyURL) throw new Error('The hypertyURL is a needed parameter');
    if (!bus) throw new Error('The MiniBus is a needed parameter');
    if (!configuration) throw new Error('The configuration is a needed parameter');

    let _this = this;
    let syncher = new Syncher(hypertyURL, bus, configuration);

    let domain = divideURL(hypertyURL).domain;
    let discovery = new Discovery(hypertyURL, bus);
    let identityManager = new IdentityManager(hypertyURL, configuration.runtimeURL, bus);

    _this._objectDescURL = 'hyperty-catalogue://catalogue.' + domain + '/.well-known/dataschema/Communication';

    _this._hypertyURL = hypertyURL;
    _this._syncher = syncher;
    _this._domain = domain;

    _this.discovery = discovery;
    _this.identityManager = identityManager;

    _this.search = new Search(discovery, identityManager);

    _this.communicationObject = communicationObject;

    console.log('Discover: ', discovery);
    console.log('Identity Manager: ', identityManager);

    syncher.onNotification(function(event) {

      if (event.type === 'create') {

        // TODO: replace the 100 for Message.Response
        event.ack(100);

        if (_this._onInvitation) { _this._onInvitation(event); }
      }

      if (event.type === 'delete') {
        // TODO: replace the 200 for Message.Response
        event.ack(200);

        //Reset all the parameters
        _this.communicationObject.owner = '';
        _this.communicationObject.name = '';
        _this.communicationObject.id = '';
        _this.communicationObject.status = '';
        _this.communicationObject.startingTime = '';
        _this.communicationObject.lastModified = '';
        _this.communicationObject.participants = [];
        _this.communicationObject.resources = ['chat'];
        _this.communicationObject.children = [];

        if (_this.chatController) {
          _this.chatController.closeEvent(event);
        }
      }

    });

  }

  /**
   * This function is used to create a new Group Chat providing the name and the identifiers of users to be invited.
   * @param  {string}                     name  Is a string to identify the Group Chat
   * @param  {array<URL.userURL>}         users Array of users to be invited to join the Group Chat. Users are identified with reTHINK User URL, like this format user://<ipddomain>/<user-identifier>
   * @return {<Promise>ChatController}    A ChatController object as a Promise.
   */
  create(name, users, domains) {

    let _this = this;
    let syncher = _this._syncher;

    return new Promise(function(resolve, reject) {

      // Create owner participant
      _this.communicationObject.owner = _this._hypertyURL;
      _this.communicationObject.name = name;
      _this.communicationObject.id = name;
      _this.communicationObject.resources = ['chat'];
      _this.communicationObject.children = [];
      _this.communicationObject.status = CommunicationStatus.OPEN;
      _this.communicationObject.startingTime = new Date().toJSON();
      _this.communicationObject.lastModified = _this.communicationObject.startingTime;

      _this.search.myIdentity().then((identity) => {

        // Add my identity
        _this.communicationObject.participants.push(identity);

        console.info('[GroupChatManager] searching ' + users + ' at domain ' + domains);

        let usersSearch = _this.search.users(users, domains, ['comm'], ['chat']);
        console.log('usersSearch->', usersSearch);
        return usersSearch;
      }).then((hypertiesIDs) => {
        console.log('hypertiesIDS',hypertiesIDs);

        let selectedHyperties = hypertiesIDs.map((hyperty) => {
          return hyperty.hypertyID;
        });

        console.info('[GroupChatManager] ------------------------ Syncher Create ---------------------- \n');
        console.info('[GroupChatManager] Selected Hyperties: !!! ', selectedHyperties);
        console.info(`Have ${selectedHyperties.length} users;`);

        if(hypertiesIDs[0] && typeof(hypertiesIDs[0]) !== 'object' &&  hypertiesIDs[0].split('@').length > 1) {
          console.log('here');
          return syncher.create(_this._objectDescURL, hypertiesIDs, _this.communicationObject);
        } else {
          console.log('here2');
          return syncher.create(_this._objectDescURL, selectedHyperties, _this.communicationObject);
        }

      }).catch((reason) => {
        console.log('Error:', reason);
      }).then(function(dataObjectReporter) {
        console.info('[GroupChatManager] 3. Return Create Data Object Reporter', dataObjectReporter);

        let chatController = new ChatController(syncher, _this.discovery, _this._domain, _this.search);
        chatController.dataObjectReporter = dataObjectReporter;
        _this.chatController = chatController;
        resolve(chatController);

      }).catch(function(reason) {
        reject(reason);
      });
    });

  }

  /**
   * This function is used to handle notifications about incoming invitations to join a Group Chat.
   * @param  {Function} CreateEvent The CreateEvent fired by the Syncher when an invitaion is received
   */
  onInvitation(callback) {
    let _this = this;
    _this._onInvitation = callback;
  }

  /**
   * This function is used to join a Group Chat.
   * @param  {URL.CommunicationURL} invitationURL  The Communication URL of the Group Chat to join that is provided in the invitation event
   * @return {<Promise>ChatController}             It returns the ChatController object as a Promise
   */
  join(invitationURL) {
    let _this = this;
    let syncher = _this._syncher;

    return new Promise(function(resolve, reject) {

      console.info('[GroupChatManager] ------------------------ Syncher subscribe ---------------------- \n');
      console.info(invitationURL);

      debugger;

      syncher.subscribe(_this._objectDescURL, invitationURL).then(function(dataObjectObserver) {
        console.info('[GroupChatManager] Data Object Observer: ', dataObjectObserver);
        let chatController = new ChatController(syncher, _this.discovery, _this._domain, _this.search);
        resolve(chatController);

        chatController.dataObjectObserver = dataObjectObserver;

      }).catch(function(reason) {
        reject(reason);
      });
    });

  }

}

export default function activate(hypertyURL, bus, configuration) {

  return {
    name: 'GroupChatManager',
    instance: new GroupChatManager(hypertyURL, bus, configuration)
  };

}
<commit_msg>breakpoint removed<commit_after>/**
* Copyright 2016 PT Inovação e Sistemas SA
* Copyright 2016 INESC-ID
* Copyright 2016 QUOBIS NETWORKS SL
* Copyright 2016 FRAUNHOFER-GESELLSCHAFT ZUR FOERDERUNG DER ANGEWANDTEN FORSCHUNG E.V
* Copyright 2016 ORANGE SA
* Copyright 2016 Deutsche Telekom AG
* Copyright 2016 Apizee
* Copyright 2016 TECHNISCHE UNIVERSITAT BERLIN
*
* 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.
**/

// Service Framework
import IdentityManager from 'service-framework/dist/IdentityManager';
import Discovery from 'service-framework/dist/Discovery';
import {Syncher} from 'service-framework/dist/Syncher';

// Utils
import {divideURL} from '../utils/utils';
import Search from '../utils/Search';

// Internals
import { communicationObject, CommunicationStatus } from './communication';
import ChatController from './ChatController';

/**
* Hyperty Group Chat Manager API (HypertyChat)
* @author Vitor Silva [vitor-t-silva@telecom.pt]
* @version 0.1.0
*/
class GroupChatManager {

  constructor(hypertyURL, bus, configuration) {
    if (!hypertyURL) throw new Error('The hypertyURL is a needed parameter');
    if (!bus) throw new Error('The MiniBus is a needed parameter');
    if (!configuration) throw new Error('The configuration is a needed parameter');

    let _this = this;
    let syncher = new Syncher(hypertyURL, bus, configuration);

    let domain = divideURL(hypertyURL).domain;
    let discovery = new Discovery(hypertyURL, bus);
    let identityManager = new IdentityManager(hypertyURL, configuration.runtimeURL, bus);

    _this._objectDescURL = 'hyperty-catalogue://catalogue.' + domain + '/.well-known/dataschema/Communication';

    _this._hypertyURL = hypertyURL;
    _this._syncher = syncher;
    _this._domain = domain;

    _this.discovery = discovery;
    _this.identityManager = identityManager;

    _this.search = new Search(discovery, identityManager);

    _this.communicationObject = communicationObject;

    console.log('Discover: ', discovery);
    console.log('Identity Manager: ', identityManager);

    syncher.onNotification(function(event) {

      if (event.type === 'create') {

        // TODO: replace the 100 for Message.Response
        event.ack(100);

        if (_this._onInvitation) { _this._onInvitation(event); }
      }

      if (event.type === 'delete') {
        // TODO: replace the 200 for Message.Response
        event.ack(200);

        //Reset all the parameters
        _this.communicationObject.owner = '';
        _this.communicationObject.name = '';
        _this.communicationObject.id = '';
        _this.communicationObject.status = '';
        _this.communicationObject.startingTime = '';
        _this.communicationObject.lastModified = '';
        _this.communicationObject.participants = [];
        _this.communicationObject.resources = ['chat'];
        _this.communicationObject.children = [];

        if (_this.chatController) {
          _this.chatController.closeEvent(event);
        }
      }

    });

  }

  /**
   * This function is used to create a new Group Chat providing the name and the identifiers of users to be invited.
   * @param  {string}                     name  Is a string to identify the Group Chat
   * @param  {array<URL.userURL>}         users Array of users to be invited to join the Group Chat. Users are identified with reTHINK User URL, like this format user://<ipddomain>/<user-identifier>
   * @return {<Promise>ChatController}    A ChatController object as a Promise.
   */
  create(name, users, domains) {

    let _this = this;
    let syncher = _this._syncher;

    return new Promise(function(resolve, reject) {

      // Create owner participant
      _this.communicationObject.owner = _this._hypertyURL;
      _this.communicationObject.name = name;
      _this.communicationObject.id = name;
      _this.communicationObject.resources = ['chat'];
      _this.communicationObject.children = [];
      _this.communicationObject.status = CommunicationStatus.OPEN;
      _this.communicationObject.startingTime = new Date().toJSON();
      _this.communicationObject.lastModified = _this.communicationObject.startingTime;

      _this.search.myIdentity().then((identity) => {

        // Add my identity
        _this.communicationObject.participants.push(identity);

        console.info('[GroupChatManager] searching ' + users + ' at domain ' + domains);

        let usersSearch = _this.search.users(users, domains, ['comm'], ['chat']);
        console.log('usersSearch->', usersSearch);
        return usersSearch;
      }).then((hypertiesIDs) => {
        console.log('hypertiesIDS',hypertiesIDs);

        let selectedHyperties = hypertiesIDs.map((hyperty) => {
          return hyperty.hypertyID;
        });

        console.info('[GroupChatManager] ------------------------ Syncher Create ---------------------- \n');
        console.info('[GroupChatManager] Selected Hyperties: !!! ', selectedHyperties);
        console.info(`Have ${selectedHyperties.length} users;`);

        if(hypertiesIDs[0] && typeof(hypertiesIDs[0]) !== 'object' &&  hypertiesIDs[0].split('@').length > 1) {
          console.log('here');
          return syncher.create(_this._objectDescURL, hypertiesIDs, _this.communicationObject);
        } else {
          console.log('here2');
          return syncher.create(_this._objectDescURL, selectedHyperties, _this.communicationObject);
        }

      }).catch((reason) => {
        console.log('Error:', reason);
      }).then(function(dataObjectReporter) {
        console.info('[GroupChatManager] 3. Return Create Data Object Reporter', dataObjectReporter);

        let chatController = new ChatController(syncher, _this.discovery, _this._domain, _this.search);
        chatController.dataObjectReporter = dataObjectReporter;
        _this.chatController = chatController;
        resolve(chatController);

      }).catch(function(reason) {
        reject(reason);
      });
    });

  }

  /**
   * This function is used to handle notifications about incoming invitations to join a Group Chat.
   * @param  {Function} CreateEvent The CreateEvent fired by the Syncher when an invitaion is received
   */
  onInvitation(callback) {
    let _this = this;
    _this._onInvitation = callback;
  }

  /**
   * This function is used to join a Group Chat.
   * @param  {URL.CommunicationURL} invitationURL  The Communication URL of the Group Chat to join that is provided in the invitation event
   * @return {<Promise>ChatController}             It returns the ChatController object as a Promise
   */
  join(invitationURL) {
    let _this = this;
    let syncher = _this._syncher;

    return new Promise(function(resolve, reject) {

      console.info('[GroupChatManager] ------------------------ Syncher subscribe ---------------------- \n');
      console.info(invitationURL);

      syncher.subscribe(_this._objectDescURL, invitationURL).then(function(dataObjectObserver) {
        console.info('[GroupChatManager] Data Object Observer: ', dataObjectObserver);
        let chatController = new ChatController(syncher, _this.discovery, _this._domain, _this.search);
        resolve(chatController);

        chatController.dataObjectObserver = dataObjectObserver;

      }).catch(function(reason) {
        reject(reason);
      });
    });

  }

}

export default function activate(hypertyURL, bus, configuration) {

  return {
    name: 'GroupChatManager',
    instance: new GroupChatManager(hypertyURL, bus, configuration)
  };

}
<|endoftext|>