<commit_before>window.GridModel = {
    sizeChange: new Notification(this),
    attrChange: new Notification(this),
    startPosChange: new Notification(this),
    endPosChange: new Notification(this),

    init: function() {
        var self = this;

        this._queue = []
        // add hook on the `set' method of PF.Node
        var orig = PF.Node.prototype.set;
        PF.Node.prototype.set = function() {
            orig.apply(this, arguments);
            self._queue.push({
                x: this.x,
                y: this.y,
                attr: arguments[0],
                value: arguments[1],
            });
        };
    },

    getWidth: function() {
        return this._grid.width;
    },
    getHeight: function() {
        return this._grid.height;
    },
    setSize: function(width, height) {
        this._grid = new PF.Grid(width, height);
        this.sizeChange.notify();
    },

    isWalkableAt: function(x, y) {
        return this._grid.isWalkableAt(x, y);
    },
    setWalkableAt: function(x, y, walkable) {
        this.setAttributeAt(x, y, 'walkable', walkable);
    },

    getAttributeAt: function(x, y, attr) {
        return this._grid.getAttributeAt(x, y, attr);
    },
    setAttributeAt: function(x, y, attr, value) {
        this._grid.setAttributeAt(x, y, attr, value);
        this.attrChange.notify({
            x: x,
            y: y,
            attr: attr,
            value: value,
        });
    },

    getStartX: function() {
        return this._startX;
    },
    getStartY: function() {
        return this._startY;
    },
    setStartPos: function(x, y) {
        this._startX = x;
        this._startY = y;
        this.startPosChange.notify();
    },

    getEndX: function() {
        return this._endX;
    },
    getEndY: function() {
        return this._endY;
    },
    setEndPos: function(x, y) {
        this._endX = x;
        this._endY = y;
        this.endPosChange.notify();
    },

    findPath: function(finder) {
        var path, operations;

        path = finder.findPath(
            this._startX, this._startY,
            this._endX, this._endY,
            this._grid
        );
        operations = this._queue;

        return {
            path: path,
            operations: operations,
        };
    },

};

window.GridView = {
    // option fields
    nodeSize: 30,
    normalNodeAttr: {
        fill: 'white',
        'stroke-opacity': 0.2,
    },
    blockNodeAttr: {
        fill: 'grey',
        'stroke-opacity': 0.2,
    },
    startNodeAttr: {
        fill: '#0d0',
        'stroke-opacity': 0.2,
    },
    endNodeAttr: {
        fill: '#e40',
        'stroke-opacity': 0.2,
    },
    openedNodeAttr: {
        fill: '#98fb98',
        'stroke-opacity': 0.2,
    },
    closedNodeAttr: {
        fill: '#afeeee',
        'stroke-opacity': 0.2,
    },
    colorizeDuration: 50,
    zoomEffect: 's1.2',
    zoomDuration: 200,

    // general initialization
    init: function(width, height) {
        this.initPaper();
        this.initListeners();
    },

    initListeners: function() {
        var self = this;

        // listeners of model notifications
        GridModel.sizeChange.attach(function() {
            self.setSize(GridModel.getWidth(), GridModel.getHeight());
        });
        GridModel.startPosChange.attach(function() {
            self.setStartPos(GridModel.getStartX(), GridModel.getStartY());
        });
        GridModel.endPosChange.attach(function() {
            self.setEndPos(GridModel.getEndX(), GridModel.getEndY());
        });
        GridModel.attrChange.attach(function(args) {
            self.setAttributeAt(args.x, args.y, args.attr, args.value);
        });

        // listeners for browser events
        $('#draw_area').mousedown(function(event) {
            var coord, x, y;
            coord = self.toGridCoordinate(event.pageX, event.pageY);
            x = coord.x; y = coord.y;

            GridController.onMouseDown(x, y);
        });
        $(window).mousemove(function(event) {
            var coord, x, y;
            coord = self.toGridCoordinate(event.pageX, event.pageY);
            x = coord.x; y = coord.y;

            GridController.onMouseMove(x, y);
        }).mouseup(function(event) {
            var coord, x, y;
            coord = self.toGridCoordinate(event.pageX, event.pageY);
            x = coord.x; y = coord.y;

            GridController.onMouseUp(x, y);
        });
    },

    // initialize paper
    initPaper: function() {
        this.paper = Raphael('draw_area');
        this.setSize();
    },

    // set paper size
    setSize: function(width, height) {
        var i, j,
            x, y,
            nodeSize = this.nodeSize,
            paper = this.paper,
            rect,
            rects = [];

        this.paper.setSize(
            nodeSize * width,
            nodeSize * height
        );

        for (i = 0; i < height; ++i) {
            rects[i] = [];

            for (j = 0; j < width; ++j) {
                x = j * nodeSize;
                y = i * nodeSize;

                rect = paper.rect(x, y, nodeSize, nodeSize);
                rect.attr(
                    GridModel.isWalkableAt(j, i) ?
                    this.normalNodeAttr :
                    this.blockNodeAttr
                );
                rects[i][j] = rect;
            }
        }

        this.rects = rects;
    },

    setStartPos: function(x, y) {
        this.setStartEndNodePos('start', x, y);
    },
    setEndPos: function(x, y) {
        this.setStartEndNodePos('end', x, y);
    },
    // helper function to set start or end position
    setStartEndNodePos: function(which, x, y) {
        var coord = this.toPageCoordinate(x, y),
            pageX = coord.x,
            pageY = coord.y,
            nodeSize = this.nodeSize,
            paper = this.paper,
            rect = this['_' + which + 'Node'];

        if (typeof rect == 'undefined') {
            rect = paper.rect(pageX, pageY, nodeSize, nodeSize);
            rect.attr(this[which + 'NodeAttr']);
            this['_' + which + 'Node'] = rect;
        } else {
            rect.attr({
                x: pageX,
                y: pageY,
            }).toFront();
        }
    },

    setAttributeAt: function(x, y, attr, value) {
        if (attr == 'walkable') {
            if (value == true) {
                this.colorizeNodeAt(x, y, this.normalNodeAttr.fill);
            } else {
                this.colorizeNodeAt(x, y, this.blockNodeAttr.fill);
            }
            this.zoomNodeAt(x, y);
        } else if (attr == 'opened') {
            this.colorizeNodeAt(x, y, this.openedNodeAttr.fill);
        } else if (attr == 'closed') {
            this.colorizeNodeAt(x, y, this.closedNodeAttr.fill);
        } else if (attr == 'parent') {
            //this.drawParent(x, y, px, py)
        }
    },

    colorizeNodeAt: function(x, y, color) {
        this.rects[y][x].animate({
            fill: color,
        }, this.colorizeDuration);
    },

    zoomNodeAt: function(x, y) {
        this.rects[y][x].toFront().attr({
            transform: this.zoomEffect,
        }).animate({
            transform: 's1.0',
        }, this.zoomDuration);
    },

    drawPath: function(path) {

    },

    // given a path, build its SVG represention.
    buildSvgPath: function(path) {
        var i, strs = [], size = this.gridSize;

        strs.push('M' + (path[0].x * size + size / 2) + ' '
                + (path[0].y * size + size / 2));
        for (i = 1; i < path.length; ++i) {
            strs.push('L' + (path[i].x * size + size / 2) + ' '
                    + (path[i].y * size + size / 2));
        }

        return strs.join('');
    },

    // helper function to convert the page coordinate to grid coordinate
    toGridCoordinate: function(pageX, pageY) {
        var nodeSize = this.nodeSize;
        return {
            x: Math.floor(pageX / nodeSize),
            y: Math.floor(pageY / nodeSize)
        };
    },

    // helper function to convert the grid coordinate to page coordinate
    toPageCoordinate: function(gridX, gridY) {
        var nodeSize = this.nodeSize;
        return {
            x: gridX * nodeSize,
            y: gridY * nodeSize
        };
    },

};

window.GridController = {
    init: function() {
        this.initGeometry();
        this.initStartEndPos();
    },

    supportedDispatcher: {
        'opened': true,
        'closed': true,
        'parent': true,
    },

    initGeometry: function() {
        var width, height,
            nodeSize = GridView.nodeSize;

        width = $(window).width();
        height = $(window).height();

        numCols = Math.ceil(width / nodeSize);
        numRows = Math.ceil(height / nodeSize);

        GridModel.setSize(numCols, numRows);
    },

    initStartEndPos: function() {
        GridModel.setStartPos(10, 10);
        GridModel.setEndPos(20, 10);
    },

    onMouseDown: function(x, y) {
        if (x == GridModel.getStartX() && y == GridModel.getStartY()) {
            this.isMoving = true;
            this.moving = 'start';
        } else if (x == GridModel.getEndX() && y == GridModel.getEndY()) {
            this.isMoving = true;
            this.moving = 'end';
        } else {
            this.isDrawing = true;
            this.drawStatus = GridModel.isWalkableAt(x, y) ? 'block' : 'clear';
            GridModel.setWalkableAt(x, y, this.drawStatus == 'clear');
        }
    },

    onMouseMove: function(x, y) {
        if (this.isDrawing) {
            if (!((x == GridModel.getStartX() && y == GridModel.getStartY()) ||
                  (x == GridModel.getEndX() && y == GridModel.getEndY()))) {
                GridModel.setWalkableAt(x, y, this.drawStatus == 'clear');
            }
        } else if (this.isMoving) {
            if (this.moving == 'start') {
                GridModel.setStartPos(x, y);
            } else {
                GridModel.setEndPos(x, y);
            }
        }
    },

    onMouseUp: function(x, y) {
        this.isDrawing = false;
        this.isMoving = false;
    },

    start: function(finder, interval) {
        var self = this,
            record = GridModel.findPath(finder);

        this.operations = record.operations;
        this.path = record.path;

        this.timer = setInterval(function() {
            self.step(function() {
                clearInterval(self.timer);
                GridView.drawPath(this.path);
            });
        }, interval);
    },

    step: function(callback) {
        var operations = this.operations,
            op, support;

        do {
            if (!operations.length) {
                callback();
                return;
            }
            op = operations.shift();
            console.log(op)
            support = this.supportedDispatcher[op.attr];
        } while (!support);

        GridView.setAttributeAt(op.x, op.y, op.attr, op.value);
    },
};
<commit_msg>add: draw path for demo page<commit_after>window.GridModel = {
    sizeChange: new Notification(this),
    attrChange: new Notification(this),
    startPosChange: new Notification(this),
    endPosChange: new Notification(this),

    init: function() {
        var self = this;

        this._queue = []
        // add hook on the `set' method of PF.Node
        var orig = PF.Node.prototype.set;
        PF.Node.prototype.set = function() {
            orig.apply(this, arguments);
            self._queue.push({
                x: this.x,
                y: this.y,
                attr: arguments[0],
                value: arguments[1],
            });
        };
    },

    getWidth: function() {
        return this._grid.width;
    },
    getHeight: function() {
        return this._grid.height;
    },
    setSize: function(width, height) {
        this._grid = new PF.Grid(width, height);
        this.sizeChange.notify();
    },

    isWalkableAt: function(x, y) {
        return this._grid.isWalkableAt(x, y);
    },
    setWalkableAt: function(x, y, walkable) {
        this.setAttributeAt(x, y, 'walkable', walkable);
    },

    getAttributeAt: function(x, y, attr) {
        return this._grid.getAttributeAt(x, y, attr);
    },
    setAttributeAt: function(x, y, attr, value) {
        this._grid.setAttributeAt(x, y, attr, value);
        this.attrChange.notify({
            x: x,
            y: y,
            attr: attr,
            value: value,
        });
    },

    getStartX: function() {
        return this._startX;
    },
    getStartY: function() {
        return this._startY;
    },
    setStartPos: function(x, y) {
        this._startX = x;
        this._startY = y;
        this.startPosChange.notify();
    },

    getEndX: function() {
        return this._endX;
    },
    getEndY: function() {
        return this._endY;
    },
    setEndPos: function(x, y) {
        this._endX = x;
        this._endY = y;
        this.endPosChange.notify();
    },

    findPath: function(finder) {
        var path, operations;

        path = finder.findPath(
            this._startX, this._startY,
            this._endX, this._endY,
            this._grid
        );
        operations = this._queue;

        return {
            path: path,
            operations: operations,
        };
    },

};

