<commit_before>/********************************************************
 * Copyright (c) 2013 Azavea, Inc.
 * See LICENSE in the project root for copying permission
 * Location with definition of location objects and
 * data loader object
 *********************************************************/

define(['jquery', 'Leaflet', 'favorites'], function($, L, favorites) {

    /*
     * Constructor for location
     * data -- object returned from the /location/api/<id>/ url
     */
    var Location = function(data){
        if (data.position && data.item) {
            this.data = data;
        } else {
            throw "CELLocationInvalid";
        }

        this.mapMarker = null;
    };

    /*
     * Return ID of the location
     *      shortcut if we need more functionality later
     */
    Location.prototype.getId = function() {
        return this.data.item.key;
    };

    /*
     * Checks if location is starred
     * Returns:
     *      true if starred
     *      false if not starred
     */
    Location.prototype.isStarred = function() {
        return favorites.isStarred(this.getId());
    };

    /*
     * Add location to cookie string
     */
    Location.prototype.setStarred = function(add) {
        if (add) {
            favorites.addIdToCookie(this.getId());
        } else {
            favorites.removeIdFromCookie(this.getId());
        }
    };

    /*
     * Is the location accredited?
     */
    Location.prototype.isAccredited = function() {
        var isAccredited = false;
        $.each(this.data.sfields, function(key, value) {
            if (value.fieldname === "Accreditation" && value.value !== "None") {
                isAccredited = true;
                return false;
            }
        });
        return isAccredited;
    };

    /*
     * Determines if the location is a school or not
     * Returns:
     *      true if school
     *      false if not
     */
    Location.prototype.isSchool = function() {
        return true;
    };

    /* Function that returns appropriate icon based on
     * a location's properties
     */
    Location.prototype.getIcon = function(options) {
        // TODO: add a cache for these icons

        var doubleDimensions = function(option) {
            option[0] *=2;
            option[1] *=2;
        };

        var setHighlighted = function(options) {
            that.doubleDimensions(options.iconSize);
            that.doubleDimensions(options.shadowSize);
            that.doubleDimensions(options.iconAnchor);
            that.doubleDimensions(options.shadowAnchor);
            that.doubleDimensions(options.popupAnchor);
            options.iconUrl.replace(".png", "@2x.png");
            options.shadowUrl.replace(".png", "@2x.png");
        };

        var defaults = {
            key: null,
            highlighted: false,
            iconUrl: '/static/img/leaflet-icons/marker-icon.png',
            shadowUrl: '/static/img/leaflet-icons/marker-shadow.png',
            iconSize: [35, 45],
            shadowSize: [41, 41],
            iconAnchor: [10, 60],
            shadowAnchor: [4, 62],
            popupAnchor: [10, -60]
        };
        var iconOpts = $.extend({}, defaults, options),
            key = '';

        // build a key!
        if (iconOpts.key) {
            key = iconOpts.key;
        } else {
            key += this.isSchool() ? 'school' : 'center';
            key += this.isAccredited() ? '-accredited' : '';
            key += this.isStarred() ? '-starred' : '';
        }

        switch (key) {
            case 'school':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'school-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'school-accredited':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'school-accredited-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'center':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'center-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'center-accredited':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'center-accredited-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'geolocation':
                break;
            default:
                return null;
        }

        // highlighting means we double all dimensions and replace the icon with @2x
        if (iconOpts.highlighted) {
            iconOpts = setHighlighted(iconOpts);
        }

        var icon = L.icon(iconOpts);
        return icon;
    };

    /*
     * Set the locations map marker icon
     */
    Location.prototype.setIcon = function(options) {
        var icon = this.getIcon(options);
        if (this.mapMarker) {
            this.mapMarker.setIcon(icon);
        }
    };

    /*
     * Get location marker object
     */
    Location.prototype.getMarker = function() {
        return this.mapMarker || null;
    };

    /*
     * Input: Leaflet Marker Icons object
     * Output: Reference to the created marker
     * If map marker already exists, updates existing marker icon
     * If map marker does not exist, creates marker with proper icon
     */
    Location.prototype.setMarker = function(options) {
        var icon = this.getIcon(options);
        var marker = this.getMarker();
        if (marker) {
            marker.setIcon(icon);
        } else {
            this.mapMarker = new L.Marker(this.getLatLng(), { icon: icon });
            // TODO: clear up popupTemplate
            this.mapMarker.bindPopup(DataManager.popupTemplate(this.data), {key: this.getId()});
        }
    };

    Location.prototype.getLatLng = function() {
        return new L.LatLng(this.data.position.lat, this.data.position.lng);
    };

    /* Sets boolean value for if location should be on list
     */
    Location.prototype.isWithinMapBounds = function(map){
        var mapBounds = map.getBounds();
        return mapBounds.contains(this.getLatLng());
    };

    var DataLoader = {

        iconcache: {},

        /* Updates if location is shown in map and list based on
         * applied filters and bounding box of map
         */
        locationUpdate: function(filters, map) {},

        /* Updates school counts for neighborhoods based on
         * filters
         *
         * @param {Filters taken from filter-list/model} filters
         */
        neighborhoodUpdate: function(filters, neighborhoodLayer) {},

        /* Updates locations when zoom changes
         */
        onZoomChange: function(map) {},

        idleListener: function(map) {}, // Listens to map for zoom and movement

        filterListener: function() {}, // listens to filters for changes

        /* Map of locations & neighborhoods, key is the id for
         * each object
         */
        locations: {},
        neighborhood: {},

        events: {zoomChanged: 'zoomChanged',
                 dataReady: 'dataReady'
                }
    };

    return {Location: Location,
            DataLoader:  DataLoader};

});

<commit_msg>Cleanup of location and add simple icon cache<commit_after>/********************************************************
 * Copyright (c) 2013 Azavea, Inc.
 * See LICENSE in the project root for copying permission
 * Location with definition of location objects and
 * data loader object
 *********************************************************/

define(['jquery', 'Leaflet', 'favorites'], function($, L, favorites) {

    /*
     * Constructor for location
     * data -- object returned from the /location/api/<id>/ url
     */
    var Location = function(data){
        if (data.position && data.item) {
            this.data = data;
        } else {
            throw "CELLocationInvalid";
        }

        this.mapMarker = null;
    };

    /*
     * Return ID of the location
     *      shortcut if we need more functionality later
     */
    Location.prototype.getId = function() {
        return this.data.item.key;
    };

    /*
     * Checks if location is starred
     * Returns:
     *      true if starred
     *      false if not starred
     */
    Location.prototype.isStarred = function() {
        return favorites.isStarred(this.getId());
    };

    /*
     * Add location to cookie string
     */
    Location.prototype.setStarred = function(add) {
        if (add) {
            favorites.addIdToCookie(this.getId());
        } else {
            favorites.removeIdFromCookie(this.getId());
        }
    };

    /*
     * Is the location accredited?
     */
    Location.prototype.isAccredited = function() {
        var isAccredited = false;
        $.each(this.data.sfields, function(key, value) {
            if (value.fieldname === "Accreditation" && value.value !== "None") {
                isAccredited = true;
                return false;
            }
        });
        return isAccredited;
    };

    /*
     * Determines if the location is a school or not
     * Returns:
     *      true if school
     *      false if not
     */
    Location.prototype.isSchool = function() {
        return true;
    };

    /* Function that returns appropriate icon based on
     * a location's properties
     */
    Location.prototype.getIcon = function(options) {

        var doubleDimensions = function(option) {
            option[0] *=2;
            option[1] *=2;
        };

        var setHighlighted = function(options) {
            that.doubleDimensions(options.iconSize);
            that.doubleDimensions(options.shadowSize);
            that.doubleDimensions(options.iconAnchor);
            that.doubleDimensions(options.shadowAnchor);
            that.doubleDimensions(options.popupAnchor);
            options.iconUrl.replace(".png", "@2x.png");
            options.shadowUrl.replace(".png", "@2x.png");
        };

        var defaults = {
            key: null,
            highlighted: false,
            iconUrl: '/static/img/leaflet-icons/marker-icon.png',
            shadowUrl: '/static/img/leaflet-icons/marker-shadow.png',
            iconSize: [35, 45],
            shadowSize: [41, 41],
            iconAnchor: [10, 60],
            shadowAnchor: [4, 62],
            popupAnchor: [10, -60]
        };
        var iconOpts = $.extend({}, defaults, options),
            key = '',
            cacheKey;

        // build a key!
        if (iconOpts.key) {
            key = iconOpts.key;
        } else {
            key += this.isSchool() ? 'school' : 'center';
            key += this.isAccredited() ? '-accredited' : '';
            key += this.isStarred() ? '-starred' : '';
        }

        // cache the icon with a simple key
        cacheKey = key;
        if (iconOpts.highlighted) {
            cacheKey += '-highlighted';
        }
        if (dataManager.iconcache[cacheKey]) {
            return dataManager.iconcache[cacheKey];
        }

        switch (key) {
            case 'school':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'school-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'school-accredited':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'school-accredited-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-school.png'});
                break;
            case 'center':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'center-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'center-accredited':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'center-accredited-starred':
                $.extend(iconOpts, {iconUrl: '/static/img/leaflet-icons/marker-home.png'});
                break;
            case 'geolocation':
                break;
            default:
                return null;
        }

        // highlighting means we double all dimensions and replace the icon with @2x
        if (iconOpts.highlighted) {
            iconOpts = setHighlighted(iconOpts);
        }

        var icon = L.icon(iconOpts);
        dataManager.iconcache[cacheKey] = icon;
        return icon;
    };

    /*
     * Set the locations map marker icon
     */
    Location.prototype.setIcon = function(options) {
        if (this.mapMarker) {
            var icon = this.getIcon(options);
            this.mapMarker.setIcon(icon);
        }
    };

    /*
     * Get location marker object
     */
    Location.prototype.getMarker = function() {
        return this.mapMarker || null;
    };

    /*
     * Input: Leaflet Marker Icons object
     * Output: Reference to the created marker
     * If map marker already exists, updates existing marker icon
     * If map marker does not exist, creates marker with proper icon
     */
    Location.prototype.setMarker = function(options) {
        var icon = this.getIcon(options);
        var marker = this.getMarker();
        if (marker) {
            marker.setIcon(icon);
        } else {
            this.mapMarker = new L.Marker(this.getLatLng(), { icon: icon });
            this.mapMarker.bindPopup(dataManager.popupTemplate(this.data), {key: this.getId()});
        }
    };

    Location.prototype.getLatLng = function() {
        return new L.LatLng(this.data.position.lat, this.data.position.lng);
    };

    /* Sets boolean value for if location should be on list
     */
    Location.prototype.isWithinMapBounds = function(map){
        var mapBounds = map.getBounds();
        return mapBounds.contains(this.getLatLng());
    };

    var DataLoader = {

        iconcache: {},

        /* Updates if location is shown in map and list based on
         * applied filters and bounding box of map
         */
        locationUpdate: function(filters, map) {},

        /* Updates school counts for neighborhoods based on
         * filters
         *
         * @param {Filters taken from filter-list/model} filters
         */
        neighborhoodUpdate: function(filters, neighborhoodLayer) {},

        /* Updates locations when zoom changes
         */
        onZoomChange: function(map) {},

        idleListener: function(map) {}, // Listens to map for zoom and movement

        filterListener: function() {}, // listens to filters for changes

        /* Map of locations & neighborhoods, key is the id for
         * each object
         */
        locations: {},
        neighborhood: {},

        events: {zoomChanged: 'zoomChanged',
                 dataReady: 'dataReady'
                }
    };

    return {Location: Location,
            DataLoader:  DataLoader};

});

<|endoftext|>


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

/*global $, Config */

var sakai = sakai || {};

/**
 * @name sakai.creategroup
 *
 * @class creategroup
 *
 * @description
 * Creategroup widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.creategroup = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var MAX_LENGTH = 30;

    // - ID
    var createGroup = "#creategroup";

    // Container
    var createGroupContainer = createGroup + "_container";

    // template containers
    var createGroupAddTemplate = "#noncourse_template_container";

    // Non course
    var createGroupAdd = createGroup + "_add";
    var createGroupAddCancel = createGroupAdd + "_cancel";
    var createGroupAddDescription = createGroupAdd + "_description";
    var createGroupAddId = createGroupAdd + "_id";
    var createGroupAddName = createGroupAdd + "_name";
    var createGroupAddProcess = createGroupAdd + "_process";
    var createGroupAddSave = createGroupAdd + "_save";
    var createGroupAddUrl = createGroupAdd + "_url";
    var createGroupAddUrlLength = createGroupAddUrl + "_length";
    var createGroupAddUrlMaxLength = createGroupAddUrl + "_max_length";

    // Error fields
    var createGroupAddNameEmpty = createGroupAddName + "_empty";
    var createGroupAddNameShort = createGroupAddName + "_short";
    var createGroupAddIdEmpty = createGroupAddId + "_empty";
    var createGroupAddIdTaken = createGroupAddId + "_taken";
    var createGroupAddIdShort = createGroupAddId + "_short";
    var errorFields = ".creategroup_error_msg";

    // CSS Classes
    var invalidFieldClass = "invalid";

    // Pages to be added to the group
    var pagestemplate = "defaultgroup";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Public function that can be called from elsewhere
     * (e.g. chat and sites widget)
     * It initializes the creategroup widget and shows the jqmodal (ligthbox)
     */
    sakai.creategroup.initialise = function(){
        $(".description_fields").show();
        $("#creategroup_add_container").show();
        $(createGroupAddTemplate).show();
        $(createGroupAddName).val("");
        $(createGroupAddDescription).val("");
        $(createGroupAddId).val("");
        $(createGroupContainer).jqmShow();
    };

    /**
     * Show or hide the process div and hide/shows the buttons
     * @param {Boolean} show
     *     true: show the process div and hide the buttons
     *     false: hide the process div and show the buttons
     */
    var showProcess = function(show){
        if(show){
            $(createGroupAddCancel).hide();
            $(createGroupAddSave).hide();
            $(createGroupAddProcess).show();
        } else {
            $(createGroupAddProcess).hide();
            $(createGroupAddCancel).show();
            $(createGroupAddSave).show();
        }
    };

    /**
     * Replace or remove malicious characters from the string
     * We use this function to modify the groupid
     * String to test against: test :?=&;\/?@+$<>#%'"''{}|\\^[]'
     * @param {Object} input The string where the characters need to be replaced
     */
    var replaceCharacters = function(input){

        input = $.trim(input); // Remove the spaces at the beginning and end of the id

        input = input.toLowerCase().replace(/ /g,"-");
        input = input.toLowerCase().replace(/'/g,"");
        input = input.toLowerCase().replace(/"/g,"");

        var regexp = new RegExp("[^a-z0-9]", "gi");
        input = input.replace(regexp,"_");

        return input;
    };

    ////////////////////
    // Error handling //
    ////////////////////

    var resetErrorFields = function(){
        $("input").removeClass(invalidFieldClass);
        $("textarea").removeClass(invalidFieldClass);
        $(errorFields).hide();
    };

    /**
     * Function that will visually mark a form field as an
     * invalid field.
     * @param String field
     *  JQuery selector of the input box we want to show as invalid
     * @param String errorField
     *  JQuery selector of the error message that needs to be shown.
     * @param boolean noReset
     *  Parameter that specifies whether we need to make all of the
     *  fiels valid again first
     */
    var setError = function(field,errorField, noReset){
        if (!noReset) {
            resetErrorFields();
        }
        $(field).addClass(invalidFieldClass);
        $(errorField).show();
    };

    var myClose = function(hash) {
        resetErrorFields();
        hash.o.remove();
        hash.w.hide();
    };

    ///////////////////
    // Create a group //
    ///////////////////

    /**
     * Check if the group is created correctly and exists
     * @param {String} groupid
     */
    var doCheckGroup = function(groupid){
        // Check if the group exists.
        var groupExists = false;

        $.ajax({
            url: "/~" + groupid + ".json",
            type: "GET",
            async: false,
            success: function(data, textStatus){
                groupExists = true;
            },
            error: function(xhr, textStatus, thrownError){
                groupExists = true;
            }
        });
        return groupExists;
    };

    /**
     * Create the group.
     * @param {String} groupid the id of the group that's being created
     * @param {String} grouptitle the title of the group that's being created
     * @param {String} groupdescription the description of the group that's being created
     * @param {String} groupidManagers the id of the managers group for the group that's being created
    */
    var doSaveGroup = function(groupid, grouptitle, groupdescription){
    // Create a group with the managers group

        $.ajax({
            url: sakai.config.URL.GROUP_CREATE_SERVICE,
            data: {
                "_charset_":"utf-8",
                ":name": groupid,
                ":sakai:manager": sakai.data.me.user.userid,
                "sakai:group-title" : grouptitle,
                "sakai:group-description" : groupdescription,
                "sakai:group-id": groupid,
                ":sakai:pages-template": "/var/templates/site/" + pagestemplate,
                "sakai:pages-visible": sakai.config.Permissions.Groups.visible["public"]
            },
            type: "POST",
            success: function(data, textStatus){
                // check if the group exists
                if (doCheckGroup(groupid)) {
                    // set default permissions for this group
                    sakai.api.Groups.setPermissions(groupid,
                        sakai.config.Permissions.Groups.joinable.manager_add,
                        sakai.config.Permissions.Groups.visible["public"],
                        function (success, errorMessage) {
                            if(success) {
                                // show the group
                                document.location = "/~" + groupid;
                            } else {
                                fluid.log("creategroup.js doSaveGroup failed to set group permissions: " + errorMessage);
                            }
                        });
                }
            },
            error: function(xhr, textStatus, thrownError){
                var groupCheck = doCheckGroup(groupid);
                if (groupCheck){
                    setError(createGroupAddId,createGroupAddIdTaken,true);
                } else {
                    fluid.log("An error has occurred: " + xhr.status + " " + xhr.statusText);
                }
                showProcess(false);
            }
        });
    };

    var saveGroup = function(){
        resetErrorFields();

        // Get the values from the input text and radio fields
        var grouptitle = $(createGroupAddName).val() || "";
        var groupdescription = $(createGroupAddDescription).val() || "";
        var groupid = replaceCharacters($(createGroupAddId).val());
        var inputError = false;

        // Check if there is a group id or group title defined
        if (grouptitle === "")
        {
            setError(createGroupAddName,createGroupAddNameEmpty,true);
            inputError = true;
        } else if (grouptitle.length < 3) {
            setError(createGroupAddName,createGroupAddNameShort,true);
            inputError = true;
        }
        if (!groupid)
        {
            setError(createGroupAddId,createGroupAddIdEmpty,true);
            inputError = true;
        } else if (groupid.length < 3) {
            setError(createGroupAddId,createGroupAddIdShort,true);
            inputError = true;
        }

        if (inputError)
        {
            return;
        }
        else
        {
            // Hide the buttons and show the process status
            showProcess(true);
            doSaveGroup(groupid, grouptitle, groupdescription);
        }
    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /*
     * Add jqModal functionality to the container.
     * This makes use of the jqModal (jQuery Modal) plugin that provides support
     * for lightboxes
     */
    $(createGroupContainer).jqm({
        modal: true,
        overlay: 20,
        toTop: true,
        onHide: myClose
    });

    /*
     * Add binding to the save button (create the group when you click on it)
     */
    $(createGroupAddSave).click(function(){
        var entered = replaceCharacters($(createGroupAddId).val());
        $(createGroupAddId).val(entered);
        saveGroup();
    });

    /*
     * When you change something in the name of the group, it first removes the bad characters
     * and then it shows the edited url in the span
     */
    $(createGroupAddName + "," + createGroupAddId).bind("keyup", function(ev){
        var entered = replaceCharacters($(this).val());
        $(createGroupAddId).val(entered);
    });

    /*
     *  When user change the group url address, reset error fields.
     */
    $(createGroupAddId).bind("keyup",function(ev){
        // reset error field.
        resetErrorFields();
    });

    /////////////////////////////
    // Initialisation function //
    /////////////////////////////

    var doInit = function(){

        // Hide error fields at start
        $(errorFields).hide();

        // Set the text of the span containing the url of the current group
        // e.g. http://celestine.caret.local:8080/~
        var url = document.location.protocol + "//" + document.location.host;
        url += "/~";
        // get max length value
        var maxLength = parseInt(MAX_LENGTH,10);

        // get length

        // if url is too long greater than 30 character
        // show only first 15 characters +...+ last 15 characters
        // e.g.http://sakai3-demo.uits.indiana.edu:8080/~
        // it will change to shorter form:
        // http://sakai3-...diana.edu:8080/~
        if (url.length > maxLength) {
            url = url.substr(0,15)+ "..."+ url.substr(url.length-15,url.length-1);
        }

        $(createGroupAddUrl).text(sakai.api.Security.saneHTML(url));
    };

    doInit();
};

sakai.api.Widgets.widgetLoader.informOnLoad("creategroup");<commit_msg>SAKIII-1218 a group url auto fill on change of group name.<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

/*global $, Config */

var sakai = sakai || {};

/**
 * @name sakai.creategroup
 *
 * @class creategroup
 *
 * @description
 * Creategroup widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.creategroup = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var MAX_LENGTH = 30;

    // - ID
    var createGroup = "#creategroup";

    // Container
    var createGroupContainer = createGroup + "_container";

    // template containers
    var createGroupAddTemplate = "#noncourse_template_container";

    // Non course
    var createGroupAdd = createGroup + "_add";
    var createGroupAddCancel = createGroupAdd + "_cancel";
    var createGroupAddDescription = createGroupAdd + "_description";
    var createGroupAddId = createGroupAdd + "_id";
    var createGroupAddName = createGroupAdd + "_name";
    var createGroupAddProcess = createGroupAdd + "_process";
    var createGroupAddSave = createGroupAdd + "_save";
    var createGroupAddUrl = createGroupAdd + "_url";
    var createGroupAddUrlLength = createGroupAddUrl + "_length";
    var createGroupAddUrlMaxLength = createGroupAddUrl + "_max_length";

    // Error fields
    var createGroupAddNameEmpty = createGroupAddName + "_empty";
    var createGroupAddNameShort = createGroupAddName + "_short";
    var createGroupAddIdEmpty = createGroupAddId + "_empty";
    var createGroupAddIdTaken = createGroupAddId + "_taken";
    var createGroupAddIdShort = createGroupAddId + "_short";
    var errorFields = ".creategroup_error_msg";

    // CSS Classes
    var invalidFieldClass = "invalid";

    // Pages to be added to the group
    var pagestemplate = "defaultgroup";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Public function that can be called from elsewhere
     * (e.g. chat and sites widget)
     * It initializes the creategroup widget and shows the jqmodal (ligthbox)
     */
    sakai.creategroup.initialise = function(){
        $(".description_fields").show();
        $("#creategroup_add_container").show();
        $(createGroupAddTemplate).show();
        $(createGroupAddName).val("");
        $(createGroupAddDescription).val("");
        $(createGroupAddId).val("");
        $(createGroupContainer).jqmShow();
    };

    /**
     * Show or hide the process div and hide/shows the buttons
     * @param {Boolean} show
     *     true: show the process div and hide the buttons
     *     false: hide the process div and show the buttons
     */
    var showProcess = function(show){
        if(show){
            $(createGroupAddCancel).hide();
            $(createGroupAddSave).hide();
            $(createGroupAddProcess).show();
        } else {
            $(createGroupAddProcess).hide();
            $(createGroupAddCancel).show();
            $(createGroupAddSave).show();
        }
    };

    /**
     * Replace or remove malicious characters from the string
     * We use this function to modify the groupid
     * String to test against: test :?=&;\/?@+$<>#%'"''{}|\\^[]'
     * @param {Object} input The string where the characters need to be replaced
     */
    var replaceCharacters = function(input){

        input = $.trim(input); // Remove the spaces at the beginning and end of the id

        input = input.toLowerCase().replace(/ /g,"-");
        input = input.toLowerCase().replace(/'/g,"");
        input = input.toLowerCase().replace(/"/g,"");

        var regexp = new RegExp("[^a-z0-9]", "gi");
        input = input.replace(regexp,"_");

        return input;
    };

    ////////////////////
    // Error handling //
    ////////////////////

    var resetErrorFields = function(){
        $("input").removeClass(invalidFieldClass);
        $("textarea").removeClass(invalidFieldClass);
        $(errorFields).hide();
    };

    /**
     * Function that will visually mark a form field as an
     * invalid field.
     * @param String field
     *  JQuery selector of the input box we want to show as invalid
     * @param String errorField
     *  JQuery selector of the error message that needs to be shown.
     * @param boolean noReset
     *  Parameter that specifies whether we need to make all of the
     *  fiels valid again first
     */
    var setError = function(field,errorField, noReset){
        if (!noReset) {
            resetErrorFields();
        }
        $(field).addClass(invalidFieldClass);
        $(errorField).show();
    };

    var myClose = function(hash) {
        resetErrorFields();
        hash.o.remove();
        hash.w.hide();
    };

    ///////////////////
    // Create a group //
    ///////////////////

    /**
     * Check if the group is created correctly and exists
     * @param {String} groupid
     */
    var doCheckGroup = function(groupid){
        // Check if the group exists.
        var groupExists = false;

        $.ajax({
            url: "/~" + groupid + ".json",
            type: "GET",
            async: false,
            success: function(data, textStatus){
                groupExists = true;
            },
            error: function(xhr, textStatus, thrownError){
                groupExists = true;
            }
        });
        return groupExists;
    };

    /**
     * Create the group.
     * @param {String} groupid the id of the group that's being created
     * @param {String} grouptitle the title of the group that's being created
     * @param {String} groupdescription the description of the group that's being created
     * @param {String} groupidManagers the id of the managers group for the group that's being created
    */
    var doSaveGroup = function(groupid, grouptitle, groupdescription){
    // Create a group with the managers group

        $.ajax({
            url: sakai.config.URL.GROUP_CREATE_SERVICE,
            data: {
                "_charset_":"utf-8",
                ":name": groupid,
                ":sakai:manager": sakai.data.me.user.userid,
                "sakai:group-title" : grouptitle,
                "sakai:group-description" : groupdescription,
                "sakai:group-id": groupid,
                ":sakai:pages-template": "/var/templates/site/" + pagestemplate,
                "sakai:pages-visible": sakai.config.Permissions.Groups.visible["public"]
            },
            type: "POST",
            success: function(data, textStatus){
                // check if the group exists
                if (doCheckGroup(groupid)) {
                    // set default permissions for this group
                    sakai.api.Groups.setPermissions(groupid,
                        sakai.config.Permissions.Groups.joinable.manager_add,
                        sakai.config.Permissions.Groups.visible["public"],
                        function (success, errorMessage) {
                            if(success) {
                                // show the group
                                document.location = "/~" + groupid;
                            } else {
                                fluid.log("creategroup.js doSaveGroup failed to set group permissions: " + errorMessage);
                            }
                        });
                }
            },
            error: function(xhr, textStatus, thrownError){
                var groupCheck = doCheckGroup(groupid);
                if (groupCheck){
                    setError(createGroupAddId,createGroupAddIdTaken,true);
                } else {
                    fluid.log("An error has occurred: " + xhr.status + " " + xhr.statusText);
                }
                showProcess(false);
            }
        });
    };

    var saveGroup = function(){
        resetErrorFields();

        // Get the values from the input text and radio fields
        var grouptitle = $(createGroupAddName).val() || "";
        var groupdescription = $(createGroupAddDescription).val() || "";
        var groupid = replaceCharacters($(createGroupAddId).val());
        var inputError = false;

        // Check if there is a group id or group title defined
        if (grouptitle === "")
        {
            setError(createGroupAddName,createGroupAddNameEmpty,true);
            inputError = true;
        } else if (grouptitle.length < 3) {
            setError(createGroupAddName,createGroupAddNameShort,true);
            inputError = true;
        }
        if (!groupid)
        {
            setError(createGroupAddId,createGroupAddIdEmpty,true);
            inputError = true;
        } else if (groupid.length < 3) {
            setError(createGroupAddId,createGroupAddIdShort,true);
            inputError = true;
        }

        if (inputError)
        {
            return;
        }
        else
        {
            // Hide the buttons and show the process status
            showProcess(true);
            doSaveGroup(groupid, grouptitle, groupdescription);
        }
    };

    ////////////////////
    // Event Handlers //
    ////////////////////

    /*
     * Add jqModal functionality to the container.
     * This makes use of the jqModal (jQuery Modal) plugin that provides support
     * for lightboxes
     */
    $(createGroupContainer).jqm({
        modal: true,
        overlay: 20,
        toTop: true,
        onHide: myClose
    });

    /*
     * Add binding to the save button (create the group when you click on it)
     */
    $(createGroupAddSave).click(function(){
        var entered = replaceCharacters($(createGroupAddId).val());
        $(createGroupAddId).val(entered);
        saveGroup();
    });

    /*
     * When you change something in the name of the group, it first removes the bad characters
     * and then it shows the edited url in the span
     */
    $(createGroupAddName + "," + createGroupAddId).bind("keyup change", function(ev){
        var entered = replaceCharacters($(this).val());
        $(createGroupAddId).val(entered);
    });

    /*
     *  When user change the group url address, reset error fields.
     */
    $(createGroupAddId).bind("keyup",function(ev){
        // reset error field.
        resetErrorFields();
    });

    /////////////////////////////
    // Initialisation function //
    /////////////////////////////

    var doInit = function(){

        // Hide error fields at start
        $(errorFields).hide();

        // Set the text of the span containing the url of the current group
        // e.g. http://celestine.caret.local:8080/~
        var url = document.location.protocol + "//" + document.location.host;
        url += "/~";
        // get max length value
        var maxLength = parseInt(MAX_LENGTH,10);

        // get length

        // if url is too long greater than 30 character
        // show only first 15 characters +...+ last 15 characters
        // e.g.http://sakai3-demo.uits.indiana.edu:8080/~
        // it will change to shorter form:
        // http://sakai3-...diana.edu:8080/~
        if (url.length > maxLength) {
            url = url.substr(0,15)+ "..."+ url.substr(url.length-15,url.length-1);
        }

        $(createGroupAddUrl).text(sakai.api.Security.saneHTML(url));
    };

    doInit();
};

sakai.api.Widgets.widgetLoader.informOnLoad("creategroup");<|endoftext|>


<commit_before>// For an introduction to the Navigation template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232506
(function () {
    "use strict";

    var app = WinJS.Application;
    var activation = Windows.ApplicationModel.Activation;
    var nav = WinJS.Navigation;
    WinJS.strictProcessing();

    window.onkeyup = function (e) {
        //TODO: check to be sure we're not in an input box before we capture some (any?) keystrokes (like home and backspace)
        switch(e.key) {
            //case "Home": WinJS.Navigation.navigate("/pages/home/home.html"); break;
            case "BrowserBack": WinJS.Navigation.back(); break;
            case "BrowserForward": WinJS.Navigation.forward(); break;
        }
    };

    app.addEventListener("activated", function (args) {

        //set up the demos list (empty for now)
        app.demosList = new WinJS.Binding.List()
            .createGrouped(function (i) { return i.group; }, function (i) { return i.group; })
            .createSorted(function (a, b) { return (a.name < b.name ? -1 : 1); });

        //start loading the demos
        app.demosLoaded = loadDemos();

        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                // TODO: This application has been newly launched. Initialize
                // your application here.
            } else {
                // TODO: This application has been reactivated from suspension.
                // Restore application state here.
            }

            if (app.sessionState.history) {
                nav.history = app.sessionState.history;
            }
            args.setPromise(WinJS.UI.processAll().then(function () {
                if (nav.location) {
                    nav.history.current.initialPlaceholder = true;
                    return nav.navigate(nav.location, nav.state);
                } else {
                    return nav.navigate(Application.navigator.home);
                }
            }));
        }
    });

    app.oncheckpoint = function (args) {
        // To complete an async operation before your app is suspended, call args.setPromise().
        app.sessionState.history = nav.history;
    };

    app.onready = function (e) {
        addSearchContract();
        addSettingsContract();
    };

    app.tileColor = "#0098ab";
    app.demosList = null;
    app.demosLoaded = null;

    function addSearchContract() {
        var searchPane = Windows.ApplicationModel.Search.SearchPane.getForCurrentView();

        //make sure demos have been loaded and then make search terms out of their keywords
        app.demosLoaded.then(function () {
            var tags = [];
            app.demosList.forEach(function (d) {
                if (d.tags)
                    d.tags.split(" ").forEach(function (t) {
                        tags.push(t);
                    });
            });

            searchPane.onsuggestionsrequested = function (e) {
                e.request.searchSuggestionCollection.appendQuerySuggestions(
                    tags.distinct().filter(function (t) { return t.startsWith(e.queryText); })
                );
            };

            searchPane.onquerysubmitted = function (e) {
                WinJS.Navigation.navigate("/pages/home/home.html", { queryText: e.queryText });
            };

        });
    }
    function addSettingsContract() {
        app.onsettings = function (e) {
            e.detail.applicationcommands = { "preferencesDiv": { title: "Preferences", href: "/pages/settings/settings.html" } };
            WinJS.UI.SettingsFlyout.populateSettings(e);
        };
    }
    function loadDemos() {
        return new WinJS.Promise(function(c, e, p) {
            Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("pages")
                .then(function (pagesFolder) {
                    return pagesFolder.getFoldersAsync();
                })
                .done(function (folders) {
                    var result = [];
                    var promises = [];
                    folders
                        .filter(function (f) { return f.name !== "home"; })
                        .forEach(function (f) {
                            promises.push(WinJS.xhr({ url: "/pages/" + f.name + "/" + f.name + ".html", responseType: "document" })
                                .then(function (xhr) {
                                    var keywords = q("meta[name='keywords']", xhr.response);
                                    keywords = (keywords ? keywords.content : "");

                                    var enabled = q("meta[name='enabled']", xhr.response);
                                    enabled = (enabled ? enabled.content == "true" : "true");

                                    if (enabled) {
                                        var pageTitle = (q("title", xhr.response) ? q("title", xhr.response).innerText : (q(".pagetitle", xhr.response) ? q(".pagetitle", xhr.response).innerText : "Unnamed"));
                                        result.push({
                                            key: f.name,
                                            name: pageTitle,
                                            tags: keywords,
                                            group: "html",
                                            difficulty: 0
                                        });
                                    }
                                }));
                        });
                    WinJS.Promise.join(promises).then(function () {
                        var colors = ["#0098ab", "#0070a9", "#d9532c", "#a400ac", "#009086", "#5838b4", "#ae193e", "#2c86ee", "#009c00"];
                        result.forEach(function (i) {
                            //i.tileColor = colors[Math.floor(Math.random() * colors.length)];
                            i.tileColor = "#336666";
                        });
                        result.forEach(function (r) { app.demosList.push(r); });
                        c();
                    });
                });
        });
    }

    app.start();
})();

var q = Ocho.Utilities.query;
var format = Ocho.Utilities.format;

String.prototype.startsWith = Ocho.String.startsWith;
String.prototype.endsWith = Ocho.String.endsWith;
String.prototype.trim = Ocho.String.trim;
Array.prototype.contains = Ocho.Array.contains;
Array.prototype.distinct = Ocho.Array.distinct;<commit_msg>compare with lowercase to make lowercase demos show up correctly<commit_after>// For an introduction to the Navigation template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232506
(function () {
    "use strict";

    var app = WinJS.Application;
    var activation = Windows.ApplicationModel.Activation;
    var nav = WinJS.Navigation;
    WinJS.strictProcessing();

    window.onkeyup = function (e) {
        //TODO: check to be sure we're not in an input box before we capture some (any?) keystrokes (like home and backspace)
        switch(e.key) {
            //case "Home": WinJS.Navigation.navigate("/pages/home/home.html"); break;
            case "BrowserBack": WinJS.Navigation.back(); break;
            case "BrowserForward": WinJS.Navigation.forward(); break;
        }
    };

    app.addEventListener("activated", function (args) {

        //set up the demos list (empty for now)
        app.demosList = new WinJS.Binding.List()
            .createGrouped(function (i) { return i.group; }, function (i) { return i.group; })
            .createSorted(function (a, b) { return (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1); });

        //start loading the demos
        app.demosLoaded = loadDemos();

        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                // TODO: This application has been newly launched. Initialize
                // your application here.
            } else {
                // TODO: This application has been reactivated from suspension.
                // Restore application state here.
            }

            if (app.sessionState.history) {
                nav.history = app.sessionState.history;
            }
            args.setPromise(WinJS.UI.processAll().then(function () {
                if (nav.location) {
                    nav.history.current.initialPlaceholder = true;
                    return nav.navigate(nav.location, nav.state);
                } else {
                    return nav.navigate(Application.navigator.home);
                }
            }));
        }
    });

    app.oncheckpoint = function (args) {
        // To complete an async operation before your app is suspended, call args.setPromise().
        app.sessionState.history = nav.history;
    };

    app.onready = function (e) {
        addSearchContract();
        addSettingsContract();
    };

    app.tileColor = "#0098ab";
    app.demosList = null;
    app.demosLoaded = null;

    function addSearchContract() {
        var searchPane = Windows.ApplicationModel.Search.SearchPane.getForCurrentView();

        //make sure demos have been loaded and then make search terms out of their keywords
        app.demosLoaded.then(function () {
            var tags = [];
            app.demosList.forEach(function (d) {
                if (d.tags)
                    d.tags.split(" ").forEach(function (t) {
                        tags.push(t);
                    });
            });

            searchPane.onsuggestionsrequested = function (e) {
                e.request.searchSuggestionCollection.appendQuerySuggestions(
                    tags.distinct().filter(function (t) { return t.startsWith(e.queryText); })
                );
            };

            searchPane.onquerysubmitted = function (e) {
                WinJS.Navigation.navigate("/pages/home/home.html", { queryText: e.queryText });
            };

        });
    }
    function addSettingsContract() {
        app.onsettings = function (e) {
            e.detail.applicationcommands = { "preferencesDiv": { title: "Preferences", href: "/pages/settings/settings.html" } };
            WinJS.UI.SettingsFlyout.populateSettings(e);
        };
    }
    function loadDemos() {
        return new WinJS.Promise(function(c, e, p) {
            Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("pages")
                .then(function (pagesFolder) {
                    return pagesFolder.getFoldersAsync();
                })
                .done(function (folders) {
                    var result = [];
                    var promises = [];
                    folders
                        .filter(function (f) { return f.name !== "home"; })
                        .forEach(function (f) {
                            promises.push(WinJS.xhr({ url: "/pages/" + f.name + "/" + f.name + ".html", responseType: "document" })
                                .then(function (xhr) {
                                    var keywords = q("meta[name='keywords']", xhr.response);
                                    keywords = (keywords ? keywords.content : "");

                                    var enabled = q("meta[name='enabled']", xhr.response);
                                    enabled = (enabled ? enabled.content == "true" : "true");

                                    if (enabled) {
                                        var pageTitle = (q("title", xhr.response) ? q("title", xhr.response).innerText : (q(".pagetitle", xhr.response) ? q(".pagetitle", xhr.response).innerText : "Unnamed"));
                                        result.push({
                                            key: f.name,
                                            name: pageTitle,
                                            tags: keywords,
                                            group: "html",
                                            difficulty: 0
                                        });
                                    }
                                }));
                        });
                    WinJS.Promise.join(promises).then(function () {
                        var colors = ["#0098ab", "#0070a9", "#d9532c", "#a400ac", "#009086", "#5838b4", "#ae193e", "#2c86ee", "#009c00"];
                        result.forEach(function (i) {
                            //i.tileColor = colors[Math.floor(Math.random() * colors.length)];
                            i.tileColor = "#336666";
                        });
                        result.forEach(function (r) { app.demosList.push(r); });
                        c();
                    });
                });
        });
    }

    app.start();
})();