window.GridView = {
    // option fields
    nodeSize: 30,
    normalNodeAttr: {
        fill: 'white',
        'stroke-opacity': 0.2,
    },
    blockNodeAttr: {
        fill: 'grey',
        'stroke-opacity': 0.2,
    },
    startNodeAttr: {
        fill: '#0d0',
        'stroke-opacity': 0.2,
    },
    endNodeAttr: {
        fill: '#e40',
        'stroke-opacity': 0.2,
    },
    openedNodeAttr: {
        fill: '#98fb98',
        'stroke-opacity': 0.2,
    },
    closedNodeAttr: {
        fill: '#afeeee',
        'stroke-opacity': 0.2,
    },
    pathAttr: {
        stroke: 'yellow',
        'stroke-width': 3,
    },
    colorizeDuration: 50,
    zoomEffect: 's1.2',
    zoomDuration: 200,

    // general initialization
    init: function(width, height) {
        this.initPaper();
        this.initListeners();
    },

    initListeners: function() {
        var self = this;

        // listeners of model notifications
        GridModel.sizeChange.attach(function() {
            self.setSize(GridModel.getWidth(), GridModel.getHeight());
        });
        GridModel.startPosChange.attach(function() {
            self.setStartPos(GridModel.getStartX(), GridModel.getStartY());
        });
        GridModel.endPosChange.attach(function() {
            self.setEndPos(GridModel.getEndX(), GridModel.getEndY());
        });
        GridModel.attrChange.attach(function(args) {
            self.setAttributeAt(args.x, args.y, args.attr, args.value);
        });

        // listeners for browser events
        $('#draw_area').mousedown(function(event) {
            var coord, x, y;
            coord = self.toGridCoordinate(event.pageX, event.pageY);
            x = coord.x; y = coord.y;

            GridController.onMouseDown(x, y);
        });
        $(window).mousemove(function(event) {
            var coord, x, y;
            coord = self.toGridCoordinate(event.pageX, event.pageY);
            x = coord.x; y = coord.y;

            GridController.onMouseMove(x, y);
        }).mouseup(function(event) {
            var coord, x, y;
            coord = self.toGridCoordinate(event.pageX, event.pageY);
            x = coord.x; y = coord.y;

            GridController.onMouseUp(x, y);
        });
    },

    // initialize paper
    initPaper: function() {
        this.paper = Raphael('draw_area');
        this.setSize();
    },

    // set paper size
    setSize: function(width, height) {
        var i, j,
            x, y,
            nodeSize = this.nodeSize,
            paper = this.paper,
            rect,
            rects = [];

        this.paper.setSize(
            nodeSize * width,
            nodeSize * height
        );

        for (i = 0; i < height; ++i) {
            rects[i] = [];

            for (j = 0; j < width; ++j) {
                x = j * nodeSize;
                y = i * nodeSize;

                rect = paper.rect(x, y, nodeSize, nodeSize);
                rect.attr(
                    GridModel.isWalkableAt(j, i) ?
                    this.normalNodeAttr :
                    this.blockNodeAttr
                );
                rects[i][j] = rect;
            }
        }

        this.rects = rects;
    },

    setStartPos: function(x, y) {
        this.setStartEndNodePos('start', x, y);
    },
    setEndPos: function(x, y) {
        this.setStartEndNodePos('end', x, y);
    },
    // helper function to set start or end position
    setStartEndNodePos: function(which, x, y) {
        var coord = this.toPageCoordinate(x, y),
            pageX = coord.x,
            pageY = coord.y,
            nodeSize = this.nodeSize,
            paper = this.paper,
            rect = this['_' + which + 'Node'];

        if (typeof rect == 'undefined') {
            rect = paper.rect(pageX, pageY, nodeSize, nodeSize);
            rect.attr(this[which + 'NodeAttr']);
            this['_' + which + 'Node'] = rect;
        } else {
            rect.attr({
                x: pageX,
                y: pageY,
            }).toFront();
        }
    },

    setAttributeAt: function(x, y, attr, value) {
        if (attr == 'walkable') {
            if (value == true) {
                this.colorizeNodeAt(x, y, this.normalNodeAttr.fill);
            } else {
                this.colorizeNodeAt(x, y, this.blockNodeAttr.fill);
            }
            this.zoomNodeAt(x, y);
        } else if (attr == 'opened') {
            this.colorizeNodeAt(x, y, this.openedNodeAttr.fill);
        } else if (attr == 'closed') {
            this.colorizeNodeAt(x, y, this.closedNodeAttr.fill);
        } else if (attr == 'parent') {
            //this.drawParent(x, y, px, py)
        }
    },

    colorizeNodeAt: function(x, y, color) {
        this.rects[y][x].animate({
            fill: color,
        }, this.colorizeDuration);
    },

    zoomNodeAt: function(x, y) {
        this.rects[y][x].toFront().attr({
            transform: this.zoomEffect,
        }).animate({
            transform: 's1.0',
        }, this.zoomDuration);
    },

    drawPath: function(path) {
        var svgPath;

        if (!path.length) {
            return;
        }

        svgPath = this.buildSvgPath(path);
        console.debug(svgPath);
        this.paper.path(svgPath).attr(this.pathAttr);
    },

    // given a path, build its SVG represention.
    buildSvgPath: function(path) {
        var i, strs = [], size = this.nodeSize;

        strs.push('M' + (path[0][0] * size + size / 2) + ' '
                + (path[0][1] * size + size / 2));
        for (i = 1; i < path.length; ++i) {
            strs.push('L' + (path[i][0] * size + size / 2) + ' '
                    + (path[i][1] * size + size / 2));
        }

        return strs.join('');
    },

    // helper function to convert the page coordinate to grid coordinate
    toGridCoordinate: function(pageX, pageY) {
        var nodeSize = this.nodeSize;
        return {
            x: Math.floor(pageX / nodeSize),
            y: Math.floor(pageY / nodeSize)
        };
    },

    // helper function to convert the grid coordinate to page coordinate
    toPageCoordinate: function(gridX, gridY) {
        var nodeSize = this.nodeSize;
        return {
            x: gridX * nodeSize,
            y: gridY * nodeSize
        };
    },

};

window.GridController = {
    init: function() {
        this.initGeometry();
        this.initStartEndPos();
    },

    supportedDispatcher: {
        'opened': true,
        'closed': true,
        'parent': true,
    },

    initGeometry: function() {
        var width, height,
            nodeSize = GridView.nodeSize;

        width = $(window).width();
        height = $(window).height();

        numCols = Math.ceil(width / nodeSize);
        numRows = Math.ceil(height / nodeSize);

        GridModel.setSize(numCols, numRows);
    },

    initStartEndPos: function() {
        GridModel.setStartPos(10, 10);
        GridModel.setEndPos(20, 10);
    },

    onMouseDown: function(x, y) {
        if (x == GridModel.getStartX() && y == GridModel.getStartY()) {
            this.isMoving = true;
            this.moving = 'start';
        } else if (x == GridModel.getEndX() && y == GridModel.getEndY()) {
            this.isMoving = true;
            this.moving = 'end';
        } else {
            this.isDrawing = true;
            this.drawStatus = GridModel.isWalkableAt(x, y) ? 'block' : 'clear';
            GridModel.setWalkableAt(x, y, this.drawStatus == 'clear');
        }
    },

    onMouseMove: function(x, y) {
        if (this.isDrawing) {
            if (!((x == GridModel.getStartX() && y == GridModel.getStartY()) ||
                  (x == GridModel.getEndX() && y == GridModel.getEndY()))) {
                GridModel.setWalkableAt(x, y, this.drawStatus == 'clear');
            }
        } else if (this.isMoving) {
            if (this.moving == 'start') {
                GridModel.setStartPos(x, y);
            } else {
                GridModel.setEndPos(x, y);
            }
        }
    },

    onMouseUp: function(x, y) {
        this.isDrawing = false;
        this.isMoving = false;
    },

    start: function(finder, interval) {
        var self = this,
            record = GridModel.findPath(finder);

        this.operations = record.operations;
        this.path = record.path;

        this.timer = setInterval(function() {
            self.step(function() {
                clearInterval(self.timer);
                GridView.drawPath(self.path);
            });
        }, interval);
    },

    step: function(callback) {
        var operations = this.operations,
            op, support;

        do {
            if (!operations.length) {
                callback();
                return;
            }
            op = operations.shift();
            support = this.supportedDispatcher[op.attr];
        } while (!support);

        GridView.setAttributeAt(op.x, op.y, op.attr, op.value);
    },
};
<|endoftext|>


<commit_before>var M={}, V={}, C={};

var Data=Backbone.Collection.extend({
	url: "services/getData.js"
});

var DetailView=Backbone.View.extend({
	template: _.template('
Id: <%= id %>

Titolo: <%= title %>

Testo: <%= text %>
'),

	initialize: function(){
		this.model.bind("reset",this.render,this);
	},

	render: function(){
		if (!this.options.detailId) return;
		var reqInstance=this.model.get(this.options.detailId);
		if (!reqInstance) return; /* mhm? */
		this.el.html( this.template(reqInstance.toJSON()) );
		return this;
	}
});

M.data=new Data();

C.renderDetail=function(type,match,ui){
	if (!match) return;
	if (M.data.isEmpty()){
		M.data.fetch();
	}
	C.appBooted.then(function(){
		var params=C.router.getParams(match[1]);
		if (params){
			V.detail.options.detailId=params.id;
			V.detail.render();
		}
	});
};

C.router=new $.mobile.Router({
	"#detail([?].*)?": {
		handler: C.renderDetail, events: "bs"
	}
});

C.appBooted=$.Deferred();

$(function(){
	V.detail=new DetailView({
		model: M.data, detailId: null, el: $("#detail :jqmData(role='content')")
	});
	C.appBooted.resolve();
});

<commit_msg>The backbone example was cleaned up a bit... Hope it can be more useful now.<commit_after>var M={}, V={}, C={};

var Data=Backbone.Collection.extend({
	url: "services/getData.js"
});

var DetailView=Backbone.View.extend({
	template: _.template('
Id: <%= id %>

Titolo: <%= title %>

Testo: <%= text %>
'),

	initialize: function(){
		this.model.bind("reset",this.render,this);
	},

	render: function(){
		if (!this.options.detailId) return;
		var reqInstance=this.model.get(this.options.detailId);
		if (!reqInstance) return; /* mhm? */
		this.el.html( this.template(reqInstance.toJSON()) );
		return this;
	}
});

M.data=new Data();

C.renderDetail=function(type,match,ui){
	if (!match) return;
	if (!V.detail){
		V.detail=new DetailView({
			model: M.data, detailId: null, el: $("#detail :jqmData(role='content')")
		});
	}
	var params=C.router.getParams(match[1]);
	if (params){
		V.detail.options.detailId=params.id;
	}
	if (M.data.isEmpty()){
		M.data.fetch();
	}
};

C.router=new $.mobile.Router({
	"#detail([?].*)?": {
		handler: C.renderDetail, events: "bs"
	}
});

<|endoftext|>


<commit_before>define(['angular'], function(angular) {

    var config = angular.module('app-config', []);
    config
        .constant('APP_FLAGS', {
            'showSidebar' : true,
            'showSearch' : true,
            'enableToggle': true,
            'defaultView' : 'expanded',
            'compact' : true,
            'expanded' : true,
            'features' : true,
            'isWeb' : true,
            'defaultTheme' : 'group'
        })
        .constant('SERVICE_LOC', {
            'aboutURL' : '/portal/web/session.json',
            'sessionInfo' : '/portal/web/session.json',
            'sidebarInfo' : '/web/staticFeeds/sidebar.json',
            'featuresInfo' : '/web/staticFeeds/features.json',
            'newstuffInfo': '/web/staticFeeds/new-stuff.json',
            'context'     : '/portal/',
            'base'        : '/portal/web/',
            'layout'      : 'layoutDoc?tab=UW Bucky Home',
            'layoutTab' : 'UW Bucky Home',
            'marketplace' : {
                'base' : 'marketplace/',
                'entry' : 'entry/',
                'entries' : 'entries.json'
            },
            'notificationsURL' : '/web/staticFeeds/notifications.json',
            'groupURL' : '/portal/api/groups',
            'kvURL' : '/storage/',
            'googleSearchURL' : '/web/staticFeeds/gcs.json'
        })
        .constant('NAMES', {
            'title' : 'MyUW',
            'ariaLabelTitle' : 'My U W',
            'crest' : 'img/uwcrest_web_sm.png',
            'crestalt' : 'UW Crest',
            'sublogo' : ''
        })
        .constant('SEARCH',{

        })
        .constant('NOTIFICATION', {
            'enabled' : true,
            'groupFiltering' : true,
            'notificationFullURL' : 'notifications'
        })
        .constant('MISC_URLS',{
            'feedbackURL' : '/portal/p/feedback',
            'back2ClassicURL' : '/portal/Login?profile=default',
            'whatsNewURL' : 'https://kb.wisc.edu/myuw/page.php?id=48181',
            'helpdeskURL' : 'https://kb.wisc.edu/helpdesk/',
            'webSearchURL' : 'http://www.wisc.edu/search/?q=',
            'webSearchDomain' : "wisc.edu",
            'directorySearchURL' : 'http://www.wisc.edu/directories/?q=',
            'kbSearchURL' : 'https://kb.wisc.edu/search.php?q=',
            'eventsSearchURL' : 'https://today.wisc.edu/events/search?term=',
            'loginURL' : '/portal/Login?profile=bucky',
            'logoutURL' : '/portal/Logout',
            'rootURL' : '/web',
            'addToHomeURLS' : {
              'layoutURL' : '/portal/web/layoutDoc?tab=UW Bucky Home',
              'addToHomeActionURL' : '/portal/web/layout?tabName=UW Bucky Home&action=addPortlet&fname='
            }
        })
        .constant('FOOTER_URLS', [
          { "url" : "static/myuw-help",
            "target" : "_blank",
            "title" : "Help"
          },
          { "url" : "https://my.wisc.edu/portal/p/feedback",
            "target" : "_blank",
            "title" : "Feedback"
          },
          { "url" : "features",
            "target" : "",
            "title" : "What's New"
          },
          { "url" : "/portal/Login?profile=default",
            "target" : "",
            "title" : "Old MyUW"
          },
        ])
        .constant('APP_BETA_FEATURES', [
          {
            "id" : "webPortletRender",
            "title" : "/web portlet rendering",
            "description" : "Renders portlets via /web's exclusive page, but only as launched from compact-mode widgets"
          },
          {
            "id" : "showKeywordsInMarketplace",
            "title" : "Show Keywords in Marketplace",
            "description" : "Enable/Disable keywords showing up in marketplace details"
          },
          {
            "id" : "typeaheadSearch",
            "title" : "Enable typeahead in the portlet search bar",
            "description" : "Enable/Disable the typeahead in the portlet search bar (I think this is broken)"
          },
          {
            "id" : "exampleWidgets",
            "title" : "Example Widgets",
            "description" : "Show the My Courses, Email, and Calendar example widgets"
          },
          {
            "id" : "showFilterOption",
            "title" : "Show Filter Option on Home",
            "description" : "Enables a filter on home to filter ones content down to what want"
          }
        ]);

    return config;

});
<commit_msg>mocking out the first variable in gcsURL<commit_after>define(['angular'], function(angular) {

    var config = angular.module('app-config', []);
    config
        .constant('APP_FLAGS', {
            'showSidebar' : true,
            'showSearch' : true,
            'enableToggle': true,
            'defaultView' : 'expanded',
            'compact' : true,
            'expanded' : true,
            'features' : true,
            'isWeb' : true,
            'defaultTheme' : 'group'
        })
        .constant('SERVICE_LOC', {
            'aboutURL' : '/portal/web/session.json',
            'sessionInfo' : '/portal/web/session.json',
            'sidebarInfo' : '/web/staticFeeds/sidebar.json',
            'featuresInfo' : '/web/staticFeeds/features.json',
            'newstuffInfo': '/web/staticFeeds/new-stuff.json',
            'context'     : '/portal/',
            'base'        : '/portal/web/',
            'layout'      : 'layoutDoc?tab=UW Bucky Home',
            'layoutTab' : 'UW Bucky Home',
            'marketplace' : {
                'base' : 'marketplace/',
                'entry' : 'entry/',
                'entries' : 'entries.json'
            },
            'notificationsURL' : '/web/staticFeeds/notifications.json',
            'groupURL' : '/portal/api/groups',
            'kvURL' : '/storage/',
            'googleSearchURL' : '/web/staticFeeds/gcs.json?firstVariablemock=true'
        })
        .constant('NAMES', {
            'title' : 'MyUW',
            'ariaLabelTitle' : 'My U W',
            'crest' : 'img/uwcrest_web_sm.png',
            'crestalt' : 'UW Crest',
            'sublogo' : ''
        })
        .constant('SEARCH',{

        })
        .constant('NOTIFICATION', {
            'enabled' : true,
            'groupFiltering' : true,
            'notificationFullURL' : 'notifications'
        })
        .constant('MISC_URLS',{
            'feedbackURL' : '/portal/p/feedback',
            'back2ClassicURL' : '/portal/Login?profile=default',
            'whatsNewURL' : 'https://kb.wisc.edu/myuw/page.php?id=48181',
            'helpdeskURL' : 'https://kb.wisc.edu/helpdesk/',
            'webSearchURL' : 'http://www.wisc.edu/search/?q=',
            'webSearchDomain' : "wisc.edu",
            'directorySearchURL' : 'http://www.wisc.edu/directories/?q=',
            'kbSearchURL' : 'https://kb.wisc.edu/search.php?q=',
            'eventsSearchURL' : 'https://today.wisc.edu/events/search?term=',
            'loginURL' : '/portal/Login?profile=bucky',
            'logoutURL' : '/portal/Logout',
            'rootURL' : '/web',
            'addToHomeURLS' : {
              'layoutURL' : '/portal/web/layoutDoc?tab=UW Bucky Home',
              'addToHomeActionURL' : '/portal/web/layout?tabName=UW Bucky Home&action=addPortlet&fname='
            }
        })
        .constant('FOOTER_URLS', [
          { "url" : "static/myuw-help",
            "target" : "_blank",
            "title" : "Help"
          },
          { "url" : "https://my.wisc.edu/portal/p/feedback",
            "target" : "_blank",
            "title" : "Feedback"
          },
          { "url" : "features",
            "target" : "",
            "title" : "What's New"
          },
          { "url" : "/portal/Login?profile=default",
            "target" : "",
            "title" : "Old MyUW"
          },
        ])
        .constant('APP_BETA_FEATURES', [
          {
            "id" : "webPortletRender",
            "title" : "/web portlet rendering",
            "description" : "Renders portlets via /web's exclusive page, but only as launched from compact-mode widgets"
          },
          {
            "id" : "showKeywordsInMarketplace",
            "title" : "Show Keywords in Marketplace",
            "description" : "Enable/Disable keywords showing up in marketplace details"
          },
          {
            "id" : "typeaheadSearch",
            "title" : "Enable typeahead in the portlet search bar",
            "description" : "Enable/Disable the typeahead in the portlet search bar (I think this is broken)"
          },
          {
            "id" : "exampleWidgets",
            "title" : "Example Widgets",
            "description" : "Show the My Courses, Email, and Calendar example widgets"
          },
          {
            "id" : "showFilterOption",
            "title" : "Show Filter Option on Home",
            "description" : "Enables a filter on home to filter ones content down to what want"
          }
        ]);

    return config;

});
<|endoftext|>


<commit_before>import {findWrapping, liftTarget, canSplit, ReplaceAroundStep} from "prosemirror-transform"
import {Slice, Fragment, NodeRange} from "prosemirror-model"

// :: NodeSpec
// An ordered list [node spec](#model.NodeSpec). Has a single
// attribute, `order`, which determines the number at which the list
// starts counting, and defaults to 1. Represented as an `<ol>`
// element.
export const orderedList = {
  attrs: {order: {default: 1}},
  parseDOM: [{tag: "ol", getAttrs(dom) {
    return {order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1}
  }}],
  toDOM(node) {
    return ["ol", {start: node.attrs.order == 1 ? null : node.attrs.order}, 0]
  }
}

// :: NodeSpec
// A bullet list node spec, represented in the DOM as `<ul>`.
export const bulletList = {
  parseDOM: [{tag: "ul"}],
  toDOM() { return ["ul", 0] }
}

// :: NodeSpec
// A list item (`<li>`) spec.
export const listItem = {
  parseDOM: [{tag: "li"}],
  toDOM() { return ["li", 0] },
  defining: true
}

function add(obj, props) {
  let copy = {}
  for (let prop in obj) copy[prop] = obj[prop]
  for (let prop in props) copy[prop] = props[prop]
  return copy
}

// :: (OrderedMap<NodeSpec>, string, ?string) → OrderedMap<NodeSpec>
// Convenience function for adding list-related node types to a map
// specifying the nodes for a schema. Adds
// [`orderedList`](#schema-list.orderedList) as `"ordered_list"`,
// [`bulletList`](#schema-list.bulletList) as `"bullet_list"`, and
// [`listItem`](#schema-list.listItem) as `"list_item"`.
//
// `itemContent` determines the content expression for the list items.
// If you want the commands defined in this module to apply to your
// list structure, it should have a shape like `"paragraph block*"` or
// `"paragraph (ordered_list | bullet_list)*"`. `listGroup` can be
// given to assign a group name to the list node types, for example
// `"block"`.
export function addListNodes(nodes, itemContent, listGroup) {
  return nodes.append({
    ordered_list: add(orderedList, {content: "list_item+", group: listGroup}),
    bullet_list: add(bulletList, {content: "list_item+", group: listGroup}),
    list_item: add(listItem, {content: itemContent})
  })
}

// :: (NodeType, ?Object) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Returns a command function that wraps the selection in a list with
// the given type an attributes. If `dispatch` is null, only return a
// value to indicate whether this is possible, but don't actually
// perform the change.
export function wrapInList(listType, attrs) {
  return function(state, dispatch) {
    let {$from, $to} = state.selection
    let range = $from.blockRange($to), doJoin = false, outerRange = range
    if (!range) return false
    // This is at the top of an existing list item
    if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex == 0) {
      // Don't do anything if this is the top of the list
      if ($from.index(range.depth - 1) == 0) return false
      let $insert = state.doc.resolve(range.start - 2)
      outerRange = new NodeRange($insert, $insert, range.depth)
      if (range.endIndex < range.parent.childCount)
        range = new NodeRange($from, state.doc.resolve($to.end(range.depth)), range.depth)
      doJoin = true
    }
    let wrap = findWrapping(outerRange, listType, attrs, range)
    if (!wrap) return false
    if (dispatch) dispatch(doWrapInList(state.tr, range, wrap, doJoin, listType).scrollIntoView())
    return true
  }
}

function doWrapInList(tr, range, wrappers, joinBefore, listType) {
  let content = Fragment.empty
  for (let i = wrappers.length - 1; i >= 0; i--)
    content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))

  tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end,
                                new Slice(content, 0, 0), wrappers.length, true))

  let found = 0
  for (let i = 0; i < wrappers.length; i++) if (wrappers[i].type == listType) found = i + 1
  let splitDepth = wrappers.length - found

  let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent
  for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
    if (!first && canSplit(tr.doc, splitPos, splitDepth)) tr.split(splitPos, splitDepth)
    splitPos += parent.child(i).nodeSize + (first ? 0 : 2 * splitDepth)
  }
  return tr
}

// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Build a command that splits a non-empty textblock at the top level
// of a list item by also splitting that list item.
export function splitListItem(itemType) {
  return function(state, dispatch) {
    let {$from, $to, node} = state.selection
    if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) return false
    let grandParent = $from.node(-1)
    if (grandParent.type != itemType) return false
    if ($from.parent.content.size == 0) {
      // In an empty block. If this is a nested list, the wrapping
      // list item should be split. Otherwise, bail out and let next
      // command handle lifting.
      if ($from.depth == 2 || $from.node(-3).type != itemType ||
          $from.index(-2) != $from.node(-2).childCount - 1) return false
      if (dispatch) {
        let wrap = Fragment.empty, keepItem = $from.index(-1) > 0
        // Build a fragment containing empty versions of the structure
        // from the outer list item to the parent node of the cursor
        for (let d = $from.depth - (keepItem ? 1 : 2); d >= $from.depth - 3; d--)
          wrap = Fragment.from($from.node(d).copy(wrap))
        // Add a second list item with an empty default start node
        wrap = wrap.append(Fragment.from(itemType.createAndFill()))
        let tr = state.tr.replace($from.before(keepItem ? null : -1), $from.after(-3), new Slice(wrap, keepItem ? 3 : 2, 2))
        tr.setSelection(state.selection.constructor.near(tr.doc.resolve($from.pos + (keepItem ? 3 : 2))))
        dispatch(tr.scrollIntoView())
      }
      return true
    }
    let nextType = $to.pos == $from.end() ? grandParent.defaultContentType(0) : null
    let tr = state.tr.delete($from.pos, $to.pos)
    let types = nextType && [null, {type: nextType}]
    if (!canSplit(tr.doc, $from.pos, 2, types)) return false
    if (dispatch) dispatch(tr.split($from.pos, 2, types).scrollIntoView())
    return true
  }
}

// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Create a command to lift the list item around the selection up into
// a wrapping list.
export function liftListItem(itemType) {
  return function(state, dispatch) {
    let {$from, $to} = state.selection
    let range = $from.blockRange($to, node => node.childCount && node.firstChild.type == itemType)
    if (!range) return false
    if (!dispatch) return true
    if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
      return liftToOuterList(state, dispatch, itemType, range)
    else // Outer list node
      return liftOutOfList(state, dispatch, range)
  }
}

function liftToOuterList(state, dispatch, itemType, range) {
  let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth)
  if (end < endOfList) {
    // There are siblings after the lifted items, which must become
    // children of the last item
    tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList,
                                  new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true))
    range = new NodeRange(tr.doc.resolveNoCache(range.$from.pos), tr.doc.resolveNoCache(endOfList), range.depth)
  }
  dispatch(tr.lift(range, liftTarget(range)).scrollIntoView())
  return true
}

function liftOutOfList(state, dispatch, range) {
  let tr = state.tr, list = range.parent
  // Merge the list items into a single big item
  for (let pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
    pos -= list.child(i).nodeSize
    tr.delete(pos - 1, pos + 1)
  }
  let $start = tr.doc.resolve(range.start), item = $start.nodeAfter
  let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount
  let parent = $start.node(-1), indexBefore = $start.index(-1)
  if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
                         item.content.append(atEnd ? Fragment.empty : Fragment.from(list))))
    return false
  let start = $start.pos, end = start + item.nodeSize
  // Strip off the surrounding list. At the sides where we're not at
  // the end of the list, the existing list is closed. At sides where
  // this is the end, it is overwritten to its end.
  tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
                                new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty)))
                                          .append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))),
                                          atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1))
  dispatch(tr.scrollIntoView())
  return true
}

// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Create a command to sink the list item around the selection down
// into an inner list.
export function sinkListItem(itemType) {
  return function(state, dispatch) {
    let {$from, $to} = state.selection
    let range = $from.blockRange($to, node => node.childCount && node.firstChild.type == itemType)
    if (!range) return false
    let startIndex = range.startIndex
    if (startIndex == 0) return false
    let parent = range.parent, nodeBefore = parent.child(startIndex - 1)
    if (nodeBefore.type != itemType) return false

    if (dispatch) {
      let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type
      let inner = Fragment.from(nestedBefore ? itemType.create() : null)
      let slice = new Slice(Fragment.from(itemType.create(null, Fragment.from(parent.copy(inner)))),
                            nestedBefore ? 3 : 1, 0)
      let before = range.start, after = range.end
      dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
                                                   before, after, slice, 1, true))
               .scrollIntoView())
    }
    return true
  }
}
<commit_msg>Drop use of undocumented method<commit_after>import {findWrapping, liftTarget, canSplit, ReplaceAroundStep} from "prosemirror-transform"
import {Slice, Fragment, NodeRange} from "prosemirror-model"

// :: NodeSpec
// An ordered list [node spec](#model.NodeSpec). Has a single
// attribute, `order`, which determines the number at which the list
// starts counting, and defaults to 1. Represented as an `<ol>`
// element.
export const orderedList = {
  attrs: {order: {default: 1}},
  parseDOM: [{tag: "ol", getAttrs(dom) {
    return {order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1}
  }}],
  toDOM(node) {
    return ["ol", {start: node.attrs.order == 1 ? null : node.attrs.order}, 0]
  }
}

// :: NodeSpec
// A bullet list node spec, represented in the DOM as `<ul>`.
export const bulletList = {
  parseDOM: [{tag: "ul"}],
  toDOM() { return ["ul", 0] }
}

// :: NodeSpec
// A list item (`<li>`) spec.
export const listItem = {
  parseDOM: [{tag: "li"}],
  toDOM() { return ["li", 0] },
  defining: true
}

function add(obj, props) {
  let copy = {}
  for (let prop in obj) copy[prop] = obj[prop]
  for (let prop in props) copy[prop] = props[prop]
  return copy
}

// :: (OrderedMap<NodeSpec>, string, ?string) → OrderedMap<NodeSpec>
// Convenience function for adding list-related node types to a map
// specifying the nodes for a schema. Adds
// [`orderedList`](#schema-list.orderedList) as `"ordered_list"`,
// [`bulletList`](#schema-list.bulletList) as `"bullet_list"`, and
// [`listItem`](#schema-list.listItem) as `"list_item"`.
//
// `itemContent` determines the content expression for the list items.
// If you want the commands defined in this module to apply to your
// list structure, it should have a shape like `"paragraph block*"` or
// `"paragraph (ordered_list | bullet_list)*"`. `listGroup` can be
// given to assign a group name to the list node types, for example
// `"block"`.
export function addListNodes(nodes, itemContent, listGroup) {
  return nodes.append({
    ordered_list: add(orderedList, {content: "list_item+", group: listGroup}),
    bullet_list: add(bulletList, {content: "list_item+", group: listGroup}),
    list_item: add(listItem, {content: itemContent})
  })
}

// :: (NodeType, ?Object) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Returns a command function that wraps the selection in a list with
// the given type an attributes. If `dispatch` is null, only return a
// value to indicate whether this is possible, but don't actually
// perform the change.
export function wrapInList(listType, attrs) {
  return function(state, dispatch) {
    let {$from, $to} = state.selection
    let range = $from.blockRange($to), doJoin = false, outerRange = range
    if (!range) return false
    // This is at the top of an existing list item
    if (range.depth >= 2 && $from.node(range.depth - 1).type.compatibleContent(listType) && range.startIndex == 0) {
      // Don't do anything if this is the top of the list
      if ($from.index(range.depth - 1) == 0) return false
      let $insert = state.doc.resolve(range.start - 2)
      outerRange = new NodeRange($insert, $insert, range.depth)
      if (range.endIndex < range.parent.childCount)
        range = new NodeRange($from, state.doc.resolve($to.end(range.depth)), range.depth)
      doJoin = true
    }
    let wrap = findWrapping(outerRange, listType, attrs, range)
    if (!wrap) return false
    if (dispatch) dispatch(doWrapInList(state.tr, range, wrap, doJoin, listType).scrollIntoView())
    return true
  }
}