var q = Ocho.Utilities.query;
var format = Ocho.Utilities.format;

String.prototype.startsWith = Ocho.String.startsWith;
String.prototype.endsWith = Ocho.String.endsWith;
String.prototype.trim = Ocho.String.trim;
Array.prototype.contains = Ocho.Array.contains;
Array.prototype.distinct = Ocho.Array.distinct;<|endoftext|>


<commit_before>'use strict';

const co = require('co');
const request = require('supertest');
const sinon = require('sinon');
const antsort = require('antsort');

const plover = require('../../');

/* eslint no-console: 0, max-nested-callbacks: [2, 4] */

describe('components/core', function() {
  const settings = { applicationRoot: 'somepath' };

  describe('应用启动相关', function() {
    it('可以正常启动plover应用', function() {
      const app = plover(settings);
      app.server.should.be.instanceof(require('koa/lib/application'));
      app.start();
    });

    it('启动时需要等待异步流程初始化完毕才提供服务', function(done) {
      const app = plover(settings);
      const workDone = app.readyCallback('longtime service');
      setTimeout(() => {
        app.mywork = 'done!';
        workDone();
      }, 100);

      (app.mywork === undefined).should.be.true();
      app.start(() => {
        app.mywork.should.equal('done!');
        done();
      });
    });

    it('使用listen可以快速启动应用', function() {
      const app = plover(settings);
      return app.listen(1234);
    });
  });

  describe('app.addMiddleware(middleware, [options])', function() {
    it('添加中间件', function() {
      const app = plover(settings);

      app.addMiddleware(function* (next) {
        if (this.url === '/a') {
          this.body = 'hello world a';
        } else {
          yield* next;
        }
      });

      app.addMiddleware(function() {
        return function* (next) {
          if (this.url === '/b') {
            this.body = 'hello world b';
          } else {
            yield* next;
          }
        };
      });

      const agent = request(app.callback());

      return co(function* () {
        yield agent.get('/a').expect('hello world a');
        yield agent.get('/b').expect('hello world b');
      });
    });

    it('设置中间件级别', function() {
      const app = plover(settings);

      app.addMiddleware(function* (next) {
        this.list = [];
        yield* next;
        this.body = this.list.join(' ');
      }, 0);

      app.addMiddleware(function* (next) {
        this.list.push('Hello');
        yield* next;
      }, 2);

      app.addMiddleware(function* (next) {
        this.list.push('Plover');
        yield* next;
      }); // 3 for default

      return request(app.callback())
          .get('/').expect('Hello Plover');
    });

    it('可以使用before精确排序', function() {
      const app = plover(settings);

      app.addMiddleware(function* mycsrf() {
        if (this.ignoreCsrf) {
          this.body = 'ignore csrf';
        } else {
          this.body = 'invalid csrf';
        }
      });

      app.addMiddleware(function* (next) {
        this.ignoreCsrf = true;
        yield* next;
      }, { before: 'mycsrf' });

      return request(app.callback())
          .get('/').expect('ignore csrf');
    });
  });

  describe('app.middleware()', function() {
    it('接入到其他koa应用', function() {
      const papp = plover(settings);

      papp.addMiddleware(function* () {
        this.body = 'hello plover';
      });

      const app = require('koa')();
      app.use(papp.middleware());

      return request(app.callback())
          .get('/').expect('hello plover');
    });
  });

  describe('扩展Plover', function() {
    it('使用$mountMiddlewares覆盖接入中间件的逻辑', function() {
      const callback = sinon.spy();

      class App extends plover.Application {
        $mountMiddlewares(app, items) {
          callback();
          items = antsort(items, { defaultLevel: 3 });
          items.forEach(item => {
            app.use(item.module);
          });
        }
      }

      const app = new App(settings);

      app.addMiddleware(function* Hello() {
        this.body = 'hello';
      });

      app.start();
      callback.called.should.be.true();

      return request(app.callback())
        .get('/hello')
        .expect('hello');
    });
  });

  describe('环境相关', function() {
    it('开发环境时，异常会打印在页面上', function() {
      const app = plover(settings);

      app.addMiddleware(function* () {
        if (this.url === '/admin') {
          this.throw(401);
        } else {
          throw new Error('some error happen');
        }
      });

      sinon.stub(console, 'error');

      return co(function* () {
        const agent = request(app.callback());

        // 500及以上 错误异常会打在页面上
        yield agent.get('/')
            .expect(/^<pre>Error: some error happen\n/);

        // 其他的正常返回到浏览器端
        yield agent.get('/admin').expect(401);

        console.error.restore();
      });
    });
  });
});