function doWrapInList(tr, range, wrappers, joinBefore, listType) {
  let content = Fragment.empty
  for (let i = wrappers.length - 1; i >= 0; i--)
    content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))

  tr.step(new ReplaceAroundStep(range.start - (joinBefore ? 2 : 0), range.end, range.start, range.end,
                                new Slice(content, 0, 0), wrappers.length, true))

  let found = 0
  for (let i = 0; i < wrappers.length; i++) if (wrappers[i].type == listType) found = i + 1
  let splitDepth = wrappers.length - found

  let splitPos = range.start + wrappers.length - (joinBefore ? 2 : 0), parent = range.parent
  for (let i = range.startIndex, e = range.endIndex, first = true; i < e; i++, first = false) {
    if (!first && canSplit(tr.doc, splitPos, splitDepth)) tr.split(splitPos, splitDepth)
    splitPos += parent.child(i).nodeSize + (first ? 0 : 2 * splitDepth)
  }
  return tr
}

// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Build a command that splits a non-empty textblock at the top level
// of a list item by also splitting that list item.
export function splitListItem(itemType) {
  return function(state, dispatch) {
    let {$from, $to, node} = state.selection
    if ((node && node.isBlock) || $from.depth < 2 || !$from.sameParent($to)) return false
    let grandParent = $from.node(-1)
    if (grandParent.type != itemType) return false
    if ($from.parent.content.size == 0) {
      // In an empty block. If this is a nested list, the wrapping
      // list item should be split. Otherwise, bail out and let next
      // command handle lifting.
      if ($from.depth == 2 || $from.node(-3).type != itemType ||
          $from.index(-2) != $from.node(-2).childCount - 1) return false
      if (dispatch) {
        let wrap = Fragment.empty, keepItem = $from.index(-1) > 0
        // Build a fragment containing empty versions of the structure
        // from the outer list item to the parent node of the cursor
        for (let d = $from.depth - (keepItem ? 1 : 2); d >= $from.depth - 3; d--)
          wrap = Fragment.from($from.node(d).copy(wrap))
        // Add a second list item with an empty default start node
        wrap = wrap.append(Fragment.from(itemType.createAndFill()))
        let tr = state.tr.replace($from.before(keepItem ? null : -1), $from.after(-3), new Slice(wrap, keepItem ? 3 : 2, 2))
        tr.setSelection(state.selection.constructor.near(tr.doc.resolve($from.pos + (keepItem ? 3 : 2))))
        dispatch(tr.scrollIntoView())
      }
      return true
    }
    let nextType = $to.pos == $from.end() ? grandParent.defaultContentType(0) : null
    let tr = state.tr.delete($from.pos, $to.pos)
    let types = nextType && [null, {type: nextType}]
    if (!canSplit(tr.doc, $from.pos, 2, types)) return false
    if (dispatch) dispatch(tr.split($from.pos, 2, types).scrollIntoView())
    return true
  }
}

// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Create a command to lift the list item around the selection up into
// a wrapping list.
export function liftListItem(itemType) {
  return function(state, dispatch) {
    let {$from, $to} = state.selection
    let range = $from.blockRange($to, node => node.childCount && node.firstChild.type == itemType)
    if (!range) return false
    if (!dispatch) return true
    if ($from.node(range.depth - 1).type == itemType) // Inside a parent list
      return liftToOuterList(state, dispatch, itemType, range)
    else // Outer list node
      return liftOutOfList(state, dispatch, range)
  }
}

function liftToOuterList(state, dispatch, itemType, range) {
  let tr = state.tr, end = range.end, endOfList = range.$to.end(range.depth)
  if (end < endOfList) {
    // There are siblings after the lifted items, which must become
    // children of the last item
    tr.step(new ReplaceAroundStep(end - 1, endOfList, end, endOfList,
                                  new Slice(Fragment.from(itemType.create(null, range.parent.copy())), 1, 0), 1, true))
    range = new NodeRange(tr.doc.resolve(range.$from.pos), tr.doc.resolve(endOfList), range.depth)
  }
  dispatch(tr.lift(range, liftTarget(range)).scrollIntoView())
  return true
}

function liftOutOfList(state, dispatch, range) {
  let tr = state.tr, list = range.parent
  // Merge the list items into a single big item
  for (let pos = range.end, i = range.endIndex - 1, e = range.startIndex; i > e; i--) {
    pos -= list.child(i).nodeSize
    tr.delete(pos - 1, pos + 1)
  }
  let $start = tr.doc.resolve(range.start), item = $start.nodeAfter
  let atStart = range.startIndex == 0, atEnd = range.endIndex == list.childCount
  let parent = $start.node(-1), indexBefore = $start.index(-1)
  if (!parent.canReplace(indexBefore + (atStart ? 0 : 1), indexBefore + 1,
                         item.content.append(atEnd ? Fragment.empty : Fragment.from(list))))
    return false
  let start = $start.pos, end = start + item.nodeSize
  // Strip off the surrounding list. At the sides where we're not at
  // the end of the list, the existing list is closed. At sides where
  // this is the end, it is overwritten to its end.
  tr.step(new ReplaceAroundStep(start - (atStart ? 1 : 0), end + (atEnd ? 1 : 0), start + 1, end - 1,
                                new Slice((atStart ? Fragment.empty : Fragment.from(list.copy(Fragment.empty)))
                                          .append(atEnd ? Fragment.empty : Fragment.from(list.copy(Fragment.empty))),
                                          atStart ? 0 : 1, atEnd ? 0 : 1), atStart ? 0 : 1))
  dispatch(tr.scrollIntoView())
  return true
}