<commit_msg>test: 修复单元测试<commit_after>'use strict';

const co = require('co');
const request = require('supertest');
const sinon = require('sinon');
const antsort = require('antsort');

const plover = require('../../');

/* eslint no-console: 0, max-nested-callbacks: [2, 4] */

describe('components/core', function() {
  const settings = { applicationRoot: 'somepath' };

  describe('应用启动相关', function() {
    it('可以正常启动plover应用', function() {
      const app = plover(settings);
      app.server.should.be.instanceof(require('koa/lib/application'));
      app.start();
    });

    it('启动时需要等待异步流程初始化完毕才提供服务', function(done) {
      const app = plover(settings);
      const workDone = app.readyCallback('longtime service');
      setTimeout(() => {
        app.mywork = 'done!';
        workDone();
      }, 100);

      (app.mywork === undefined).should.be.true();
      app.start(() => {
        app.mywork.should.equal('done!');
        done();
      });
    });

    it('使用listen可以快速启动应用', function() {
      const app = plover(settings);
      return app.listen(1234);
    });
  });

  describe('app.addMiddleware(middleware, [options])', function() {
    it('添加中间件', function() {
      const app = plover(settings);

      app.addMiddleware(function* (next) {
        if (this.url === '/a') {
          this.body = 'hello world a';
        } else {
          yield* next;
        }
      });

      app.addMiddleware(function() {
        return function* (next) {
          if (this.url === '/b') {
            this.body = 'hello world b';
          } else {
            yield* next;
          }
        };
      });

      const agent = request(app.callback());

      return co(function* () {
        yield agent.get('/a').expect('hello world a');
        yield agent.get('/b').expect('hello world b');
      });
    });

    it('设置中间件级别', function() {
      const app = plover(settings);

      app.addMiddleware(function* (next) {
        this.list = [];
        yield* next;
        this.body = this.list.join(' ');
      }, 0);

      app.addMiddleware(function* (next) {
        this.list.push('Hello');
        yield* next;
      }, 2);

      app.addMiddleware(function* (next) {
        this.list.push('Plover');
        yield* next;
      }); // 3 for default

      return request(app.callback())
          .get('/').expect('Hello Plover');
    });

    it('可以使用before精确排序', function() {
      const app = plover(settings);

      app.addMiddleware(function* mycsrf() {
        if (this.ignoreCsrf) {
          this.body = 'ignore csrf';
        } else {
          this.body = 'invalid csrf';
        }
      });

      app.addMiddleware(function* (next) {
        this.ignoreCsrf = true;
        yield* next;
      }, { before: 'mycsrf' });

      return request(app.callback())
          .get('/').expect('ignore csrf');
    });
  });

  describe('app.middleware()', function() {
    it('接入到其他koa应用', function() {
      const papp = plover(settings);

      papp.addMiddleware(function* () {
        this.body = 'hello plover';
      });

      const app = require('koa')();
      app.use(papp.middleware());

      return request(app.callback())
          .get('/').expect('hello plover');
    });
  });

  describe('扩展Plover', function() {
    it('使用$mountMiddlewares覆盖接入中间件的逻辑', function() {
      const callback = sinon.spy();

      class App extends plover.Application {
        $mountMiddlewares(app, items) {
          callback();
          items = antsort(items, { defaultLevel: 3 });
          items.forEach(item => {
            app.use(item.module);
          });
        }
      }

      const app = new App(settings);

      app.addMiddleware(function* Hello() {
        this.body = 'hello';
      });

      app.start();
      callback.called.should.be.true();

      return request(app.callback())
        .get('/hello')
        .expect('hello');
    });
  });

  describe('环境相关', function() {
    it('开发环境时，异常会打印在页面上', function() {
      const app = plover(settings);

      app.addMiddleware(function* () {
        if (this.url === '/admin') {
          this.throw(401);
        } else {
          throw new Error('some error happen');
        }
      });

      sinon.stub(console, 'error');

      return co(function* () {
        const agent = request(app.callback());

        // 500及以上 错误异常会打在页面上
        yield agent.get('/')
            .expect(/Error: some error happen/);

        // 其他的正常返回到浏览器端
        yield agent.get('/admin').expect(401);

        console.error.restore();
      });
    });
  });
});