// :: (NodeType) → (state: EditorState, dispatch: ?(tr: Transaction)) → bool
// Create a command to sink the list item around the selection down
// into an inner list.
export function sinkListItem(itemType) {
  return function(state, dispatch) {
    let {$from, $to} = state.selection
    let range = $from.blockRange($to, node => node.childCount && node.firstChild.type == itemType)
    if (!range) return false
    let startIndex = range.startIndex
    if (startIndex == 0) return false
    let parent = range.parent, nodeBefore = parent.child(startIndex - 1)
    if (nodeBefore.type != itemType) return false

    if (dispatch) {
      let nestedBefore = nodeBefore.lastChild && nodeBefore.lastChild.type == parent.type
      let inner = Fragment.from(nestedBefore ? itemType.create() : null)
      let slice = new Slice(Fragment.from(itemType.create(null, Fragment.from(parent.copy(inner)))),
                            nestedBefore ? 3 : 1, 0)
      let before = range.start, after = range.end
      dispatch(state.tr.step(new ReplaceAroundStep(before - (nestedBefore ? 3 : 1), after,
                                                   before, after, slice, 1, true))
               .scrollIntoView())
    }
    return true
  }
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

'use strict';

const getInverseDependencies = require('./getInverseDependencies');
const querystring = require('querystring');
const url = require('url');

import type {ResolutionResponse} from './getInverseDependencies';
import type {Server as HTTPServer} from 'http';
import type {Server as HTTPSServer} from 'https';

const blacklist = [
  'Libraries/Utilities/HMRClient.js',
];

type HMRBundle = {
  getModulesIdsAndCode(): Array<{id: string, code: string}>,
  getSourceMappingURLs(): Array<mixed>,
  getSourceURLs(): Array<mixed>,
  isEmpty(): boolean,
};

type DependencyOptions = {|
  +dev: boolean,
  +entryFile: string,
  +hot: boolean,
  +minify: boolean,
  +platform: ?string,
  +recursive: boolean,
|};

/**
 * This is a subset of the actual `metro-bundler`'s `Server` class,
 * without all the stuff we don't need to know about. This allows us to use
 * `attachHMRServer` with different versions of `metro-bundler` as long as
 * this specific contract is maintained.
 */
type PackagerServer<TModule> = {
  buildBundleForHMR(
    options: {platform: ?string},
    host: string,
    port: number,
  ): Promise<HMRBundle>,
  getDependencies(options: DependencyOptions): Promise<ResolutionResponse<TModule>>,
  getModuleForPath(entryFile: string): Promise<TModule>,
  getShallowDependencies(options: DependencyOptions): Promise<Array<TModule>>,
  setHMRFileChangeListener(listener: ?(type: string, filePath: string) => mixed): void,
};

type HMROptions<TModule> = {
  httpServer: HTTPServer | HTTPSServer,
  packagerServer: PackagerServer<TModule>,
  path: string,
};

type Moduleish = {
  getName(): Promise<string>,
  isAsset(): boolean,
  isJSON(): boolean,
  path: string,
};

/**
 * Attaches a WebSocket based connection to the Packager to expose
 * Hot Module Replacement updates to the simulator.
 */
function attachHMRServer<TModule: Moduleish>(
  {httpServer, path, packagerServer}: HMROptions<TModule>,
) {
  let client = null;

  function disconnect() {
    client = null;
    packagerServer.setHMRFileChangeListener(null);
  }

  // For the give platform and entry file, returns a promise with:
  //   - The full list of dependencies.
  //   - The shallow dependencies each file on the dependency list has
  //   - Inverse shallow dependencies map
  async function getDependencies(platform: string, bundleEntry: string): Promise<{
    dependenciesCache: Array<string>,
    dependenciesModulesCache: {[mixed]: TModule},
    shallowDependencies: {[string]: Array<TModule>},
    inverseDependenciesCache: mixed,
    resolutionResponse: ResolutionResponse<TModule>,
  }> {
    const response = await packagerServer.getDependencies({
      dev: true,
      entryFile: bundleEntry,
      hot: true,
      minify: false,
      platform: platform,
      recursive: true,
    });

    /* $FlowFixMe: getModuleId might be null */
    const {getModuleId}: {getModuleId: () => number} = response;

    // for each dependency builds the object:
    // `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}`
    const deps: Array<{
      path: string,
      name?: string,
      deps: Array<TModule>,
    }> = await Promise.all(response.dependencies.map(async (dep: TModule) => {
      const depName = await dep.getName();

      if (dep.isAsset() || dep.isJSON()) {
        return {path: dep.path, deps: []};
      }
      const dependencies = await packagerServer.getShallowDependencies({
        dev: true,
        entryFile: dep.path,
        hot: true,
        minify: false,
        platform: platform,
        recursive: true,
      });

      return {
        path: dep.path,
        name: depName,
        deps: dependencies,
      };
    }));

    // list with all the dependencies' filenames the bundle entry has
    const dependenciesCache = response.dependencies.map(dep => dep.path);

    // map from module name to path
    const moduleToFilenameCache = Object.create(null);
    deps.forEach(dep => {
      /* $FlowFixMe: `name` could be null, but `deps` would be as well. */
      moduleToFilenameCache[dep.name] = dep.path;
    });

    // map that indicates the shallow dependency each file included on the
    // bundle has
    const shallowDependencies = Object.create(null);
    deps.forEach(dep => {
      shallowDependencies[dep.path] = dep.deps;
    });

    // map from module name to the modules' dependencies the bundle entry
    // has
    const dependenciesModulesCache = Object.create(null);
    response.dependencies.forEach(dep => {
      dependenciesModulesCache[getModuleId(dep)] = dep;
    });

    const inverseDependenciesCache = Object.create(null);
    const inverseDependencies = getInverseDependencies(response);
    for (const [module, dependents] of inverseDependencies) {
      inverseDependenciesCache[getModuleId(module)] =
        Array.from(dependents).map(getModuleId);
    }

    return {
      dependenciesCache,
      dependenciesModulesCache,
      shallowDependencies,
      inverseDependenciesCache,
      resolutionResponse: response,
    };
  }

  async function prepareResponse(filename): Object {
    try {
      const bundle = await generateBundle(filename);

      if (!client || !bundle || bundle.isEmpty()) {
        return;
      }

      return {
        type: 'update',
        body: {
          modules: bundle.getModulesIdsAndCode(),
          inverseDependencies: client.inverseDependenciesCache,
          sourceURLs: bundle.getSourceURLs(),
          sourceMappingURLs: bundle.getSourceMappingURLs(),
        },
      };
    } catch (error) {
      // send errors to the client instead of killing packager server
      let body;
      if (error.type === 'TransformError' ||
          error.type === 'NotFoundError' ||
          error.type === 'UnableToResolveError') {
        body = {
          type: error.type,
          description: error.description,
          filename: error.filename,
          lineNumber: error.lineNumber,
        };
      } else {
        console.error(error.stack || error);
        body = {
          type: 'InternalError',
          description: 'react-packager has encountered an internal error, ' +
            'please check your terminal error output for more details',
        };
      }

      return {type: 'error', body};
    }
  }

  async function generateBundle(filename) {
    if (client === null) {
      return;
    }

    const deps = await packagerServer.getShallowDependencies({
      dev: true,
      minify: false,
      entryFile: filename,
      hot: true,
      platform: client.platform,
      recursive: true,
    });

    if (client === null) {
      return;
    }

    // if the file dependencies have change we need to invalidate the
    // dependencies caches because the list of files we need to send
    // to the client may have changed
    const oldDependencies = client.shallowDependencies[filename];

    let resolutionResponse;

    if (arrayEquals(deps, oldDependencies)) {
      // Need to create a resolution response to pass to the bundler
      // to process requires after transform. By providing a
      // specific response we can compute a non recursive one which
      // is the least we need and improve performance.
      const response = await packagerServer.getDependencies({
        dev: true,
        entryFile: filename,
        hot: true,
        minify: false,
        platform: client.platform,
        recursive: true,
      });

      const module = await packagerServer.getModuleForPath(filename);

      resolutionResponse = await response.copy({dependencies: [module]});
    } else {
      // if there're new dependencies compare the full list of
      // dependencies we used to have with the one we now have
      const {
        dependenciesCache: depsCache,
        dependenciesModulesCache: depsModulesCache,
        shallowDependencies: shallowDeps,
        inverseDependenciesCache: inverseDepsCache,
        resolutionResponse: myResolutionReponse,
      } = await getDependencies(client.platform, client.bundleEntry);

      if (client === null) {
        return;
      }

      const moduleToUpdate = await packagerServer.getModuleForPath(filename);

      if (client === null) {
        return;
      }

      // build list of modules for which we'll send HMR updates
      const modulesToUpdate = [moduleToUpdate];
      Object.keys(depsModulesCache).forEach(module => {
        if (!client || !client.dependenciesModulesCache[module]) {
          modulesToUpdate.push(depsModulesCache[module]);
        }
      });

      // Need to send modules to the client in an order it can
      // process them: if a new dependency graph was uncovered
      // because a new dependency was added, the file that was
      // changed, which is the root of the dependency tree that
      // will be sent, needs to be the last module that gets
      // processed. Reversing the new modules makes sense
      // because we get them through the resolver which returns
      // a BFS ordered list.
      modulesToUpdate.reverse();

      // invalidate caches
      client.dependenciesCache = depsCache;
      client.dependenciesModulesCache = depsModulesCache;
      client.shallowDependencies = shallowDeps;
      client.inverseDependenciesCache = inverseDepsCache;

      resolutionResponse = await myResolutionReponse.copy({
        dependencies: modulesToUpdate,
      });
    }

    // make sure the file was modified is part of the bundle
    if (!client || !client.shallowDependencies[filename]) {
      return;
    }

    const httpServerAddress = httpServer.address();

    // Sanitize the value from the HTTP server
    let packagerHost = 'localhost';
    if (httpServer.address().address &&
        httpServer.address().address !== '::' &&
        httpServer.address().address !== '') {
      packagerHost = httpServerAddress.address;
    }

    const bundle: HMRBundle = await packagerServer.buildBundleForHMR(
      {
        entryFile: client.bundleEntry,
        platform: client.platform,
        resolutionResponse,
      },
      packagerHost,
      httpServerAddress.port,
    );

    return bundle;
  }

  const WebSocketServer = require('ws').Server;
  const wss = new WebSocketServer({
    server: httpServer,
    path: path,
  });

  wss.on('connection', async ws => {
    /* $FlowFixMe: url might be null */
    const params = querystring.parse(url.parse(ws.upgradeReq.url).query);

    try {
      const {
        dependenciesCache,
        dependenciesModulesCache,
        shallowDependencies,
        inverseDependenciesCache,
      } = await getDependencies(params.platform, params.bundleEntry);

      client = {
        ws,
        platform: params.platform,
        bundleEntry: params.bundleEntry,
        dependenciesCache,
        dependenciesModulesCache,
        shallowDependencies,
        inverseDependenciesCache,
      };

      packagerServer.setHMRFileChangeListener(async (type, filename) => {
        if (client === null) {
          return;
        }

        const blacklisted = blacklist.find(
          blacklistedPath => filename.indexOf(blacklistedPath) !== -1,
        );
        if (blacklisted) {
          return;
        }

        client.ws.send(JSON.stringify({type: 'update-start'}));

        if (type !== 'delete') {
          const response = await prepareResponse(filename);

          if (client && response) {
            client.ws.send(JSON.stringify(response));
          }
        }

        client.ws.send(JSON.stringify({type: 'update-done'}));
      });

      client.ws.on('error', e => {
        console.error('[Hot Module Replacement] Unexpected error', e);
        disconnect();
      });

      client.ws.on('close', () => disconnect());
    } catch (err) {
      throw err;
    }
  });
}

function arrayEquals<T>(arrayA: Array<T>, arrayB: Array<T>): boolean {
  arrayA = arrayA || [];
  arrayB = arrayB || [];
  return (
    arrayA.length === arrayB.length &&
    arrayA.every((element, index) => {
      return element === arrayB[index];
    })
  );
}

module.exports = attachHMRServer;
<commit_msg>Add multi-client support for HMR<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

'use strict';

const getInverseDependencies = require('./getInverseDependencies');
const querystring = require('querystring');
const url = require('url');

import type {ResolutionResponse} from './getInverseDependencies';
import type {Server as HTTPServer} from 'http';
import type {Server as HTTPSServer} from 'https';
import type {Client as WebSocketClient} from 'ws';

const blacklist = [
  'Libraries/Utilities/HMRClient.js',
];

type HMRBundle = {
  getModulesIdsAndCode(): Array<{id: string, code: string}>,
  getSourceMappingURLs(): Array<mixed>,
  getSourceURLs(): Array<mixed>,
  isEmpty(): boolean,
};

type DependencyOptions = {|
  +dev: boolean,
  +entryFile: string,
  +hot: boolean,
  +minify: boolean,
  +platform: ?string,
  +recursive: boolean,
|};

/**
 * This is a subset of the actual `metro-bundler`'s `Server` class,
 * without all the stuff we don't need to know about. This allows us to use
 * `attachHMRServer` with different versions of `metro-bundler` as long as
 * this specific contract is maintained.
 */
type PackagerServer<TModule> = {
  buildBundleForHMR(
    options: {platform: ?string},
    host: string,
    port: number,
  ): Promise<HMRBundle>,
  getDependencies(options: DependencyOptions): Promise<ResolutionResponse<TModule>>,
  getModuleForPath(entryFile: string): Promise<TModule>,
  getShallowDependencies(options: DependencyOptions): Promise<Array<TModule>>,
  setHMRFileChangeListener(listener: ?(type: string, filePath: string) => mixed): void,
};

type HMROptions<TModule> = {
  httpServer: HTTPServer | HTTPSServer,
  packagerServer: PackagerServer<TModule>,
  path: string,
};

type Moduleish = {
  getName(): Promise<string>,
  isAsset(): boolean,
  isJSON(): boolean,
  path: string,
};

/**
 * Attaches a WebSocket based connection to the Packager to expose
 * Hot Module Replacement updates to the simulator.
 */
function attachHMRServer<TModule: Moduleish>(
  {httpServer, path, packagerServer}: HMROptions<TModule>,
) {
  type Client = {|
    ws: WebSocketClient,
    platform: string,
    bundleEntry: string,
    dependenciesCache: Array<string>,
    dependenciesModulesCache: {[mixed]: TModule},
    shallowDependencies: {[string]: Array<TModule>},
    inverseDependenciesCache: mixed,
  |};

  const clients: Set<Client> = new Set();

  function disconnect(client: Client) {
    clients.delete(client);

    // If there are no clients connected, stop listenig for file changes
    if (clients.size === 0) {
      packagerServer.setHMRFileChangeListener(null);
    }
  }

  // For the give platform and entry file, returns a promise with:
  //   - The full list of dependencies.
  //   - The shallow dependencies each file on the dependency list has
  //   - Inverse shallow dependencies map
  async function getDependencies(platform: string, bundleEntry: string): Promise<{
    dependenciesCache: Array<string>,
    dependenciesModulesCache: {[mixed]: TModule},
    shallowDependencies: {[string]: Array<TModule>},
    inverseDependenciesCache: mixed,
    resolutionResponse: ResolutionResponse<TModule>,
  }> {
    const response = await packagerServer.getDependencies({
      dev: true,
      entryFile: bundleEntry,
      hot: true,
      minify: false,
      platform: platform,
      recursive: true,
    });

    /* $FlowFixMe: getModuleId might be null */
    const {getModuleId}: {getModuleId: () => number} = response;

    // for each dependency builds the object:
    // `{path: '/a/b/c.js', deps: ['modA', 'modB', ...]}`
    const deps: Array<{
      path: string,
      name?: string,
      deps: Array<TModule>,
    }> = await Promise.all(response.dependencies.map(async (dep: TModule) => {
      const depName = await dep.getName();

      if (dep.isAsset() || dep.isJSON()) {
        return {path: dep.path, deps: []};
      }
      const dependencies = await packagerServer.getShallowDependencies({
        dev: true,
        entryFile: dep.path,
        hot: true,
        minify: false,
        platform: platform,
        recursive: true,
      });

      return {
        path: dep.path,
        name: depName,
        deps: dependencies,
      };
    }));

    // list with all the dependencies' filenames the bundle entry has
    const dependenciesCache = response.dependencies.map(dep => dep.path);

    // map from module name to path
    const moduleToFilenameCache = Object.create(null);
    deps.forEach(dep => {
      /* $FlowFixMe: `name` could be null, but `deps` would be as well. */
      moduleToFilenameCache[dep.name] = dep.path;
    });

    // map that indicates the shallow dependency each file included on the
    // bundle has
    const shallowDependencies = Object.create(null);
    deps.forEach(dep => {
      shallowDependencies[dep.path] = dep.deps;
    });

    // map from module name to the modules' dependencies the bundle entry
    // has
    const dependenciesModulesCache = Object.create(null);
    response.dependencies.forEach(dep => {
      dependenciesModulesCache[getModuleId(dep)] = dep;
    });

    const inverseDependenciesCache = Object.create(null);
    const inverseDependencies = getInverseDependencies(response);
    for (const [module, dependents] of inverseDependencies) {
      inverseDependenciesCache[getModuleId(module)] =
        Array.from(dependents).map(getModuleId);
    }

    return {
      dependenciesCache,
      dependenciesModulesCache,
      shallowDependencies,
      inverseDependenciesCache,
      resolutionResponse: response,
    };
  }

  async function prepareResponse(
    client: Client,
    filename: string,
  ): Promise<?Object> {
    try {
      const bundle = await generateBundle(client, filename);

      if (!bundle || bundle.isEmpty()) {
        return;
      }

      return {
        type: 'update',
        body: {
          modules: bundle.getModulesIdsAndCode(),
          inverseDependencies: client.inverseDependenciesCache,
          sourceURLs: bundle.getSourceURLs(),
          sourceMappingURLs: bundle.getSourceMappingURLs(),
        },
      };
    } catch (error) {
      // send errors to the client instead of killing packager server
      let body;
      if (error.type === 'TransformError' ||
          error.type === 'NotFoundError' ||
          error.type === 'UnableToResolveError') {
        body = {
          type: error.type,
          description: error.description,
          filename: error.filename,
          lineNumber: error.lineNumber,
        };
      } else {
        console.error(error.stack || error);
        body = {
          type: 'InternalError',
          description: 'react-packager has encountered an internal error, ' +
            'please check your terminal error output for more details',
        };
      }

      return {type: 'error', body};
    }
  }

  async function generateBundle(
    client: Client,
    filename: string,
  ): Promise<?HMRBundle> {
    const deps = await packagerServer.getShallowDependencies({
      dev: true,
      minify: false,
      entryFile: filename,
      hot: true,
      platform: client.platform,
      recursive: true,
    });

    // if the file dependencies have change we need to invalidate the
    // dependencies caches because the list of files we need to send
    // to the client may have changed
    const oldDependencies = client.shallowDependencies[filename];

    let resolutionResponse;

    if (arrayEquals(deps, oldDependencies)) {
      // Need to create a resolution response to pass to the bundler
      // to process requires after transform. By providing a
      // specific response we can compute a non recursive one which
      // is the least we need and improve performance.
      const response = await packagerServer.getDependencies({
        dev: true,
        entryFile: filename,
        hot: true,
        minify: false,
        platform: client.platform,
        recursive: true,
      });

      const module = await packagerServer.getModuleForPath(filename);

      resolutionResponse = await response.copy({dependencies: [module]});
    } else {
      // if there're new dependencies compare the full list of
      // dependencies we used to have with the one we now have
      const {
        dependenciesCache: depsCache,
        dependenciesModulesCache: depsModulesCache,
        shallowDependencies: shallowDeps,
        inverseDependenciesCache: inverseDepsCache,
        resolutionResponse: myResolutionReponse,
      } = await getDependencies(client.platform, client.bundleEntry);

      const moduleToUpdate = await packagerServer.getModuleForPath(filename);

      // build list of modules for which we'll send HMR updates
      const modulesToUpdate = [moduleToUpdate];
      Object.keys(depsModulesCache).forEach(module => {
        if (!client.dependenciesModulesCache[module]) {
          modulesToUpdate.push(depsModulesCache[module]);
        }
      });

      // Need to send modules to the client in an order it can
      // process them: if a new dependency graph was uncovered
      // because a new dependency was added, the file that was
      // changed, which is the root of the dependency tree that
      // will be sent, needs to be the last module that gets
      // processed. Reversing the new modules makes sense
      // because we get them through the resolver which returns
      // a BFS ordered list.
      modulesToUpdate.reverse();

      // invalidate caches
      client.dependenciesCache = depsCache;
      client.dependenciesModulesCache = depsModulesCache;
      client.shallowDependencies = shallowDeps;
      client.inverseDependenciesCache = inverseDepsCache;

      resolutionResponse = await myResolutionReponse.copy({
        dependencies: modulesToUpdate,
      });
    }

    // make sure the file was modified is part of the bundle
    if (!client.shallowDependencies[filename]) {
      return;
    }

    const httpServerAddress = httpServer.address();

    // Sanitize the value from the HTTP server
    let packagerHost = 'localhost';
    if (httpServer.address().address &&
        httpServer.address().address !== '::' &&
        httpServer.address().address !== '') {
      packagerHost = httpServerAddress.address;
    }

    const bundle: HMRBundle = await packagerServer.buildBundleForHMR(
      {
        entryFile: client.bundleEntry,
        platform: client.platform,
        resolutionResponse,
      },
      packagerHost,
      httpServerAddress.port,
    );

    return bundle;
  }

  function handleFileChange(
    type: string,
    filename: string,
  ): void {
    clients.forEach(
      client => sendFileChangeToClient(client, type, filename),
    );
  }

  async function sendFileChangeToClient(
    client: Client,
    type: string,
    filename: string,
  ): Promise<mixed> {
    const blacklisted = blacklist.find(
      blacklistedPath => filename.indexOf(blacklistedPath) !== -1,
    );
    if (blacklisted) {
      return;
    }

    if (clients.has(client)) {
      client.ws.send(JSON.stringify({type: 'update-start'}));
    }

    if (type !== 'delete') {
      const response = await prepareResponse(client, filename);

      if (response && clients.has(client)) {
        client.ws.send(JSON.stringify(response));
      }
    }

    if (clients.has(client)) {
      client.ws.send(JSON.stringify({type: 'update-done'}));
    }
  }

  const WebSocketServer = require('ws').Server;
  const wss = new WebSocketServer({
    server: httpServer,
    path: path,
  });

  wss.on('connection', async ws => {
    /* $FlowFixMe: url might be null */
    const params = querystring.parse(url.parse(ws.upgradeReq.url).query);

    const {
      dependenciesCache,
      dependenciesModulesCache,
      shallowDependencies,
      inverseDependenciesCache,
    } = await getDependencies(params.platform, params.bundleEntry);

    const client = {
      ws,
      platform: params.platform,
      bundleEntry: params.bundleEntry,
      dependenciesCache,
      dependenciesModulesCache,
      shallowDependencies,
      inverseDependenciesCache,
    };
    clients.add(client);

    // If this is the first client connecting, start listening to file changes
    if (clients.size === 1) {
      packagerServer.setHMRFileChangeListener(handleFileChange);
    }

    client.ws.on('error', e => {
      console.error('[Hot Module Replacement] Unexpected error', e);
      disconnect(client);
    });

    client.ws.on('close', () => disconnect(client));
  });
}

function arrayEquals<T>(arrayA: Array<T>, arrayB: Array<T>): boolean {
  arrayA = arrayA || [];
  arrayB = arrayB || [];
  return (
    arrayA.length === arrayB.length &&
    arrayA.every((element, index) => {
      return element === arrayB[index];
    })
  );
}

module.exports = attachHMRServer;
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'
import OKCupid from 'okcupidjs'

const app = function() {

	const Header = React.createClass({
		render: () => {
			return
YOLO

		}
	})

	ReactDOM.render(,document.querySelector('.container'))
}

app()<commit_msg>removed okc<commit_after>import React from 'react'
import ReactDOM from 'react-dom'

const app = function() {

	const Header = React.createClass({
		render: () => {
			return
YOLO

		}
	})

	ReactDOM.render(,document.querySelector('.container'))
}

app()<|endoftext|>


<commit_before>/* eslint-env node */

var FeedParser = require('feedparser'); // to parse feeds
// var Feed = require('feed'); // to write feeds (but not necessary to require here because we should get a Feed object from app.js -- which seems wrong)
var async = require('async');
// var request = require('request'); // TODO let's abstract this so that we can switch it out (e.g., to fs.readFile )
var fs = require('fs');

exports.feedmerger = function(feedsJson, feedObject, mergedCallback) {
  'use strict';
  // TODO some error handling; checking the arguments etc.

  var getFeed = function(feed, callback) {
    var feedparser = new FeedParser();
    var req = fs.createReadStream(feed); // request(feed);
    req.on('error', function(error) {
        console.error(error);
    });
    req.on('readable', function() {
      var stream = this;
      stream.pipe(feedparser);
    });
    // req.on('response', function(res) {
    //   var stream = this;
    //   if (res.statusCode !== 200) {
    //     return this.emit('error', new Error('Bad status code'));
    //   }
    //   //    stream.pipe(process.stdout); //works
    //   stream.pipe(feedparser);
    // });
    feedparser.on('error', function(error) {
      console.error(feed + ' as a parsing error' + error);
    });
    feedparser.on('readable', function() {
      // This is where the action is!
      var stream = this;
      //    var meta = this.meta; // **NOTE** the "meta" is always available in the context of the feedparser instance
      var item;
      while ( (item = stream.read())) {
        var itemOptions = {
          date: item.date,
          title: item.title,
          url: item.link,
          guid: item.guid || item.permalink || '',
          description: '',
          author: stream.meta.title
        };
        // console.log(itemOptions);
        feedObject.item(itemOptions);
      }
    });
    feedparser.on('finish', function() {
      callback();
    });
  };

  //  console.log(feedsJson);
  async.each(feedsJson, getFeed, function(err) {
    if (err) {
      throw err;
    }
    feedObject.items.sort(function(a, b) { //sort by date for creating pages later
      return b.date - a.date;
    });
    //   console.log("feedmerger completed!");
    //     console.log(feedObject.render('atom-1.0'));
    return mergedCallback(feedObject);
  });
};

//    exports.feedmerger = new feedmerger();
<commit_msg>feedmerger: catch errors when feed file is not accessible, e.g., not existing<commit_after>/* eslint-env node */

var FeedParser = require('feedparser'); // to parse feeds
// var Feed = require('feed'); // to write feeds (but not necessary to require here because we should get a Feed object from app.js -- which seems wrong)
var async = require('async');
// var request = require('request'); // TODO let's abstract this so that we can switch it out (e.g., to fs.readFile )
var fs = require('fs');

exports.feedmerger = function(feedsJson, feedObject, mergedCallback) {
  'use strict';
  // TODO some error handling; checking the arguments etc.

  var getFeed = function(feed, callback) {
    var feedparser = new FeedParser();
    try {
      fs.accessSync(feed);
      var req = fs.createReadStream(feed); // request(feed);
    }
    catch (e){
      console.log('File for ' + feed + ' was not accessible');
      callback();
      return;
    }
    req.on('error', function(error) {
        console.error(error);
    });
    req.on('readable', function() {
      var stream = this;
      stream.pipe(feedparser);
    });
    // req.on('response', function(res) {
    //   var stream = this;
    //   if (res.statusCode !== 200) {
    //     return this.emit('error', new Error('Bad status code'));
    //   }
    //   //    stream.pipe(process.stdout); //works
    //   stream.pipe(feedparser);
    // });
    feedparser.on('error', function(error) {
      console.error(feed + ' as a parsing error' + error);
    });
    feedparser.on('readable', function() {
      // This is where the action is!
      var stream = this;
      //    var meta = this.meta; // **NOTE** the "meta" is always available in the context of the feedparser instance
      var item;
      while ( (item = stream.read())) {
        var itemOptions = {
          date: item.date,
          title: item.title,
          url: item.link,
          guid: item.guid || item.permalink || '',
          description: '',
          author: stream.meta.title
        };
        // console.log(itemOptions);
        feedObject.item(itemOptions);
      }
    });
    feedparser.on('finish', function() {
      callback();
    });
  };

  //  console.log(feedsJson);
  async.each(feedsJson, getFeed, function(err) {
    if (err) {
      console.log(err);
    }
    feedObject.items.sort(function(a, b) { //sort by date for creating pages later
      return b.date - a.date;
    });
    //   console.log("feedmerger completed!");
    //     console.log(feedObject.render('atom-1.0'));
    return mergedCallback(feedObject);
  });
};

//    exports.feedmerger = new feedmerger();
<|endoftext|>


<commit_before>module.directive('volumeEditMaterialsForm', [
	'pageService', function (page) {
		var link = function ($scope) {
			var form = $scope.volumeEditMaterialsForm;

			form.data = {};
			form.volume = undefined;
			var backup = [];

			form.saveFn = undefined;
			form.successFn = undefined;
			form.errorFn = undefined;
			form.resetFn = undefined;
			form.cancelFn = undefined;

			//

			form.init = function (data, volume) {
				form.data = data;
				form.volume = form.volume || volume;
				backup = data.assets.slice(0);
			};

			//

			form.save = function () {
				if (angular.isFunction(form.saveFn)) {
					form.saveFn(form);
				}

				var ids = [];
				var promises = [];

				angular.forEach(form.data.assets, function (asset) {
					asset.saving = true;

					if (asset.file) {
						var fd = new FormData();
						fd.append('file', asset.file[0]);
						fd.append('name', asset.name || '');
						fd.append('classification', asset.classification || 0);
						fd.append('container', form.slot.container.id);
						var msg = form.messages.add({
							type: 'yellow',
							body: page.constants.message('volume.edit.materials.create', asset.name || asset.file[0].name),
						});

						promises.push(page.$http
							.post('/api/asset?volume=' + form.volume.id, fd, {
								transformRequest: angular.identity,
								headers: {
									'Content-Type': undefined
								},
							}).success(function () {
								form.messages.update(msg, {
									type: 'green',
									body: page.constants.message('volume.edit.materials.create.success', asset.name || asset.file[0].name),
								});

								asset.saving = false;
							}).error(function (res, status) {
								form.messages.addError({
									type: 'red',
									body: page.constants.message('volume.edit.materials.create.error', asset.name || asset.file[0].name),
									errors: res,
									status: status
								});
								form.messages.remove(msg);

								asset.saving = false;
							}));
					} else if (asset.asset) {
						ids.push(asset.asset.id);

						promises.push(page.$http
							.post('/api/asset/' + asset.asset.id, {
								name: asset.name || '',
								classification: asset.classification,
							}).success(function () {
								form.messages.add({
									type: 'green',
									countdown: 3000,
									body: page.constants.message('volume.edit.materials.update.success', asset.name || asset.file[0].name),
								});

								asset.saving = false;
							}).error(function (res, status) {
								form.messages.addError({
									type: 'red',
									body: page.constants.message('volume.edit.materials.update.error', asset.name || asset.file[0].name),
									errors: res,
									status: status
								});

								asset.saving = false;
							}));
					}
				});

				angular.forEach(backup, function (asset) {
					if (ids.indexOf(asset.asset.id) > -1) {
						return;
					}

					promises.push(page.$http
						.$delete('/api/asset/' + asset.asset.id)
						.success(function () {
							form.messages.add({
								type: 'green',
								countdown: 3000,
								body: page.constants.message('volume.edit.materials.remove.success', asset.name || asset.file[0].name),
							});

							asset.saving = false;
						}).error(function (data, status) {
							form.messages.addError({
								type: 'red',
								body: page.constants.message('volume.edit.materials.remove.error', asset.name || asset.file[0].name),
								errors: data,
								status: status
							});

							asset.saving = false;
						}));
				});

				page.$q.all(promises).finally(function () {
					page.models.Slot.$cache.removeAll();
					page.models.Slot.get({
						id: form.volume.top.id,
						segment: ',',
						assets: ''
					}, function (res) {
						var clean = false;

						angular.forEach(res.assets, function (asset) {
							if (!clean) {
								// XXX Hack #1520, don't clean out list for the first failed upload.
								// Really we should not do so for any failed upload.
								form.repeater.repeats.splice(0, form.repeater.repeats.length);
								clean = true;
							}
							asset.name = asset.asset.name;
							asset.classification = ''+asset.asset.classification;

							form.repeater.repeats.push(asset);
						});

						angular.extend(form.slot, res);

						form.$setPristine();
					}, function (res) {
						form.messages.addError({
							type: 'red',
							body: page.constants.message('volume.edit.materials.refresh.error'),
							report: res
						});
					});
				});
			};

			//

			var changeFn = function () {
				form.$setDirty();
			};

			form.retrieveRepeater = function (repeater) {
				form.repeater = repeater;
				form.repeater.autoFile = form.autoFile;
				form.repeater.repeats = form.data.assets;
				form.repeater.addFn = changeFn;
				form.repeater.removeFn = changeFn;
			};

			//

			page.events.talk('volumeEditMaterialsForm-init', form, $scope);
		};

		//

		return {
			restrict: 'E',
			templateUrl: 'volumeEditMaterialsForm.html',
			scope: false,
			replace: true,
			link: link
		};
	}
]);
<commit_msg>js function to list accepted file formats. not currently used, but thought it worth saving<commit_after>module.directive('volumeEditMaterialsForm', [
	'pageService', function (page) {

		var post = function ($scope) {

			var form = $scope.volumeEditMaterialsForm;
			form.data = {};
			form.volume = undefined;
			var backup = [];

			form.saveFn = undefined;
			form.successFn = undefined;
			form.errorFn = undefined;
			form.resetFn = undefined;
			form.cancelFn = undefined;

			//

			form.init = function (data, volume) {
				form.data = data;
				form.volume = form.volume || volume;
				backup = data.assets.slice(0);
			};

			//

			form.save = function () {
				if (angular.isFunction(form.saveFn)) {
					form.saveFn(form);
				}

				var ids = [];
				var promises = [];

				angular.forEach(form.data.assets, function (asset) {
					asset.saving = true;

					if (asset.file) {
						var fd = new FormData();
						fd.append('file', asset.file[0]);
						fd.append('name', asset.name || '');
						fd.append('classification', asset.classification || 0);
						fd.append('container', form.slot.container.id);
						var msg = form.messages.add({
							type: 'yellow',
							body: page.constants.message('volume.edit.materials.create', asset.name || asset.file[0].name),
						});

						promises.push(page.$http
							.post('/api/asset?volume=' + form.volume.id, fd, {
								transformRequest: angular.identity,
								headers: {
									'Content-Type': undefined
								},
							}).success(function () {
								form.messages.update(msg, {
									type: 'green',
									body: page.constants.message('volume.edit.materials.create.success', asset.name || asset.file[0].name),
								});

								asset.saving = false;
							}).error(function (res, status) {
								form.messages.addError({
									type: 'red',
									body: page.constants.message('volume.edit.materials.create.error', asset.name || asset.file[0].name),
									errors: res,
									status: status
								});
								form.messages.remove(msg);

								asset.saving = false;
							}));
					} else if (asset.asset) {
						ids.push(asset.asset.id);

						promises.push(page.$http
							.post('/api/asset/' + asset.asset.id, {
								name: asset.name || '',
								classification: asset.classification,
							}).success(function () {
								form.messages.add({
									type: 'green',
									countdown: 3000,
									body: page.constants.message('volume.edit.materials.update.success', asset.name || asset.file[0].name),
								});

								asset.saving = false;
							}).error(function (res, status) {
								form.messages.addError({
									type: 'red',
									body: page.constants.message('volume.edit.materials.update.error', asset.name || asset.file[0].name),
									errors: res,
									status: status
								});

								asset.saving = false;
							}));
					}
				});

				angular.forEach(backup, function (asset) {
					if (ids.indexOf(asset.asset.id) > -1) {
						return;
					}

					promises.push(page.$http
						.$delete('/api/asset/' + asset.asset.id)
						.success(function () {
							form.messages.add({
								type: 'green',
								countdown: 3000,
								body: page.constants.message('volume.edit.materials.remove.success', asset.name || asset.file[0].name),
							});

							asset.saving = false;
						}).error(function (data, status) {
							form.messages.addError({
								type: 'red',
								body: page.constants.message('volume.edit.materials.remove.error', asset.name || asset.file[0].name),
								errors: data,
								status: status
							});

							asset.saving = false;
						}));
				});

				page.$q.all(promises).finally(function () {
					page.models.Slot.$cache.removeAll();
					page.models.Slot.get({
						id: form.volume.top.id,
						segment: ',',
						assets: ''
					}, function (res) {
						var clean = false;

						angular.forEach(res.assets, function (asset) {
							if (!clean) {
								// XXX Hack #1520, don't clean out list for the first failed upload.
								// Really we should not do so for any failed upload.
								form.repeater.repeats.splice(0, form.repeater.repeats.length);
								clean = true;
							}
							asset.name = asset.asset.name;
							asset.classification = ''+asset.asset.classification;

							form.repeater.repeats.push(asset);
						});

						angular.extend(form.slot, res);

						form.$setPristine();
					}, function (res) {
						form.messages.addError({
							type: 'red',
							body: page.constants.message('volume.edit.materials.refresh.error'),
							report: res
						});
					});
				});
			};

			//
			//

			var changeFn = function () {
				form.$setDirty();
			};

			form.retrieveRepeater = function (repeater) {
				form.repeater = repeater;
				form.repeater.autoFile = form.autoFile;
				form.repeater.repeats = form.data.assets;
				form.repeater.addFn = changeFn;
				form.repeater.removeFn = changeFn;
			};

			form.getFormatsAsUnorderedList = function(){
					var DELIMIT = "

";
					var materialsFormats = page.constants.data.format;
					var x="";
					for(var i in materialsFormats)
					{
						var cur = materialsFormats[i];
						console.log(i);
						x += cur.extension +  ": " + cur.name + DELIMIT;
					}
					x = x.substr(0,x.length - DELIMIT.length);
					console.log("arf");
					return "

"+x+"

";
			};

			//

			page.events.talk('volumeEditMaterialsForm-init', form, $scope);
		};

		//

		return {
			restrict: 'E',
			templateUrl: 'volumeEditMaterialsForm.html',
			scope: false,
			replace: true,
			link: {
				post: post
			}
		};
	}
]);
<|endoftext|>