<|endoftext|>


<commit_before>var assert = require('assert');

var compiler = require('../lib/compiler.js');

exports.test_comment = function(test){
    var code = ";this is a comment;\n";
    var tokens = compiler.lexical(code);
    test.equal(1, tokens.length);
    test.equal('T_ENDLINE', tokens[0].type);
    test.done();
};

exports.test_marker = function(test){
    var code = "BPL WAITVBLANK";
    var tokens = compiler.lexical(code);
    test.equal(2, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_MARKER', tokens[1].type);
    test.done();
};

exports.test_binary_number = function(test){
    var tokens = compiler.lexical('LDA #%10000000');
    test.equal(2, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_BINARY_NUMBER', tokens[1].type);
    var ast = compiler.syntax(tokens);
    test.equal(1 , ast.length);
    test.equal('S_IMMEDIATE', ast[0].type);
    var code = compiler.semantic(ast);
    test.deepEqual(code, [0xa9, 0x80]);
    test.done();
};

exports.test_string = function(test){
    var code = '.incbin "player.chr"';
    var tokens = compiler.lexical(code);
    test.equal(2, tokens.length);
    test.equal('T_DIRECTIVE', tokens[0].type);
    test.equal('T_STRING', tokens[1].type);
    test.done();
};

exports.test_double_instruction_as_marker = function(test){
    var code = 'RTIRTI: RTI';
    var tokens = compiler.lexical(code);
    test.equal(2, tokens.length);
    test.equal('T_LABEL', tokens[0].type);
    test.equal('T_INSTRUCTION', tokens[1].type);
    test.done();
};

exports.test_lowercase_instructions = function(test){
    var code = 'lda $20\nrti';
    var tokens = compiler.lexical(code);
    test.equal(4, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_ADDRESS', tokens[1].type);
    test.equal('T_ENDLINE', tokens[2].type);
    test.equal('T_INSTRUCTION', tokens[3].type);
    test.done();
};

exports.test_lowercase_relative_instructions = function(test){
    var source = 'vblankwait1: bit $2002\n';
    source += '\tbpl vblankwait1';
    var tokens = compiler.lexical(source);
    test.equal(6, tokens.length);
    test.equal('T_LABEL', tokens[0].type);
    test.equal('T_INSTRUCTION', tokens[1].type);
    test.equal('T_ADDRESS', tokens[2].type);
    test.equal('T_ENDLINE', tokens[3].type);
    test.equal('T_INSTRUCTION', tokens[4].type);
    test.equal('T_MARKER', tokens[5].type);
    var ast = compiler.syntax(tokens);
    test.equal(2 , ast.length);
    test.equal('S_ABSOLUTE', ast[0].type);
    test.equal('S_RELATIVE', ast[1].type);
    var code = compiler.semantic(ast);
    test.deepEqual(code, [0x2c, 0x02, 0x20, 0x10, 0xfb]); //TODO: check later
    test.done();
};

exports.test_address_with_one_digit = function (test){
    var code = 'sprites: .db $80, 112, $0, $80';
    var tokens = compiler.lexical(code);
    test.equal(9, tokens.length);
    test.equal('T_LABEL', tokens[0].type);
    test.equal('T_DIRECTIVE', tokens[1].type);
    test.equal('T_ADDRESS', tokens[2].type);
    test.equal('T_SEPARATOR', tokens[3].type);
    test.equal('T_DECIMAL_ARGUMENT', tokens[4].type);
    test.equal('T_SEPARATOR', tokens[5].type);
    test.equal('T_ADDRESS', tokens[6].type);
    test.equal('T_SEPARATOR', tokens[7].type);
    test.equal('T_ADDRESS', tokens[8].type);

    test.done();
};

exports.test_compile_more_than_on_instruction = function(test){
    var code = "SEC     ;clear the carry;\n";
    code += "LDA $20     ;get the low byte of the first number;\n";
    var tokens = compiler.lexical(code);
    test.equal(5, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_ENDLINE', tokens[1].type);
    test.equal('T_INSTRUCTION', tokens[2].type);
    test.equal('T_ADDRESS', tokens[3].type);
    test.equal('T_ENDLINE', tokens[4].type);
    var ast = compiler.syntax(tokens);
    test.equal(2, ast.length);
    test.equal('S_IMPLIED', ast[0].type);
    //TODO: test.equal('SEC', ast[0].instruction.value);
    test.equal('S_ZEROPAGE', ast[1].type);
    //TODO: test.equal('LDA', ast[1].instruction.value);
    test.done();
};

//NesASM compatible
exports.test_low_modifier = function(test){
    var code = "ADC #LOW($E030)";
    var tokens = compiler.lexical(code);
    test.equal(5, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_MODIFIER', tokens[1].type);
    test.equal('T_OPEN', tokens[2].type);
    test.equal('T_ADDRESS', tokens[3].type);
    test.equal('T_CLOSE', tokens[4].type);
    var ast = compiler.syntax(tokens);
    var opcodes = compiler.semantic(ast);
    test.deepEqual(opcodes, [0x69, 0x30]);
    test.done();
};

//NesASM compatible
exports.test_high_modifier = function(test){
    var code = "ADC #HIGH($E030)";
    var tokens = compiler.lexical(code);
    test.equal(5, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_MODIFIER', tokens[1].type);
    test.equal('T_OPEN', tokens[2].type);
    test.equal('T_ADDRESS', tokens[3].type);
    test.equal('T_CLOSE', tokens[4].type);
    var ast = compiler.syntax(tokens);
    var opcodes = compiler.semantic(ast);
    test.deepEqual(opcodes, [0x69, 0xe0]);
    test.done();
};

//NesASM indyY address mode compatible

exports.test_lda_indy = function(test){
    var tokens = compiler.lexical('LDA [$20],Y');
    test.equal(6 , tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_OPEN_SQUARE_BRACKETS', tokens[1].type);
    test.equal('T_ADDRESS', tokens[2].type);
    test.equal('T_CLOSE_SQUARE_BRACKETS', tokens[3].type);
    test.equal('T_SEPARATOR', tokens[4].type);
    test.equal('T_REGISTER', tokens[5].type);
    var ast = compiler.syntax(tokens);
    test.equal(1 , ast.length);
    test.equal('S_INDIRECT_Y', ast[0].type);
    var code = compiler.semantic(ast);
    test.deepEqual(code, [0xb1, 0x20]);
    test.done();
};

exports.test_invalid_token = function(test){
    try {
        var tokens = compiler.lexical('.invalid #TOKEN');
        test.fail();
    }catch(e){
        test.equal("Lexical Error" , e.name);
        test.equal("Lexical Error Message" , e.message);
        test.equal(1 , e.erros.length);
        test.equal("Invalid Token" , e.erros[0].name);
        test.equal(1 , e.erros[0].line);
        test.equal(10 , e.erros[0].column);
        test.equal("#TOKEN" , e.erros[0].value);
        test.equal("Token #TOKEN at line 1 column 10 is invalid" , e.erros[0].message);
    }
    test.done();
};

exports.test_invalid_directive = function (test){
    try {
        var tokens = compiler.lexical('.invalid 171');
        test.equal(2 , tokens.length);
        test.equal('T_DIRECTIVE', tokens[0].type);
        test.equal('T_DECIMAL_ARGUMENT', tokens[1].type);
        var ast = compiler.syntax(tokens);
        test.equal(1 , ast.length);
        test.equal('S_DIRECTIVE', ast[0].type);
        var code = compiler.semantic(ast);
        test.fail();
    } catch (e){
        test.equal("Semantic Error" , e.name);
        test.equal("Semantic Error Message" , e.message);
        test.equal(1 , e.erros.length);
        test.equal("Unknow Directive" , e.erros[0].type);
    }
    test.done();
}

/*
TODO: fix later syntax test
exports.test_invalid_syntax = function (test){
    try {
        var tokens = compiler.lexical('php php');
        var ast = compiler.syntax(tokens);
        test.fail();
    }catch(e){
        test.equal('Syntax Error' , e.name);
        test.equal('There were found 1 erros:\n' , e.message);
        test.equal(1 , e.erros.length);
        test.equal('Syntax Error' , e.erros[0].type);
    }
    test.done();
};
*/

exports.test_nes_invalid_token = function(test){
    try {
        compiler.nes_compiler('#INVALIDTOKEN');
        test.fail();
    }catch(e){
        test.equal(1 , e.length);
        test.equal("Invalid Token" , e[0].name);
        test.equal(1 , e[0].line);
        test.equal(1 , e[0].column);
        test.equal("#INVALIDTOKEN" , e[0].value);
        test.equal("Token #INVALIDTOKEN at line 1 column 1 is invalid" , e[0].message);
    }
    test.done();
};
<commit_msg>fixing a missing semicolon<commit_after>var assert = require('assert');

var compiler = require('../lib/compiler.js');

exports.test_comment = function(test){
    var code = ";this is a comment;\n";
    var tokens = compiler.lexical(code);
    test.equal(1, tokens.length);
    test.equal('T_ENDLINE', tokens[0].type);
    test.done();
};

exports.test_marker = function(test){
    var code = "BPL WAITVBLANK";
    var tokens = compiler.lexical(code);
    test.equal(2, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_MARKER', tokens[1].type);
    test.done();
};

exports.test_binary_number = function(test){
    var tokens = compiler.lexical('LDA #%10000000');
    test.equal(2, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_BINARY_NUMBER', tokens[1].type);
    var ast = compiler.syntax(tokens);
    test.equal(1 , ast.length);
    test.equal('S_IMMEDIATE', ast[0].type);
    var code = compiler.semantic(ast);
    test.deepEqual(code, [0xa9, 0x80]);
    test.done();
};

exports.test_string = function(test){
    var code = '.incbin "player.chr"';
    var tokens = compiler.lexical(code);
    test.equal(2, tokens.length);
    test.equal('T_DIRECTIVE', tokens[0].type);
    test.equal('T_STRING', tokens[1].type);
    test.done();
};

exports.test_double_instruction_as_marker = function(test){
    var code = 'RTIRTI: RTI';
    var tokens = compiler.lexical(code);
    test.equal(2, tokens.length);
    test.equal('T_LABEL', tokens[0].type);
    test.equal('T_INSTRUCTION', tokens[1].type);
    test.done();
};

exports.test_lowercase_instructions = function(test){
    var code = 'lda $20\nrti';
    var tokens = compiler.lexical(code);
    test.equal(4, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_ADDRESS', tokens[1].type);
    test.equal('T_ENDLINE', tokens[2].type);
    test.equal('T_INSTRUCTION', tokens[3].type);
    test.done();
};

exports.test_lowercase_relative_instructions = function(test){
    var source = 'vblankwait1: bit $2002\n';
    source += '\tbpl vblankwait1';
    var tokens = compiler.lexical(source);
    test.equal(6, tokens.length);
    test.equal('T_LABEL', tokens[0].type);
    test.equal('T_INSTRUCTION', tokens[1].type);
    test.equal('T_ADDRESS', tokens[2].type);
    test.equal('T_ENDLINE', tokens[3].type);
    test.equal('T_INSTRUCTION', tokens[4].type);
    test.equal('T_MARKER', tokens[5].type);
    var ast = compiler.syntax(tokens);
    test.equal(2 , ast.length);
    test.equal('S_ABSOLUTE', ast[0].type);
    test.equal('S_RELATIVE', ast[1].type);
    var code = compiler.semantic(ast);
    test.deepEqual(code, [0x2c, 0x02, 0x20, 0x10, 0xfb]); //TODO: check later
    test.done();
};

exports.test_address_with_one_digit = function (test){
    var code = 'sprites: .db $80, 112, $0, $80';
    var tokens = compiler.lexical(code);
    test.equal(9, tokens.length);
    test.equal('T_LABEL', tokens[0].type);
    test.equal('T_DIRECTIVE', tokens[1].type);
    test.equal('T_ADDRESS', tokens[2].type);
    test.equal('T_SEPARATOR', tokens[3].type);
    test.equal('T_DECIMAL_ARGUMENT', tokens[4].type);
    test.equal('T_SEPARATOR', tokens[5].type);
    test.equal('T_ADDRESS', tokens[6].type);
    test.equal('T_SEPARATOR', tokens[7].type);
    test.equal('T_ADDRESS', tokens[8].type);

    test.done();
};

exports.test_compile_more_than_on_instruction = function(test){
    var code = "SEC     ;clear the carry;\n";
    code += "LDA $20     ;get the low byte of the first number;\n";
    var tokens = compiler.lexical(code);
    test.equal(5, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_ENDLINE', tokens[1].type);
    test.equal('T_INSTRUCTION', tokens[2].type);
    test.equal('T_ADDRESS', tokens[3].type);
    test.equal('T_ENDLINE', tokens[4].type);
    var ast = compiler.syntax(tokens);
    test.equal(2, ast.length);
    test.equal('S_IMPLIED', ast[0].type);
    //TODO: test.equal('SEC', ast[0].instruction.value);
    test.equal('S_ZEROPAGE', ast[1].type);
    //TODO: test.equal('LDA', ast[1].instruction.value);
    test.done();
};

//NesASM compatible
exports.test_low_modifier = function(test){
    var code = "ADC #LOW($E030)";
    var tokens = compiler.lexical(code);
    test.equal(5, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_MODIFIER', tokens[1].type);
    test.equal('T_OPEN', tokens[2].type);
    test.equal('T_ADDRESS', tokens[3].type);
    test.equal('T_CLOSE', tokens[4].type);
    var ast = compiler.syntax(tokens);
    var opcodes = compiler.semantic(ast);
    test.deepEqual(opcodes, [0x69, 0x30]);
    test.done();
};

//NesASM compatible
exports.test_high_modifier = function(test){
    var code = "ADC #HIGH($E030)";
    var tokens = compiler.lexical(code);
    test.equal(5, tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_MODIFIER', tokens[1].type);
    test.equal('T_OPEN', tokens[2].type);
    test.equal('T_ADDRESS', tokens[3].type);
    test.equal('T_CLOSE', tokens[4].type);
    var ast = compiler.syntax(tokens);
    var opcodes = compiler.semantic(ast);
    test.deepEqual(opcodes, [0x69, 0xe0]);
    test.done();
};

//NesASM indyY address mode compatible

exports.test_lda_indy = function(test){
    var tokens = compiler.lexical('LDA [$20],Y');
    test.equal(6 , tokens.length);
    test.equal('T_INSTRUCTION', tokens[0].type);
    test.equal('T_OPEN_SQUARE_BRACKETS', tokens[1].type);
    test.equal('T_ADDRESS', tokens[2].type);
    test.equal('T_CLOSE_SQUARE_BRACKETS', tokens[3].type);
    test.equal('T_SEPARATOR', tokens[4].type);
    test.equal('T_REGISTER', tokens[5].type);
    var ast = compiler.syntax(tokens);
    test.equal(1 , ast.length);
    test.equal('S_INDIRECT_Y', ast[0].type);
    var code = compiler.semantic(ast);
    test.deepEqual(code, [0xb1, 0x20]);
    test.done();
};

exports.test_invalid_token = function(test){
    try {
        var tokens = compiler.lexical('.invalid #TOKEN');
        test.fail();
    }catch(e){
        test.equal("Lexical Error" , e.name);
        test.equal("Lexical Error Message" , e.message);
        test.equal(1 , e.erros.length);
        test.equal("Invalid Token" , e.erros[0].name);
        test.equal(1 , e.erros[0].line);
        test.equal(10 , e.erros[0].column);
        test.equal("#TOKEN" , e.erros[0].value);
        test.equal("Token #TOKEN at line 1 column 10 is invalid" , e.erros[0].message);
    }
    test.done();
};

exports.test_invalid_directive = function (test){
    try {
        var tokens = compiler.lexical('.invalid 171');
        test.equal(2 , tokens.length);
        test.equal('T_DIRECTIVE', tokens[0].type);
        test.equal('T_DECIMAL_ARGUMENT', tokens[1].type);
        var ast = compiler.syntax(tokens);
        test.equal(1 , ast.length);
        test.equal('S_DIRECTIVE', ast[0].type);
        var code = compiler.semantic(ast);
        test.fail();
    } catch (e){
        test.equal("Semantic Error" , e.name);
        test.equal("Semantic Error Message" , e.message);
        test.equal(1 , e.erros.length);
        test.equal("Unknow Directive" , e.erros[0].type);
    }
    test.done();
};

/*
TODO: fix later syntax test
exports.test_invalid_syntax = function (test){
    try {
        var tokens = compiler.lexical('php php');
        var ast = compiler.syntax(tokens);
        test.fail();
    }catch(e){
        test.equal('Syntax Error' , e.name);
        test.equal('There were found 1 erros:\n' , e.message);
        test.equal(1 , e.erros.length);
        test.equal('Syntax Error' , e.erros[0].type);
    }
    test.done();
};
*/

exports.test_nes_invalid_token = function(test){
    try {
        compiler.nes_compiler('#INVALIDTOKEN');
        test.fail();
    }catch(e){
        test.equal(1 , e.length);
        test.equal("Invalid Token" , e[0].name);
        test.equal(1 , e[0].line);
        test.equal(1 , e[0].column);
        test.equal("#INVALIDTOKEN" , e[0].value);
        test.equal("Token #INVALIDTOKEN at line 1 column 1 is invalid" , e[0].message);
    }
    test.done();
};
<|endoftext|>


<commit_before>$(document).ready(function() {

    // caching
    var slider = $('.works-container-project-slider');
    var slidesBox = $('.works-container-project-slider ul');
    var slide = $('.works-container-project-slider ul li');
    var slideWidth = slide.width();

    var carouselBox = $('.works-container-projectimage-carousel');
    var carouselSlidesBox = $('.works-container-projectimage-carousel-boxslides');
    var carouselSlide = $('.works-container-projectimage-carousel-boxslides figure');
    var carosulSlideWidth = carouselSlide.width();

    // options
    var currentPosition = 0;
    var numItems = slide.length;

    var currentPositionCar = 0;
    var numImages = carouselSlide.length;

    var left = $('.works-container-project-navigator-left');
    var right = $('.works-container-project-navigator-right');

    // make each slide as large as slider
    slide.css({
        'display': 'block',
        'width': slider.width()
    });

    carouselSlide.css({
        'display': 'block',
        'width': carouselBox.width()
    });

    // make slides box as large as all slides
    slidesBox.css({
        'width': slider.width() * numItems + 'px'
    });

    carouselSlidesBox.css({
        'width': carouselBox.width() * numImages + 'px'
    });

    // navigate the slides
    left.click(slideLeft);
    right.click(slideRight);

    function slideLeft() {
        if (currentPosition > 0) {
            currentPosition -= 1;
            slidesBox.css({
                'left': currentPosition * slideWidth + 'px'
            });
            currentPositionCar -=1;
            carouselSlidesBox.css({
                'left': currentPositionCar * carosulSlideWidth + 'px'
            })
        }
    }

    function slideRight() {
        if (currentPosition < numItems - 1) {
            currentPosition += 1;
            slidesBox.css({
                'left': currentPosition * -slideWidth + 'px'
            });
            currentPositionCar +=1;
            carouselSlidesBox.css({
                'left': currentPositionCar * -carosulSlideWidth + 'px'
            })
        }
    }
});
<commit_msg>added missing semicolons in works-slider.js<commit_after>$(document).ready(function() {

    // caching
    var slider = $('.works-container-project-slider');
    var slidesBox = $('.works-container-project-slider ul');
    var slide = $('.works-container-project-slider ul li');
    var slideWidth = slide.width();

    var carouselBox = $('.works-container-projectimage-carousel');
    var carouselSlidesBox = $('.works-container-projectimage-carousel-boxslides');
    var carouselSlide = $('.works-container-projectimage-carousel-boxslides figure');
    var carosulSlideWidth = carouselSlide.width();

    // options
    var currentPosition = 0;
    var numItems = slide.length;

    var currentPositionCar = 0;
    var numImages = carouselSlide.length;

    var left = $('.works-container-project-navigator-left');
    var right = $('.works-container-project-navigator-right');

    // make each slide as large as slider
    slide.css({
        'display': 'block',
        'width': slider.width()
    });

    carouselSlide.css({
        'display': 'block',
        'width': carouselBox.width()
    });

    // make slides box as large as all slides
    slidesBox.css({
        'width': slider.width() * numItems + 'px'
    });

    carouselSlidesBox.css({
        'width': carouselBox.width() * numImages + 'px'
    });

    // navigate the slides
    left.click(slideLeft);
    right.click(slideRight);

    function slideLeft() {
        if (currentPosition > 0) {
            currentPosition -= 1;
            slidesBox.css({
                'left': currentPosition * slideWidth + 'px'
            });
            currentPositionCar -=1;
            carouselSlidesBox.css({
                'left': currentPositionCar * carosulSlideWidth + 'px'
            });
        }
    }

    function slideRight() {
        if (currentPosition < numItems - 1) {
            currentPosition += 1;
            slidesBox.css({
                'left': currentPosition * -slideWidth + 'px'
            });
            currentPositionCar +=1;
            carouselSlidesBox.css({
                'left': currentPositionCar * -carosulSlideWidth + 'px'
            });
        }
    }
});
<|endoftext|>