<commit_before>/*
Copyright 2015 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");

var ComponentBroker = require('../../ComponentBroker');

var RoomTile = ComponentBroker.get("molecules/RoomTile");

module.exports = {
    componentWillMount: function() {
        var cli = MatrixClientPeg.get();
        cli.on("Room", this.onRoom);
        cli.on("Room.timeline", this.onRoomTimeline);
        cli.on("Room.name", this.onRoomName);

        this.setState({
            roomList: cli.getRooms(),
            activityMap: {}
        });
    },

    componentWillUnmount: function() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("Room", this.onRoom);
            MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
            MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
        }
    },

    componentWillReceiveProps: function(newProps) {
        this.state.activityMap[newProps.selectedRoom] = undefined;
        this.setState({
            activityMap: this.state.activityMap
        });
    },

    onRoom: function(room) {
        var cli = MatrixClientPeg.get();
        this.setState({
            roomList: cli.getRooms(),
        });
    },

    onRoomTimeline: function(ev, room, toStartOfTimeline) {
        if (toStartOfTimeline) return;
        if (room.roomId == this.props.selectedRoom) return;
        if (ev.getSender() == MatrixClientPeg.get().credentials.userId) return;

        var hl = 1;

        var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
        if (actions && actions.tweaks && actions.tweaks.highlight) {
            hl = 2;
        }

        // obviously this won't deep copy but we this shouldn't be necessary
        var amap = this.state.activityMap;
        amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
        this.setState({
            activityMap: amap
        });
    },

    onRoomName: function(room) {
        var cli = MatrixClientPeg.get();
        this.setState({
            roomList: cli.getRooms(),
        });
    },

    makeRoomTiles: function() {
        var that = this;
        return this.state.roomList.map(function(room) {
            var selected = room.roomId == that.props.selectedRoom;
            return (
                <RoomTile
                    room={room}
                    key={room.roomId}
                    selected={selected}
                    unread={that.state.activityMap[room.roomId] === 1}
                    highlight={that.state.activityMap[room.roomId] === 2}
                />
            );
        });
    },
};

<commit_msg>Only highlight room list for messages where we notify - this may want to be an option in the react client potentially.<commit_after>/*
Copyright 2015 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

var React = require("react");
var MatrixClientPeg = require("../../MatrixClientPeg");

var ComponentBroker = require('../../ComponentBroker');

var RoomTile = ComponentBroker.get("molecules/RoomTile");

module.exports = {
    componentWillMount: function() {
        var cli = MatrixClientPeg.get();
        cli.on("Room", this.onRoom);
        cli.on("Room.timeline", this.onRoomTimeline);
        cli.on("Room.name", this.onRoomName);

        this.setState({
            roomList: cli.getRooms(),
            activityMap: {}
        });
    },

    componentWillUnmount: function() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("Room", this.onRoom);
            MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
            MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
        }
    },

    componentWillReceiveProps: function(newProps) {
        this.state.activityMap[newProps.selectedRoom] = undefined;
        this.setState({
            activityMap: this.state.activityMap
        });
    },

    onRoom: function(room) {
        var cli = MatrixClientPeg.get();
        this.setState({
            roomList: cli.getRooms(),
        });
    },

    onRoomTimeline: function(ev, room, toStartOfTimeline) {
        if (toStartOfTimeline) return;
        if (room.roomId == this.props.selectedRoom) return;
        if (ev.getSender() == MatrixClientPeg.get().credentials.userId) return;

        var hl = 1;

        var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
        if (actions && actions.tweaks && actions.tweaks.highlight) {
            hl = 2;
        }
        if (!actions.notify) {
            return;
        }

        // obviously this won't deep copy but we this shouldn't be necessary
        var amap = this.state.activityMap;
        amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
        this.setState({
            activityMap: amap
        });
    },

    onRoomName: function(room) {
        var cli = MatrixClientPeg.get();
        this.setState({
            roomList: cli.getRooms(),
        });
    },

    makeRoomTiles: function() {
        var that = this;
        return this.state.roomList.map(function(room) {
            var selected = room.roomId == that.props.selectedRoom;
            return (
                <RoomTile
                    room={room}
                    key={room.roomId}
                    selected={selected}
                    unread={that.state.activityMap[room.roomId] === 1}
                    highlight={that.state.activityMap[room.roomId] === 2}
                />
            );
        });
    },
};

<|endoftext|>


<commit_before>(function() {
	// The HintBox validation listener will successfully bind unto any doodad which has
	// validation enabled *and* exposes the following interface:
	// * hasInputFocus
	// * validationTarget
	// * focus event
	// * blur event

	// To optimize DOM usage, the HintBox validation listener shares an instance of the
	// hint box and hint list doodads
	var tipsyBox,
		isBoxVisible = false,
		template = Mustache.compile('
{{#messages}}
{{text}}
{{/messages}}
');

	$(function() {
		$('head').append(
			'');
		$(document).tipsy({
			trigger:'manual',
			html: true,
			className: 'doodads-tipsy',
			gravity: 'w',
			opacity: 1
		});
		tipsyBox = $(document).tipsy(true);
	});

	var HintBoxValidationListener = function (doodad) {
		if (arguments.length === 0) { return; }

		this.onValidationApplied$proxy = doodads.proxy(this.onValidationApplied, this);
		this.onDoodadFocus$proxy = doodads.proxy(this.onDoodadFocus, this);
		this.onDoodadBlur$proxy = doodads.proxy(this.onDoodadBlur, this);
		this.onCapturedMouseDown$proxy = doodads.proxy(this.onCapturedMouseDown, this);

		doodad.bind('validationApplied', this.onValidationApplied$proxy);
		doodad.bind('focus', this.onDoodadFocus$proxy);
		doodad.bind('blur', this.onDoodadBlur$proxy);

		this._doodad = null;
		this._validationState = {
			messages: []
			, isValid: true
		};
	}
	HintBoxValidationListener.canListen = function (doodad) {
		if (doodad._options.validationListeners.indexOf('hintbox') !== -1 && // the doodad *wants* the hint-list
			$.isFunction(doodad.validationTarget)) // and the doodad implements the IHintBoxListenerSource interface
		{
			return true;
		} else {
			return false;
		}
	}
	HintBoxValidationListener.listen = function (doodad) {
		return new HintBoxValidationListener(doodad);
	}

	HintBoxValidationListener.prototype = {
		/* BEGIN Event Handlers */

		onValidationApplied: function HintBoxValidationListener$onValidationApplied(e, args) {
			this._doodad = e.target;
			this._validationState = args;

			this.setHintBoxVisibility();
		}

		, onDoodadFocus: function HintBoxValidationListener$onDoodadFocus(e) {
			if (e.target._options.validates) {
				if (!e.target.ranValidation()) {
					e.target.validate();
				} else {
					this._doodad = e.target;
					this.setHintBoxVisibility();
				}
			}
		}
		, onDoodadBlur: function HintBoxValidationListener$onDoodadBlur(e) {
			if (e.target._options.validates) {
				this.hideHintBox();
			}
		}
		, onCapturedMouseDown: function HintBoxValidationListener$onCapturedMouseDown(e) {
			var validationTarget = this._doodad.validationTarget();
			if (validationTarget && e.originalTarget === validationTarget[0]) return;

			this.hideHintBox();
		}

		/* END Event Handlers */

		, setHintBoxVisibility: function HintBoxValidationListener$setHintBoxVisibility(doodad) {
			if (this._doodad && this._doodad.hasInputFocus() && (this._validationState.messages || []).length > 0) {
				this.showHintBox();
			} else {
				this.hideHintBox();
			}
		}

		, showHintBox: function HintBoxValidationListener$showHintBox() {
			var target = this._doodad ? this._doodad.validationTarget() : null,
				self = this;

			tipsyBox.options.title = function() {
				return template(self._validationState);
			};

			if (target) {
				tipsyBox.$element = target;
			}

			// set the color
			if (this._validationState.isValid) {
				tipsyBox.options.className = 'doodads-tipsy doodads-tipsy-info';
			} else {
				tipsyBox.options.className = 'doodads-tipsy doodads-tipsy-error';
			}

			tipsyBox.show();

			isBoxVisible = true;

			window.captureEvent('mousedown', this._doodad.element(), this.onCapturedMouseDown$proxy);
		}
		, hideHintBox: function HintBoxValidationListener$hideHintBox() {
			if (!isBoxVisible) {
				return;
			}

			tipsyBox.hide();

			isBoxVisible = false;

			window.releaseEvent('mousedown');
		}

		, dispose: function HintBoxValidationListener$dispose(doodad) {
			doodad.unbind('validationApplied', this.onValidationApplied$proxy);
			doodad.unbind('focus', this.onDoodadFocus$proxy);
			doodad.unbind('blur', this.onDoodadBlur$proxy);
		}
	};
	HintBoxValidationListener.prototype.constructor = HintBoxValidationListener;

	doodads.validation.listeners.HintBoxValidationListener = HintBoxValidationListener;
	doodads.validation.registerListener(HintBoxValidationListener);
})();<commit_msg>Whitespace fix<commit_after>(function() {
	// The HintBox validation listener will successfully bind unto any doodad which has
	// validation enabled *and* exposes the following interface:
	// * hasInputFocus
	// * validationTarget
	// * focus event
	// * blur event

	// To optimize DOM usage, the HintBox validation listener shares an instance of the
	// hint box and hint list doodads
	var tipsyBox,
		isBoxVisible = false,
		template = Mustache.compile('
{{#messages}}
{{text}}
{{/messages}}
');

	$(function() {
		// add the doodads specific tipsy stylesheets
		$('head').append(
			'');

		$(document).tipsy({
			trigger: 'manual',
			html: true,
			className: 'doodads-tipsy',
			gravity: 'w',
			opacity: 1
		});
		tipsyBox = $(document).tipsy(true);
	});

	var HintBoxValidationListener = function (doodad) {
		if (arguments.length === 0) { return; }

		this.onValidationApplied$proxy = doodads.proxy(this.onValidationApplied, this);
		this.onDoodadFocus$proxy = doodads.proxy(this.onDoodadFocus, this);
		this.onDoodadBlur$proxy = doodads.proxy(this.onDoodadBlur, this);
		this.onCapturedMouseDown$proxy = doodads.proxy(this.onCapturedMouseDown, this);

		doodad.bind('validationApplied', this.onValidationApplied$proxy);
		doodad.bind('focus', this.onDoodadFocus$proxy);
		doodad.bind('blur', this.onDoodadBlur$proxy);

		this._doodad = null;
		this._validationState = {
			messages: []
			, isValid: true
		};
	}
	HintBoxValidationListener.canListen = function (doodad) {
		if (doodad._options.validationListeners.indexOf('hintbox') !== -1 && // the doodad *wants* the hint-list
			$.isFunction(doodad.validationTarget)) // and the doodad implements the IHintBoxListenerSource interface
		{
			return true;
		} else {
			return false;
		}
	}
	HintBoxValidationListener.listen = function (doodad) {
		return new HintBoxValidationListener(doodad);
	}

	HintBoxValidationListener.prototype = {
		/* BEGIN Event Handlers */

		onValidationApplied: function HintBoxValidationListener$onValidationApplied(e, args) {
			this._doodad = e.target;
			this._validationState = args;

			this.setHintBoxVisibility();
		}

		, onDoodadFocus: function HintBoxValidationListener$onDoodadFocus(e) {
			if (e.target._options.validates) {
				if (!e.target.ranValidation()) {
					e.target.validate();
				} else {
					this._doodad = e.target;
					this.setHintBoxVisibility();
				}
			}
		}
		, onDoodadBlur: function HintBoxValidationListener$onDoodadBlur(e) {
			if (e.target._options.validates) {
				this.hideHintBox();
			}
		}
		, onCapturedMouseDown: function HintBoxValidationListener$onCapturedMouseDown(e) {
			var validationTarget = this._doodad.validationTarget();
			if (validationTarget && e.originalTarget === validationTarget[0]) return;

			this.hideHintBox();
		}

		/* END Event Handlers */

		, setHintBoxVisibility: function HintBoxValidationListener$setHintBoxVisibility(doodad) {
			if (this._doodad && this._doodad.hasInputFocus() && (this._validationState.messages || []).length > 0) {
				this.showHintBox();
			} else {
				this.hideHintBox();
			}
		}

		, showHintBox: function HintBoxValidationListener$showHintBox() {
			var target = this._doodad ? this._doodad.validationTarget() : null,
				self = this;

			tipsyBox.options.title = function() {
				return template(self._validationState);
			};

			if (target) {
				tipsyBox.$element = target;
			}

			// set the color
			if (this._validationState.isValid) {
				tipsyBox.options.className = 'doodads-tipsy doodads-tipsy-info';
			} else {
				tipsyBox.options.className = 'doodads-tipsy doodads-tipsy-error';
			}

			tipsyBox.show();

			isBoxVisible = true;

			window.captureEvent('mousedown', this._doodad.element(), this.onCapturedMouseDown$proxy);
		}
		, hideHintBox: function HintBoxValidationListener$hideHintBox() {
			if (!isBoxVisible) {
				return;
			}

			tipsyBox.hide();

			isBoxVisible = false;

			window.releaseEvent('mousedown');
		}

		, dispose: function HintBoxValidationListener$dispose(doodad) {
			doodad.unbind('validationApplied', this.onValidationApplied$proxy);
			doodad.unbind('focus', this.onDoodadFocus$proxy);
			doodad.unbind('blur', this.onDoodadBlur$proxy);
		}
	};
	HintBoxValidationListener.prototype.constructor = HintBoxValidationListener;

	doodads.validation.listeners.HintBoxValidationListener = HintBoxValidationListener;
	doodads.validation.registerListener(HintBoxValidationListener);
})();<|endoftext|>


<commit_before>var gamejs = require('gamejs');
var sprite = require('gamejs/sprite');
qModule('gamejs/sprite');

test('SpriteConstructor', function() {
   var sp = new sprite.Sprite();
   ok(sp instanceof sprite.Sprite);
   strictEqual(sp.image, null);
   strictEqual(sp.rect, null);
});

test('SpriteMethods', function() {
   var sp = new sprite.Sprite();
   sp.kill();
   ok(sp.isDead());
});

test('SpriteGroup', function() {
   var sprites = [];
   var i = 5;
   while (i-->0) {
      sprites.push(new sprite.Sprite());
   }
   var group = new sprite.Group(sprites);
   ok(group instanceof sprite.Group);
   // group has sprites?
   ok(group.has(sprites));
   sprites.forEach(function(sp) {
      ok(group.has(sp));
   });
   // group cascades update & draw?
   sprites = sprites.map(function(sp) {
      sp._testUpdateCalled = false;
      sp._testDurationPassed = null;

      sp._testDrawCalled = false;
      sp._testSurfacePassed = null;
      sp.draw = function(surface) {
         sp._testDrawCalled = true;
         sp._testSurfacePassed = surface;
      }
      sp.update = function(msDuration) {
         sp._testUpdateCalled = true;
         sp._testDurationPassed = msDuration;

      };
      return sp;
   }, this);
   var msDuration = 12345;
   group.update(msDuration);
   sprites.forEach(function(sp) {
      ok(sp._testUpdateCalled);
      equal(sp._testDurationPassed, msDuration);
   });
   var surface = '{{A Surface Object}}';
   group.draw(surface);
   sprites.forEach(function(sp) {
      ok(sp._testDrawCalled);
      equal(sp._testSurfacePassed, surface);
   });

   // remove one by one
   sprites.forEach(function(sp) {
      group.remove(sp);
      ok(!group.has(sp));
   });

   // is other sprite removed if group.remove is called with invalid arg?
   group.add(sprites[0]);
   group.remove(sprites[1]);
   ok(group.has(sprites[0]));
});

test('SpriteCollisions', function() {
   var a = new sprite.Sprite();
   a.rect = new gamejs.Rect([0, 0], [10, 10]);
   var b = new sprite.Sprite();
   b.rect = new gamejs.Rect([10, 10], [10, 10]);

   ok(gamejs.sprite.collideCircle(a, b));

   a.radius = 1;
   b.radius = 1;
   ok(!gamejs.sprite.collideCircle(a, b));

   a.radius = 6;
   b.radius = 6;
   ok(gamejs.sprite.collideCircle(a, b));

   a.rect = new gamejs.Rect([0,0], [5,5]);
   b.rect = new gamejs.Rect([-1,-1], [5,5]);
   ok(gamejs.sprite.collideCircle(a,b));

   a.radius = 6;
   b.radius = 6;
   ok(gamejs.sprite.collideCircle(a,b));

   a.radius = 0.3;
   b.radius = 0.3;
   ok(!gamejs.sprite.collideCircle(a,b));
});
<commit_msg>fix unit test due to 9d1c86248bcd1cd<commit_after>var gamejs = require('gamejs');
var sprite = require('gamejs/sprite');
qModule('gamejs/sprite');

test('SpriteConstructor', function() {
   var sp = new sprite.Sprite();
   ok(sp instanceof sprite.Sprite);
   strictEqual(sp.image, null);
   strictEqual(sp.rect, null);
});

test('SpriteMethods', function() {
   var sp = new sprite.Sprite();
   sp.kill();
   ok(sp.isDead());
});

test('SpriteGroup', function() {
   var sprites = [];
   var i = 5;
   while (i-->0) {
      sprites.push(new sprite.Sprite());
   }
   var group = new sprite.Group(sprites);
   ok(group instanceof sprite.Group);
   // group has sprites?
   ok(group.has(sprites));
   sprites.forEach(function(sp) {
      ok(group.has(sp));
   });
   // group cascades update & draw?
   sprites = sprites.map(function(sp) {
      sp._testUpdateCalled = false;
      sp._testDurationPassed = null;

      sp._testDrawCalled = false;
      sp._testSurfacePassed = null;
      sp.draw = function(surface) {
         sp._testDrawCalled = true;
         sp._testSurfacePassed = surface;
      }
      sp.update = function(msDuration) {
         sp._testUpdateCalled = true;
         sp._testDurationPassed = msDuration;

      };
      return sp;
   }, this);
   var msDuration = 12345;
   group.update(msDuration);
   sprites.forEach(function(sp) {
      ok(sp._testUpdateCalled);
      equal(sp._testDurationPassed, msDuration);
   });
   var surface = '{{A Surface Object}}';
   group.draw(surface);
   sprites.forEach(function(sp) {
      ok(sp._testDrawCalled);
      equal(sp._testSurfacePassed, surface);
   });

   // remove one by one
   sprites.forEach(function(sp) {
      group.remove(sp);
      ok(!group.has(sp));
   });

   // is other sprite removed if group.remove is called with invalid arg?
   group.add(sprites[0]);
   group.remove(sprites[1]);
   ok(group.has(sprites[0]));
});

test('SpriteCollisions', function() {
   var a = new sprite.Sprite();
   a.rect = new gamejs.Rect([0, 0], [10, 10]);
   var b = new sprite.Sprite();
   b.rect = new gamejs.Rect([10, 10], [10, 10]);

   ok(gamejs.sprite.collideCircle(a, b));

   a.radius = 1;
   b.radius = 1;
   ok(!gamejs.sprite.collideCircle(a, b));

   a.radius = 7;
   b.radius = 8;
   ok(gamejs.sprite.collideCircle(a, b));

   a.rect = new gamejs.Rect([0,0], [5,5]);
   b.rect = new gamejs.Rect([-1,-1], [5,5]);
   ok(gamejs.sprite.collideCircle(a,b));

   a.radius = 6;
   b.radius = 6;
   ok(gamejs.sprite.collideCircle(a,b));

   a.radius = 0.3;
   b.radius = 0.3;
   ok(!gamejs.sprite.collideCircle(a,b));
});
<|endoftext|>


<commit_before>const readline = require('readline')
const cli = require('commander')
const moment = require('moment')
const notifier = require('node-notifier')
const chalk = require('chalk')
const config = require('../apiConfig')
const apiCall = require('./apiCall')

const startTime = moment()
const requestBody = {}
const assigned = []
// The wait between calling submissionRequests().
const tickrate = 30000 // 30 seconds
const infoInterval = 10 // 10 * 30 seconds === 5 minutes

// Calculates how many seconds remain for a given interval.
const remainingSeconds = interval => (interval - (tick % interval)) * (tickrate / 1000)

let tick = 0
let assignedCount = 0
let assignedTotal = 0
let requestId = 0
let unreadFeedbacks = []
let positions = []

// ********************
// HANDLE STARTUP TASKS
// ********************

cli
  .arguments('<ids...>')
  .action((ids) => {
    // Get ids for all certified projects
    const certIds = Object.keys(config.certs)

    // Validate ids if the user passed them in as arguments.
    if (ids[0] !== 'all') {
      const invalidIds = ids.filter(id => certIds.indexOf(id) === -1)
      if (invalidIds.length) {
        throw new Error(
          `Illegal Action: Not certified for project(s) ${[...invalidIds].join(', ')}`)
      }
    } else {
      ids = certIds
    }

    // Initialize the requestBody.
    requestBody.projects = ids.map(id => {
      return {
        project_id: id,
        language: config.language
      }
    })
    // Start the request loop and set the prompt.
    submissionRequests()
    setPrompt()
  })
  .parse(process.argv)

// ***************
// CALLING THE API
// ***************

function submissionRequests () {
  // Call API to check how many submissions are currently assigned.
  apiCall('count')
  .then(res => {
    // If assigned_count has gone up we check the new submission assignment.
    if (assignedCount < res.body.assigned_count) {
      checkAssigned()
    }
    assignedCount = res.body.assigned_count
    // If then the assignedCount is less than the maximum number of assignments
    // allowed, we go through checking the submission_request.
    if (assignedCount < 2) {
      apiCall('get')
      .then(res => {
        const submissionRequest = res.body[0]
        // If there is no current submission_request we create a new one.
        if (!submissionRequest) {
          createSubmissionRequest()
        } else {
          requestId = submissionRequest.id
          // Refresh the submission_request if it's is about to expire.
          checkRefresh(submissionRequest.closed_at)
          // Check the queue positions and for new feedbacks.
          if (tick % infoInterval === 0) {
            checkPositions()
            checkFeedbacks()
          }
        }
      })
    }
  })

  setTimeout(() => {
    tick++
    setPrompt()
    submissionRequests()
  }, tickrate)
}

function createSubmissionRequest () {
  apiCall('create', '', requestBody)
  .then(res => {
    requestId = res.body.id
    checkPositions()
  })
  // Reset tick to reset the timers.
  tick = 0
}

function checkRefresh (closedAt) {
  const closingIn = Date.parse(closedAt) - Date.now()
  // If it expires in less than 5 minutes we refresh.
  if (closingIn < 300000) {
    apiCall('refresh', requestId)
  }
}

function checkPositions () {
  apiCall('position', requestId)
  .then(res => {
    positions = res.body.error ? [] : res.body
    setPrompt()
  })
}

function checkFeedbacks () {
  apiCall('stats')
  .then(res => {
    const diff = res.body.unread_count - unreadFeedbacks.length
    if (diff > 0) {
      apiCall('feedbacks')
      .then(res => {
        unreadFeedbacks = res.body.filter(fb => fb.read_at === null)
        // Notify the user of the new feedbacks.
        unreadFeedbacks.slice(-diff).forEach(fb => {
          feedbackNotification(fb.rating, fb.project.name, fb.submission_id)
        })
      })
    } else if (diff < 0) {
      // Note: If you check your feedbacks in the Reviews dashboard the unread
      // count always goes to 0. Therefore we can assume that a negative
      // difference between the current unread_count and the number of elements
      // in unreadFeedbacks, will mean that the new unread_count is 0.
      unreadFeedbacks = []
    }
  })
}

function checkAssigned () {
  apiCall('assigned')
  .then(res => {
    if (res.body.length) {
      const newAssigned = res.body.filter(s => assigned.indexOf(s.id) === -1)
      newAssigned.forEach(s => {
        // Only add it to the total number of assigned if it's been assigned
        // after the command was initiated.
        if (Date.parse(s.assigned_at) > Date.parse(startTime)) {
          assignedTotal++
        }
        assigned.push(s)
        assignmentNotification(s.project, s.id)
      })
    }
  })
}

// *********************
// DESKTOP NOTIFICATIONS
// *********************

function assignmentNotification ({name, id}, submissionId) {
  notifier.notify({
    title: 'New Review Assigned!',
    message: `${moment().format('HH:mm')} - ${name} (${id})`,
    open: `https://review.udacity.com/#!/submissions/${submissionId}`,
    icon: '../assets/clipboard.png',
    sound: 'Ping'
  })
}

function feedbackNotification (rating, name, id) {
  notifier.notify({
    title: `New ${rating}-star Feedback!`,
    message: `Project: ${name}`,
    open: `https://review.udacity.com/#!/reviews/${id}`,
    icon: '../assets/clipboard.png',
    sound: 'Pop'
  })
}

// ******************
// SETTING THE PROMPT
// ******************

function setPrompt () {
  // Clearing the screen.
  readline.cursorTo(process.stdout, 0, 0)
  readline.clearScreenDown(process.stdout)

  // Warnings.
  const tokenExpiryWarning = () => config.tokenAge - moment().dayOfYear() < 5
  const tokenExpires = moment().dayOfYear(config.tokenAge).fromNow()
  console.log(chalk[tokenExpiryWarning() ? 'red' : 'green'](`Token expires ${tokenExpires}`))

  // General info.
  console.log(chalk.green(`Uptime: ${chalk.white(startTime.fromNow(true))}\n`))
  // Positions in request queues.
  console.log(chalk.blue('You are queued up for:\n'))
  positions.forEach(project => {
    console.log(chalk.blue(`    Project ${project.project_id} - ${config.certs[project.project_id].name}`))
    console.log(chalk.yellow(`    Position in queue: ${chalk.white(project.position)}\n`))
  })
  if (!positions.length) {
    console.log(chalk.yellow(`    You have ${chalk.white(assignedCount)} (max) submissions assigned.\n`))
  }

  // Info on when the next check will occur for queue position and feedbacks.
  if (tick % infoInterval === 0) {
    console.log(chalk.blue('Checked the queue a few seconds ago...'))
    console.log(chalk.blue('Checked for new feedbacks a few seconds ago...\n'))
  } else {
    let remainingSeconds = (infoInterval - (tick % infoInterval)) * (tickrate / 1000)
    let infoIsCheckedAt = moment().add(remainingSeconds, 'seconds')
    let humanReadableMessage = moment().to(infoIsCheckedAt)
    console.log(chalk.blue(`Updating queue information ${humanReadableMessage}`))
    console.log(chalk.blue(`Checking feedbacks ${humanReadableMessage}\n`))
  }

  // Assigned info.
  console.log(chalk.green(`Currently assigned: ${chalk.white(assignedCount)}`))
  console.log(chalk.green(`Total assigned: ${chalk.white(assignedTotal)} since ${startTime.format('dddd, MMMM Do YYYY, HH:mm')}\n`))
  // How to exit.
  console.log(chalk.green.dim(`Press ${chalk.white('ctrl+c')} to exit the queue cleanly by deleting the submission_request.`))
  console.log(chalk.green.dim(`Press ${chalk.white('ESC')} to suspend the script without deleting the submission_request.\n`))
}

// ******************
// EXITING THE SCRIPT
// ******************

// It's necessary to add a readline interface to catch <ctrl>-C on Windows.
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

// Exit cleanly on <ctrl>-C by deleting the submission_request.
rl.on('SIGINT', () => {
  apiCall('delete', requestId)
  .then(res => {
    console.log(chalk.green('Successfully deleted request and exited..'))
    process.exit(0)
  })
  .catch(err => {
    console.log(chalk.red('Was unable to exit cleanly.'))
    console.log(err)
    process.exit(1)
  })
})

// Suspend on ESC without deleting the submission_request.
process.stdin.on('data', key => {
  if (key == '\u001b') {
    console.log(chalk.green('Exited without deleting the submission_request...'))
    process.exit(0)
  }
})
<commit_msg>Allow requests for multiple languages<commit_after>const readline = require('readline')
const cli = require('commander')
const moment = require('moment')
const notifier = require('node-notifier')
const chalk = require('chalk')
const config = require('../apiConfig')
const apiCall = require('./apiCall')

const startTime = moment()
const requestBody = {}
const assigned = []
// The wait between calling submissionRequests().
const tickrate = 30000 // 30 seconds
const infoInterval = 10 // 10 * 30 seconds === 5 minutes

// Calculates how many seconds remain for a given interval.
const remainingSeconds = interval => (interval - (tick % interval)) * (tickrate / 1000)

let tick = 0
let assignedCount = 0
let assignedTotal = 0
let requestId = 0
let unreadFeedbacks = []
let positions = []

// ********************
// HANDLE STARTUP TASKS
// ********************

cli
  .arguments('<ids...>')
  .action((ids) => {
    // Get ids for all certified projects
    const certIds = Object.keys(config.certs)

    // Validate ids if the user passed them in as arguments.
    if (ids[0] !== 'all') {
      const invalidIds = ids.filter(id => certIds.indexOf(id) === -1)
      if (invalidIds.length) {
        throw new Error(
          `Illegal Action: Not certified for project(s) ${[...invalidIds].join(', ')}`)
      }
    } else {
      ids = certIds
    }
    // Create a list of project/language pairs
    const projectLanguagePairs = config.languages.map((language) => {
      return ids.map(id => {
        console.log(id, language)
        return {
          project_id: id,
          language: language
        }
      })
    })
    // Initialize the requestBody.
    requestBody.projects = [].concat(...projectLanguagePairs)
    // Start the request loop and set the prompt.
    submissionRequests()
    setPrompt()
  })
  .parse(process.argv)

// ***************
// CALLING THE API
// ***************

function submissionRequests () {
  // Call API to check how many submissions are currently assigned.
  apiCall('count')
  .then(res => {
    // If assigned_count has gone up we check the new submission assignment.
    if (assignedCount < res.body.assigned_count) {
      checkAssigned()
    }
    assignedCount = res.body.assigned_count
    // If then the assignedCount is less than the maximum number of assignments
    // allowed, we go through checking the submission_request.
    if (assignedCount < 2) {
      apiCall('get')
      .then(res => {
        const submissionRequest = res.body[0]
        // If there is no current submission_request we create a new one.
        if (!submissionRequest) {
          createSubmissionRequest()
        } else {
          requestId = submissionRequest.id
          // Refresh the submission_request if it's is about to expire.
          checkRefresh(submissionRequest.closed_at)
          // Check the queue positions and for new feedbacks.
          if (tick % infoInterval === 0) {
            checkPositions()
            checkFeedbacks()
          }
        }
      })
    }
  })

  setTimeout(() => {
    tick++
    setPrompt()
    submissionRequests()
  }, tickrate)
}

function createSubmissionRequest () {
  apiCall('create', '', requestBody)
  .then(res => {
    requestId = res.body.id
    checkPositions()
  })
  // Reset tick to reset the timers.
  tick = 0
}

function checkRefresh (closedAt) {
  const closingIn = Date.parse(closedAt) - Date.now()
  // If it expires in less than 5 minutes we refresh.
  if (closingIn < 300000) {
    apiCall('refresh', requestId)
  }
}

function checkPositions () {
  apiCall('position', requestId)
  .then(res => {
    positions = res.body.error ? [] : res.body
    setPrompt()
  })
}

function checkFeedbacks () {
  apiCall('stats')
  .then(res => {
    const diff = res.body.unread_count - unreadFeedbacks.length
    if (diff > 0) {
      apiCall('feedbacks')
      .then(res => {
        unreadFeedbacks = res.body.filter(fb => fb.read_at === null)
        // Notify the user of the new feedbacks.
        unreadFeedbacks.slice(-diff).forEach(fb => {
          feedbackNotification(fb.rating, fb.project.name, fb.submission_id)
        })
      })
    } else if (diff < 0) {
      // Note: If you check your feedbacks in the Reviews dashboard the unread
      // count always goes to 0. Therefore we can assume that a negative
      // difference between the current unread_count and the number of elements
      // in unreadFeedbacks, will mean that the new unread_count is 0.
      unreadFeedbacks = []
    }
  })
}

function checkAssigned () {
  apiCall('assigned')
  .then(res => {
    if (res.body.length) {
      const newAssigned = res.body.filter(s => assigned.indexOf(s.id) === -1)
      newAssigned.forEach(s => {
        // Only add it to the total number of assigned if it's been assigned
        // after the command was initiated.
        if (Date.parse(s.assigned_at) > Date.parse(startTime)) {
          assignedTotal++
        }
        assigned.push(s)
        assignmentNotification(s.project, s.id)
      })
    }
  })
}

// *********************
// DESKTOP NOTIFICATIONS
// *********************

function assignmentNotification ({name, id}, submissionId) {
  notifier.notify({
    title: 'New Review Assigned!',
    message: `${moment().format('HH:mm')} - ${name} (${id})`,
    open: `https://review.udacity.com/#!/submissions/${submissionId}`,
    icon: '../assets/clipboard.png',
    sound: 'Ping'
  })
}

function feedbackNotification (rating, name, id) {
  notifier.notify({
    title: `New ${rating}-star Feedback!`,
    message: `Project: ${name}`,
    open: `https://review.udacity.com/#!/reviews/${id}`,
    icon: '../assets/clipboard.png',
    sound: 'Pop'
  })
}

// ******************
// SETTING THE PROMPT
// ******************

function setPrompt () {
  // Clearing the screen.
  readline.cursorTo(process.stdout, 0, 0)
  readline.clearScreenDown(process.stdout)

  // Warnings.
  const tokenExpiryWarning = () => config.tokenAge - moment().dayOfYear() < 5
  const tokenExpires = moment().dayOfYear(config.tokenAge).fromNow()
  console.log(chalk[tokenExpiryWarning() ? 'red' : 'green'](`Token expires ${tokenExpires}`))

  // General info.
  console.log(chalk.green(`Uptime: ${chalk.white(startTime.fromNow(true))}\n`))
  // Positions in request queues.
  console.log(chalk.blue('You are queued up for:\n'))
  positions.forEach(project => {
    console.log(chalk.blue(`    Project ${project.project_id} - ${config.certs[project.project_id].name}`))
    console.log(chalk.yellow(`    Position in queue: ${chalk.white(project.position)}\n`))
  })
  if (!positions.length) {
    console.log(chalk.yellow(`    You have ${chalk.white(assignedCount)} (max) submissions assigned.\n`))
  }

  // Info on when the next check will occur for queue position and feedbacks.
  if (tick % infoInterval === 0) {
    console.log(chalk.blue('Checked the queue a few seconds ago...'))
    console.log(chalk.blue('Checked for new feedbacks a few seconds ago...\n'))
  } else {
    let remainingSeconds = (infoInterval - (tick % infoInterval)) * (tickrate / 1000)
    let infoIsCheckedAt = moment().add(remainingSeconds, 'seconds')
    let humanReadableMessage = moment().to(infoIsCheckedAt)
    console.log(chalk.blue(`Updating queue information ${humanReadableMessage}`))
    console.log(chalk.blue(`Checking feedbacks ${humanReadableMessage}\n`))
  }

  // Assigned info.
  console.log(chalk.green(`Currently assigned: ${chalk.white(assignedCount)}`))
  console.log(chalk.green(`Total assigned: ${chalk.white(assignedTotal)} since ${startTime.format('dddd, MMMM Do YYYY, HH:mm')}\n`))
  // How to exit.
  console.log(chalk.green.dim(`Press ${chalk.white('ctrl+c')} to exit the queue cleanly by deleting the submission_request.`))
  console.log(chalk.green.dim(`Press ${chalk.white('ESC')} to suspend the script without deleting the submission_request.\n`))
}

// ******************
// EXITING THE SCRIPT
// ******************

// It's necessary to add a readline interface to catch <ctrl>-C on Windows.
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

// Exit cleanly on <ctrl>-C by deleting the submission_request.
rl.on('SIGINT', () => {
  apiCall('delete', requestId)
  .then(res => {
    console.log(chalk.green('Successfully deleted request and exited..'))
    process.exit(0)
  })
  .catch(err => {
    console.log(chalk.red('Was unable to exit cleanly.'))
    console.log(err)
    process.exit(1)
  })
})

// Suspend on ESC without deleting the submission_request.
process.stdin.on('data', key => {
  if (key == '\u001b') {
    console.log(chalk.green('Exited without deleting the submission_request...'))
    process.exit(0)
  }
})
<|endoftext|>


<commit_before>var async = require('async'),
  clc = require('cli-color'),
  fs = require('fs'),
  moment = require('moment'),
  rimraf = require('rimraf'),
  util = require('../util'),
  file = util.file,
  log = util.log,
  spawn = util.spawn;

var command = function(command, sub, callback){
  spawn(command, sub,
    function(data){
      log.info(data);
    },
    function(data){
      log.error(data);
    },
    function(code){
      if (code == 0) callback();
      else log.error(code);
    }
  );
};

exports.deploy = function(){
  var target = process.cwd();

  async.series([
    function(next){
      require('../config')(target, function(){
        if (hexo.config.deploy) next();
        else log.error('You have to use `%s` to setup deploy.', clc.bold('hexo setup_deploy'));
      });
    },
    function(next){
      var publicDir = hexo.public_dir;

      fs.exists(publicDir, function(exist){
        if (exist){
          log.info('Copying public to deploy.');
          file.dir(publicDir, function(files){
            async.forEach(files, function(item, next){
              file.copy(publicDir + item, target + '/.deploy/' + item);
              next();
            }, next);
          });
        } else {
          log.error('You have to use `%s` to generate files first.', clc.bold('hexo generate'));
        }
      });
    },
    function(next){
      command('cd', [target + '/.deploy'], next);
    },
    function(next){
      command('git', ['add', '.'], next);
    },
    function(next){
      command('git', ['add', '-u'], next);
    },
    function(next){
      var message = 'Site updated: ' + moment().format(hexo.config.date_format + ' ' + hexo.config.time_format);
      log.info('Commiting: ' + message);
      command('git', ['commit', '-m', message], next);
    },
    function(next){
      log.info('Pushing files to remote');
      command('git', ['push', 'origin', hexo.config.deploy, '--force'], next);
    },
    function(next){
      rimraf(target + '/.deploy', function(){
        log.info('.deploy deleted.');
        next(null);
      });
    }
  ], function(){
    log.success('Deploy complete.');
  });
};

exports.setup = function(args){
  var repo = args[0];

  if (repo === undefined) return false;

  if (args[1]){
    var branch = args[1];
  } else {
    if (repo.match(/^(https?:\/\/|git(@|:\/\/))([^\/]+)/)[3].match(/github\.com/)){
      var branch = repo.match(/\/\w+\.github\.com/) ? 'master' : 'gh-pages';
    } else {
      var branch = 'master';
    }
  }

  async.series([
    function(next){
      command('git', ['init'], next);
    },
    function(next){
      command('git', ['add', '.'], next);
    },
    function(next){
      command('git', ['commit', '-m', 'init'], next);
    },
    function(next){
      if (branch == 'gh-pages') command('git', ['branch', '-m', 'gh-pages'], next);
      else next();
    },
    function(next){
      command('git', ['remote', 'add', 'origin', repo], next);
    },
    function(next){
      var configFile = process.cwd() + '/_config.yml',
        regex = /deploy:\s*[^\n]+/;

      file.read(configFile, function(err, content){
        if (err) throw err;

        if (content.match(regex)){
          var newContent = content.replace(regex, 'deploy: ' + branch);
        } else {
          var newContent = content + '\n\n' +
            '# Deploy\n' +
            'deploy: ' + branch;
        }

        file.write(configFile, newContent, next);
      });
    }
  ], function(){
    log.success('Now you can deploy to ' + repo + ' with `%s`.', clc.bold('hexo deploy'));
  });
};<commit_msg>fixed deploy bug<commit_after>var async = require('async'),
  clc = require('cli-color'),
  fs = require('fs'),
  moment = require('moment'),
  rimraf = require('rimraf'),
  util = require('../util'),
  file = util.file,
  log = util.log,
  spawn = util.spawn;

var command = function(command, sub, options, callback){
  spawn(command, sub, options,
    function(data){
      log.info(data);
    },
    function(data){
      log.error(data);
    },
    function(code){
      if (code == 0) callback();
      else log.error(code);
    }
  );
};

exports.deploy = function(){
  var target = process.cwd();

  async.series([
    // Load and check config
    function(next){
      require('../config')(target, function(){
        if (hexo.config.deploy) next();
        else log.error('You have to use `%s` to setup deploy.', clc.bold('hexo setup_deploy'));
      });
    },
    // Rename .deploy to .deploy.old
    function(next){
      fs.rename(target + '/.deploy', target + '/.deploy.old', function(){
        log.info('Renamed .deploy to .deploy.old.');
        next();
      });
    },
    // Create new .deploy folder
    function(next){
      file.mkdir(target + '/.deploy', function(){
        log.info('Created .deploy.');
        next();
      });
    },
    // Copy .deploy.old/git to .deploy/git
    function(next){
      file.dir(target + '/.deploy.old/.git', function(files){
        async.forEach(files, function(item, next){
          file.copy(target + '/.deploy.old/.git' + item, target + '/.deploy/.git' + item);
          next();
        }, function(){
          log.info('Copied .git folder from .deploy.old to .deploy.');
          next(null);
        });
      });
    },
    // Remove .deploy.old
    function(next){
      fs.rmdir(target + '/.deploy.old', function(){
        log.info('Removed .deploy.old.');
        next();
      });
    },
    // Copy public to .deploy
    function(next){
      var publicDir = hexo.public_dir;

      fs.exists(publicDir, function(exist){
        if (exist){
          file.dir(publicDir, function(files){
            async.forEach(files, function(item, next){
              var dirs = item.split('/');

              // Ignore dotfiles
              for (var i=0, len=dirs.length; i<len; i++){
                var front = dirs[i].substring(0, 1);
                if (front === '.'){
                  return next();
                }
              }

              file.copy(publicDir + item, target + '/.deploy/' + item);
              next();
            }, function(){
              log.info('Copied public to .deploy');
            });
          });
        } else {
          log.error('You have to use `%s` to generate files first.', clc.bold('hexo generate'));
        }
      });
    },
    function(next){
      command('git', ['add', '.'], {cwd: deployDir}, next);
    },
    function(next){
      command('git', ['add', '-u'], {cwd: deployDir}, next);
    },
    function(next){
      var message = 'Site updated: ' + moment().format(hexo.config.date_format + ' ' + hexo.config.time_format);
      log.info('Commiting: ' + message);
      command('git', ['commit', '-m', message], {cwd: deployDir}, next);
    },
    function(next){
      log.info('Pushing files to remote');
      command('git', ['push', 'origin', hexo.config.deploy, '--force'], {cwd: deployDir}, next);
    }
  ], function(){
    log.success('Deploy complete.');
  });
};

exports.setup = function(args){
  var repo = args[0],
    target = process.cwd(),
    deployDir = target + '/.deploy';

  if (repo === undefined) return false;

  if (args[1]){
    var branch = args[1];
  } else {
    if (repo.match(/^(https?:\/\/|git(@|:\/\/))([^\/]+)/)[3].match(/github\.com/)){
      var branch = repo.match(/\/\w+\.github\.com/) ? 'master' : 'gh-pages';
    } else {
      var branch = 'master';
    }
  }

  async.series([
    // Create and cd .deploy folder
    function(next){
      file.mkdir(deployDir, next);
    },
    // Create placeholder file
    function(next){
      file.write(deployDir + '/.placeholder', '', next);
    },
    function(next){
      command('git', ['init'], {cwd: deployDir}, next);
    },
    function(next){
      command('git', ['add', '.'], {cwd: deployDir}, next)
    },
    function(next){
      command('git', ['commit', '-m', 'Hexo init'], {cwd: deployDir}, next);
    },
    function(next){
      if (branch == 'gh-pages') command('git', ['branch', '-m', 'gh-pages'], {cwd: deployDir}, next);
      else next();
    },
    function(next){
      command('git', ['remote', 'add', 'origin', repo], {cwd: deployDir}, next);
    },
    function(next){
      var configFile = process.cwd() + '/_config.yml',
        regex = /deploy:\s*[^\n]+/;

      file.read(configFile, function(err, content){
        if (err) throw err;

        if (content.match(regex)){
          var newContent = content.replace(regex, 'deploy: ' + branch);
        } else {
          var newContent = content + '\n\n' +
            '# Deploy\n' +
            'deploy: ' + branch;
        }

        file.write(configFile, newContent, next);
      });
    }
  ], function(){
    log.success('Now you can deploy to ' + repo + ' with `%s`.', clc.bold('hexo deploy'));
  });
};<|endoftext|>


<commit_before>'use strict';

var fs = require('fs'),
    path = require('path'),
    npm = require('npm'),
    ejs = require('ejs'),
    ncp = require('ncp').ncp,
    mkdirp = require('mkdirp'),
    log = console.log;

function createPlugin (pluginName, appName) {
    log();

    var pluginNpmName = appName + '-plugin-' + pluginName;

    npm.load(null, function () {
        log(
            'Checking npm registry for module '.info + pluginNpmName.highlight
        );

        npm.view(pluginNpmName, function (error) {
            if (error) {
                if (error.code === 'E404') {
                    log(
                        ('npm module ' + pluginNpmName + ' is').info +
                        ' available'.success
                    );
                } else {
                    log(
                        'Failed to check module existance. Error code: ' +
                        error.code
                    );
                }

                var directory = path.normalize(
                    process.cwd() + '/' + pluginNpmName
                );

                mkdirp(directory, function (err) {
                    if (err) {
                        return console.error(err);
                    }

                    ncp(
                        path.join(__dirname, '/../../templates/plugin'),
                        directory,
                        function (err) {
                            if (err) {
                                return console.error(err);
                            }

                            // hack for webpack regexp based require
                            // See installable-browser-plugins-loader code
                            fs.renameSync(
                                directory + '/client-code',
                                directory + '/client'
                            );

                            var packageTemplatePath =
                                directory + '/' + 'package.ejs';

                            npm.view(appName, function (error, data) {
                                var appVersion;

                                if (error) {
                                    appVersion = '^0.0.1';
                                } else {
                                    appVersion = '^' + data.version;
                                }

                                fs.writeFileSync(
                                    directory + '/' + 'package.json',
                                    ejs.render(
                                        fs.readFileSync(
                                            packageTemplatePath, 'utf8'
                                        ),
                                        {
                                            pluginName: pluginNpmName,
                                            keyword: appName + '-plugin',
                                            appName: appName,
                                            appVersion: appVersion
                                        }
                                    )
                                );

                                fs.unlinkSync(packageTemplatePath);

                                log('Your application structure is ready'.info);
                            });
                        }
                    );
                });
            } else {
                console.error(
                    'npm module %s already exists'.error,
                    pluginNpmName
                );
            }
        });
    });
}

module.exports = createPlugin;
<commit_msg>add dotfiles to plugin <commit_after>'use strict';

var fs = require('fs'),
    path = require('path'),
    npm = require('npm'),
    ejs = require('ejs'),
    ncp = require('ncp').ncp,
    mkdirp = require('mkdirp'),
    log = console.log;

function createPlugin (pluginName, appName) {
    log();

    var pluginNpmName = appName + '-plugin-' + pluginName;

    npm.load(null, function () {
        log(
            'Checking npm registry for module '.info + pluginNpmName.highlight
        );

        npm.view(pluginNpmName, function (error) {
            if (error) {
                if (error.code === 'E404') {
                    log(
                        ('npm module ' + pluginNpmName + ' is').info +
                        ' available'.success
                    );
                } else {
                    log(
                        'Failed to check module existance. Error code: ' +
                        error.code
                    );
                }

                var directory = path.normalize(
                    process.cwd() + '/' + pluginNpmName
                );

                mkdirp(directory, function (err) {
                    if (err) {
                        return console.error(err);
                    }

                    ncp(
                        path.join(__dirname, '/../../templates/plugin'),
                        directory,
                        function (err) {
                            if (err) {
                                return console.error(err);
                            }

                            // hack for webpack regexp based require
                            // See installable-browser-plugins-loader code
                            fs.renameSync(
                                directory + '/client-code',
                                directory + '/client'
                            );

                            var packageTemplatePath =
                                directory + '/' + 'package.ejs';

                            npm.view(appName, function (error, data) {
                                var appVersion;

                                if (error) {
                                    appVersion = '^0.0.1';
                                } else {
                                    appVersion = '^' + data.version;
                                }

                                fs.writeFileSync(
                                    directory + '/' + 'package.json',
                                    ejs.render(
                                        fs.readFileSync(
                                            packageTemplatePath, 'utf8'
                                        ),
                                        {
                                            pluginName: pluginNpmName,
                                            keyword: appName + '-plugin',
                                            appName: appName,
                                            appVersion: appVersion
                                        }
                                    )
                                );

                                fs.unlinkSync(packageTemplatePath);

                                ncp(path.join(
                                    __dirname, '/../../templates/dotfiles'),
                                    directory,
                                    function (err) {
                                        if (err) {
                                            return console.error(err);
                                        }

                                        if (fs.existsSync(directory + '/.npmignore')) {
                                            fs.renameSync(
                                                directory + '/.npmignore',
                                                directory + '/.gitignore'
                                            );
                                        }
                                    }
                                );

                                log('Your application structure is ready'.info);
                            });
                        }
                    );
                });
            } else {
                console.error(
                    'npm module %s already exists'.error,
                    pluginNpmName
                );
            }
        });
    });
}

module.exports = createPlugin;
<|endoftext|>


<commit_before>var util = {

  hasProperty: function (obj, prop) {
    return Object.prototype.hasOwnProperty.call(Object(obj), prop);
  },

  noop: function () {
  },

  clone: function (proto, obj) {
    // note regarding speed and C++ bindings: evidently it's slightly
    // faster to provide a wrapper function than adding an additional
    // level of lookup.
    if (!(obj && typeof obj === 'object')) {
      obj = {};
    }
    obj.__proto__ = proto;
    return obj;
  },

  generateRandomString: function () {
    return ("" + Math.random()).substring(2);
  },

  error: function (err) {
    console.log(err);
    if (util.hasProperty(err, 'stack')) {
      console.log(err.stack);
    }
  },

  getAllChildFqns: function (parentObj, parentFqn) {
    var fqns = [];

    function getAllChildFqnsHelper(parentObj, parentFqn) {
      for (var prop in parentObj) {
        if (util.hasProperty(parentObj, prop)) {
          fqns.push(parentFqn + '.' + prop);
          if (parentObj[prop] && typeof parentObj[prop] === 'object') {
            getAllChildFqnsHelper(parentObj[prop], parentFqn + '.' + prop);
          }
        }
      }
    }
    getAllChildFqnsHelper(parentObj, parentFqn);
    return fqns;
  },

  extend: function (target, obj) {
    for (var i = 0, keys = Object.keys(obj), ii = keys.length; i < ii; i++) {
      var key = keys[i];
      if (obj[key] && typeof obj[key] === 'object') {
        target[key] = target[key] || {};
        util.extend(target[key], obj[key]);
      } else {
        target[key] = obj[key];
      }
    }
  },

  getVarFromFqn: function (fqn, scope) {
    var path = fqn.split('.');
    path.shift();
    var currVar = scope;
    var prop;
    while (path.length > 0) {
      prop = path.shift();
      if (util.hasProperty(currVar, prop)) {
        currVar = currVar[prop];
      } else {
        return false;
      }
    }
    return currVar;
  },

  stringify: JSON.stringify,
  parse: JSON.parse,

  flatten: function (val, fqn) {
    var vals = {};
    var fqns = util.getAllChildFqns(val, '');
    var m;
    for (var i = 0, ii = fqns.length; i < ii; i++) {
      m = util.getVarFromFqn(fqns[i], val);
      if (!Array.isArray(m)) {
        vals[fqn + fqns[i]] = m;
      }
    }
    return vals;
  },

  getValOrFqn: function (val, fqn) {
    if (typeof val === 'function') {
      return {fqn: fqn};
    } else {
      return val;
    }
  },

  isEmptyObj: function (obj) {
    for (var i in obj) {
      return false;
    }
    return true;
  }
};

exports.nowUtil = util;<commit_msg>fixed array bug<commit_after>var util = {

  hasProperty: function (obj, prop) {
    return Object.prototype.hasOwnProperty.call(Object(obj), prop);
  },

  noop: function () {
  },

  clone: function (proto, obj) {
    // note regarding speed and C++ bindings: evidently it's slightly
    // faster to provide a wrapper function than adding an additional
    // level of lookup.
    if (!(obj && typeof obj === 'object')) {
      obj = {};
    }
    obj.__proto__ = proto;
    return obj;
  },

  generateRandomString: function () {
    return ("" + Math.random()).substring(2);
  },

  error: function (err) {
    console.log(err);
    if (util.hasProperty(err, 'stack')) {
      console.log(err.stack);
    }
  },

  getAllChildFqns: function (parentObj, parentFqn) {
    var fqns = [];

    function getAllChildFqnsHelper(parentObj, parentFqn) {
      for (var prop in parentObj) {
        if (util.hasProperty(parentObj, prop)) {
          fqns.push(parentFqn + '.' + prop);
          if (parentObj[prop] && typeof parentObj[prop] === 'object') {
            getAllChildFqnsHelper(parentObj[prop], parentFqn + '.' + prop);
          }
        }
      }
    }
    getAllChildFqnsHelper(parentObj, parentFqn);
    return fqns;
  },

  extend: function (target, obj) {
    for (var i = 0, keys = Object.keys(obj), ii = keys.length; i < ii; i++) {
      var key = keys[i];
      if (obj[key] && typeof obj[key] === 'object') {
				if(Array.isArray(obj[key])) {
        	target[key] = target[key] || [];
				} else {
        	target[key] = target[key] || {};
				}
        util.extend(target[key], obj[key]);
      } else {
        target[key] = obj[key];
      }
    }
  },

  getVarFromFqn: function (fqn, scope) {
    var path = fqn.split('.');
    path.shift();
    var currVar = scope;
    var prop;
    while (path.length > 0) {
      prop = path.shift();
      if (util.hasProperty(currVar, prop)) {
        currVar = currVar[prop];
      } else {
        return false;
      }
    }
    return currVar;
  },

  stringify: JSON.stringify,
  parse: JSON.parse,

  flatten: function (val, fqn) {
    var vals = {};
    var fqns = util.getAllChildFqns(val, '');
    var m;
    for (var i = 0, ii = fqns.length; i < ii; i++) {
      m = util.getVarFromFqn(fqns[i], val);
      if (!Array.isArray(m)) {
        vals[fqn + fqns[i]] = m;
      }
    }
    return vals;
  },

  getValOrFqn: function (val, fqn) {
    if (typeof val === 'function') {
      return {fqn: fqn};
    } else {
      return val;
    }
  },

  isEmptyObj: function (obj) {
    for (var i in obj) {
      return false;
    }
    return true;
  }
};

exports.nowUtil = util;
<|endoftext|>


<commit_before>const prior = () => {};

export {prior};
<commit_msg>change the message to: TypeError: api is not a function<commit_after>const current = () => {};
const prior = () => {};

export {current, prior};
<|endoftext|>


<commit_before>angular.module("umbraco.directives")

    /**
    * @ngdoc directive
    * @name umbraco.directives.directive:localize
    * @restrict EA
    * @function
    * @description Localize directive
    **/
    .directive('localize', function ($log, localizationService) {
        return {
            restrict: 'E',
            scope:{
                key: '@'
            },
            replace: true,

            link: function (scope, element, attrs) {
                var key = scope.key;
                localizationService.localize(key).then(function(value){
                    element.html(value);
                });
            }
        };
    })

    .directive('localize', function ($log, localizationService) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                var keys = attrs.localize.split(',');

                angular.forEach(keys, function(value, key){
                    var attr = element.attr(value);

                    if(attr){
                        if(attr[0] === '@'){

                            var t = localizationService.tokenize(attr.substring(1), scope);
                            localizationService.localize(t.key, t.tokens).then(function(val){
                                    element.attr(value, val);
                            });

                        }
                    }
                });
            }
        };

    });
<commit_msg>Update Localize Directive (attribute & element) with some specifics in documentation about the format of the string for the key. Localize attribute now supports a key without having to be prefixed with @<commit_after>angular.module("umbraco.directives")

    /**
    * @ngdoc directive
    * @name umbraco.directives.directive:localize
    * @restrict E
    * @function
    * @description
    * Localize a specific token to put into the HTML as an item
    * ##Usage
    *

    * <localize key="general_close">Close</localize>
    * <localize key="section_key">Fallback value</localize>
    *

    **/
    .directive('localize', function ($log, localizationService) {
        return {
            restrict: 'E',
            scope:{
                key: '@'
            },
            replace: true,

            link: function (scope, element, attrs) {
                var key = scope.key;
                localizationService.localize(key).then(function(value){
                    element.html(value);
                });
            }
        };
    })

    /**
    * @ngdoc directive
    * @name umbraco.directives.directive:localize
    * @restrict A
    * @function
    * @description
    * Add a HTML attribute to an element containing the HTML attribute name you wish to localise
    * Using the format of '@section_key' or 'section_key'

    * ##Usage
    *

    * <input type="text" localize="placeholder" placeholder="@placeholders_entername" />
    * <input type="text" localize="placeholder,title" title="@section_key" placeholder="@placeholders_entername" />
    *

    *

    **/
    .directive('localize', function ($log, localizationService) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                //Support one or more attribute properties to update
                var keys = attrs.localize.split(',');

                angular.forEach(keys, function(value, key){
                    var attr = element.attr(value);

                    if(attr){
                        if(attr[0] === '@'){
                            //If the translation key starts with @ then remove it
                            attr = attr.substring(1);
                        }

                        var t = localizationService.tokenize(attr, scope);

                        localizationService.localize(t.key, t.tokens).then(function(val){
                                element.attr(value, val);
                        });
                    }
                });
            }
        };

    });
<|endoftext|>


<commit_before>angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [

    "$scope",
    "Umbraco.PropertyEditors.NestedContent.Resources",

    function ($scope, ncResources) {

        $scope.add = function () {
            $scope.model.value.push({
                // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                // For good measure we'll also prefix the tab alias "nc"
                ncAlias: "",
                ncTabAlias: "",
                nameTemplate: ""
            }
            );
        }

        $scope.remove = function (index) {
            $scope.model.value.splice(index, 1);
        }

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".icon-navigation"
        };

        $scope.docTypeTabs = {};

        ncResources.getContentTypes().then(function (docTypes) {
            $scope.model.docTypes = docTypes;

            // Populate document type tab dictionary
            docTypes.forEach(function (value) {
                $scope.docTypeTabs[value.alias] = value.tabs;
            });
        });

        if (!$scope.model.value) {
            $scope.model.value = [];
            $scope.add();
        }
    }
]);

angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [

    "$scope",
    "$interpolate",
    "$filter",
    "$timeout",
    "contentResource",
    "localizationService",
    "iconHelper",

    function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {

        //$scope.model.config.contentTypes;
        //$scope.model.config.minItems;
        //$scope.model.config.maxItems;
        //console.log($scope);

        var inited = false;

        _.each($scope.model.config.contentTypes, function (contentType) {
            contentType.nameExp = !!contentType.nameTemplate
                ? $interpolate(contentType.nameTemplate)
                : undefined;
        });

        $scope.editIconTitle = '';
        $scope.moveIconTitle = '';
        $scope.deleteIconTitle = '';

        // localize the edit icon title
        localizationService.localize('general_edit').then(function (value) {
            $scope.editIconTitle = value;
        });

        // localize the delete icon title
        localizationService.localize('general_delete').then(function (value) {
            $scope.deleteIconTitle = value;
        });

        // localize the move icon title
        localizationService.localize('actions_move').then(function (value) {
            $scope.moveIconTitle = value;
        });

        $scope.nodes = [];
        $scope.currentNode = undefined;
        $scope.realCurrentNode = undefined;
        $scope.scaffolds = undefined;
        $scope.sorting = false;

        $scope.minItems = $scope.model.config.minItems || 0;
        $scope.maxItems = $scope.model.config.maxItems || 0;

        if ($scope.maxItems == 0)
            $scope.maxItems = 1000;

        $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
        $scope.showIcons = $scope.model.config.showIcons || true;
        $scope.wideMode = $scope.model.config.hideLabel == "1";

        $scope.overlayMenu = {
            show: false,
            style: {}
        };

        // helper to force the current form into the dirty state
        $scope.setDirty = function () {
            if ($scope.propertyForm) {
                $scope.propertyForm.$setDirty();
            }
        };

        $scope.addNode = function (alias) {
            var scaffold = $scope.getScaffold(alias);

            var newNode = initNode(scaffold, null);

            $scope.currentNode = newNode;
            $scope.setDirty();

            $scope.closeNodeTypePicker();
        };

        $scope.openNodeTypePicker = function (event) {
            if ($scope.nodes.length >= $scope.maxItems) {
                return;
            }

            // this could be used for future limiting on node types
            $scope.overlayMenu.scaffolds = [];
            _.each($scope.scaffolds, function (scaffold) {
                $scope.overlayMenu.scaffolds.push({
                    alias: scaffold.contentTypeAlias,
                    name: scaffold.contentTypeName,
                    icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
                });
            });

            if ($scope.overlayMenu.scaffolds.length == 0) {
                return;
            }

            if ($scope.overlayMenu.scaffolds.length == 1) {
                // only one scaffold type - no need to display the picker
                $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                return;
            }

            $scope.overlayMenu.show = true;
        };

        $scope.closeNodeTypePicker = function () {
            $scope.overlayMenu.show = false;
        };

        $scope.editNode = function (idx) {
            if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
                $scope.currentNode = undefined;
            } else {
                $scope.currentNode = $scope.nodes[idx];
            }
        };

        $scope.deleteNode = function (idx) {
            if ($scope.nodes.length > $scope.model.config.minItems) {
                if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                    localizationService.localize('content_nestedContentDeleteItem').then(function (value) {
                        if (confirm(value)) {
                            $scope.nodes.splice(idx, 1);
                            $scope.setDirty();
                            updateModel();
                        }
                    });
                } else {
                    $scope.nodes.splice(idx, 1);
                    $scope.setDirty();
                    updateModel();
                }
            }
        };

        $scope.getName = function (idx) {

            var name = "Item " + (idx + 1);

            if ($scope.model.value[idx]) {

                var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);

                if (contentType != null && contentType.nameExp) {
                    // Run the expression against the stored dictionary value, NOT the node object
                    var item = $scope.model.value[idx];

                    // Add a temporary index property
                    item['$index'] = (idx + 1);

                    var newName = contentType.nameExp(item);
                    if (newName && (newName = $.trim(newName))) {
                        name = newName;
                    }

                    // Delete the index property as we don't want to persist it
                    delete item['$index'];
                }

            }

            // Update the nodes actual name value
            if ($scope.nodes[idx].name !== name) {
                $scope.nodes[idx].name = name;
            }

            return name;
        };

        $scope.getIcon = function (idx) {
            var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
            return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
        }

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".nested-content__icon--move",
            start: function (ev, ui) {
                // Yea, yea, we shouldn't modify the dom, sue me
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                    $(this).css("visibility", "hidden");
                });
                $scope.$apply(function () {
                    $scope.sorting = true;
                });
            },
            update: function (ev, ui) {
                $scope.setDirty();
            },
            stop: function (ev, ui) {
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                    $(this).css("visibility", "visible");
                });
                $scope.$apply(function () {
                    $scope.sorting = false;
                    updateModel();
                });
            }
        };

        $scope.getScaffold = function (alias) {
            return _.find($scope.scaffolds, function (scaffold) {
                return scaffold.contentTypeAlias == alias;
            });
        }

        $scope.getContentTypeConfig = function (alias) {
            return _.find($scope.model.config.contentTypes, function (contentType) {
                return contentType.ncAlias == alias;
            });
        }

        var notSupported = [
          "Umbraco.CheckBoxList",
          "Umbraco.DropDownMultiple",
          "Umbraco.MacroContainer",
          "Umbraco.RadioButtonList",
          "Umbraco.MultipleTextstring",
          "Umbraco.Tags",
          "Umbraco.UploadField",
          "Umbraco.ImageCropper"
        ];

        // Initialize
        var scaffoldsLoaded = 0;
        $scope.scaffolds = [];
        _.each($scope.model.config.contentTypes, function (contentType) {
            contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
                // remove all tabs except the specified tab
                var tab = _.find(scaffold.tabs, function (tab) {
                    return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == "");
                });
                scaffold.tabs = [];
                if (tab) {
                    scaffold.tabs.push(tab);

                    angular.forEach(tab.properties,
                      function (property) {
                          if (_.find(notSupported, function (x) { return x === property.editor; })) {
                              property.notSupported = true;
                              //TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                              property.notSupportedMessage = "Property " + property.label + " uses editor " + property.editor + " which is not supported by Nested Content.";
                          }
                      });
                }

                // Store the scaffold object
                $scope.scaffolds.push(scaffold);

                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            }, function (error) {
                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            });
        });

        var initIfAllScaffoldsHaveLoaded = function () {
            // Initialize when all scaffolds have loaded
            if ($scope.model.config.contentTypes.length == scaffoldsLoaded) {
                // Because we're loading the scaffolds async one at a time, we need to
                // sort them explicitly according to the sort order defined by the data type.
                var contentTypeAliases = [];
                _.each($scope.model.config.contentTypes, function (contentType) {
                    contentTypeAliases.push(contentType.ncAlias);
                });
                $scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) {
                    return contentTypeAliases.indexOf(s.contentTypeAlias);
                });

                // Convert stored nodes
                if ($scope.model.value) {
                    for (var i = 0; i < $scope.model.value.length; i++) {
                        var item = $scope.model.value[i];
                        var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
                        if (scaffold == null) {
                            // No such scaffold - the content type might have been deleted. We need to skip it.
                            continue;
                        }
                        initNode(scaffold, item);
                    }
                }

                // Enforce min items
                if ($scope.nodes.length < $scope.model.config.minItems) {
                    for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                        $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                    }
                }

                // If there is only one item, set it as current node
                if ($scope.singleMode || ($scope.nodes.length == 1 && $scope.maxItems == 1)) {
                    $scope.currentNode = $scope.nodes[0];
                }

                inited = true;
            }
        }

        var initNode = function (scaffold, item) {
            var node = angular.copy(scaffold);

            node.key = item && item.key ? item.key : UUID.generate();
            node.ncContentTypeAlias = scaffold.contentTypeAlias;

            for (var t = 0; t < node.tabs.length; t++) {
                var tab = node.tabs[t];
                for (var p = 0; p < tab.properties.length; p++) {
                    var prop = tab.properties[p];
                    prop.propertyAlias = prop.alias;
                    prop.alias = $scope.model.alias + "___" + prop.alias;
                    // Force validation to occur server side as this is the
                    // only way we can have consistancy between mandatory and
                    // regex validation messages. Not ideal, but it works.
                    prop.validation = {
                        mandatory: false,
                        pattern: ""
                    };
                    if (item) {
                        if (item[prop.propertyAlias]) {
                            prop.value = item[prop.propertyAlias];
                        }
                    }
                }
            }

            $scope.nodes.push(node);

            return node;
        }

        var updateModel = function () {
            if ($scope.realCurrentNode) {
                $scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
            }
            if (inited) {
                var newValues = [];
                for (var i = 0; i < $scope.nodes.length; i++) {
                    var node = $scope.nodes[i];
                    var newValue = {
                        key: node.key,
                        name: node.name,
                        ncContentTypeAlias: node.ncContentTypeAlias
                    };
                    for (var t = 0; t < node.tabs.length; t++) {
                        var tab = node.tabs[t];
                        for (var p = 0; p < tab.properties.length; p++) {
                            var prop = tab.properties[p];
                            if (typeof prop.value !== "function") {
                                newValue[prop.propertyAlias] = prop.value;
                            }
                        }
                    }
                    newValues.push(newValue);
                }
                $scope.model.value = newValues;
            }
        }

        $scope.$watch("currentNode", function (newVal) {
            updateModel();
            $scope.realCurrentNode = newVal;
        });

        var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
            updateModel();
        });

        $scope.$on('$destroy', function () {
            unsubscribe();
        });

        //TODO: Move this into a shared location?
        var UUID = (function () {
            var self = {};
            var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); }
            self.generate = function () {
                var d0 = Math.random() * 0xffffffff | 0;
                var d1 = Math.random() * 0xffffffff | 0;
                var d2 = Math.random() * 0xffffffff | 0;
                var d3 = Math.random() * 0xffffffff | 0;
                return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
                  lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
                  lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
                  lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
            }
            return self;
        })();
    }

]);
<commit_msg>Nested Content - enabled support for property-editors:<commit_after>angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [

    "$scope",
    "Umbraco.PropertyEditors.NestedContent.Resources",

    function ($scope, ncResources) {

        $scope.add = function () {
            $scope.model.value.push({
                // As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
                // For good measure we'll also prefix the tab alias "nc"
                ncAlias: "",
                ncTabAlias: "",
                nameTemplate: ""
            }
            );
        }

        $scope.remove = function (index) {
            $scope.model.value.splice(index, 1);
        }

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".icon-navigation"
        };

        $scope.docTypeTabs = {};

        ncResources.getContentTypes().then(function (docTypes) {
            $scope.model.docTypes = docTypes;

            // Populate document type tab dictionary
            docTypes.forEach(function (value) {
                $scope.docTypeTabs[value.alias] = value.tabs;
            });
        });

        if (!$scope.model.value) {
            $scope.model.value = [];
            $scope.add();
        }
    }
]);

angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [

    "$scope",
    "$interpolate",
    "$filter",
    "$timeout",
    "contentResource",
    "localizationService",
    "iconHelper",

    function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {

        //$scope.model.config.contentTypes;
        //$scope.model.config.minItems;
        //$scope.model.config.maxItems;
        //console.log($scope);

        var inited = false;

        _.each($scope.model.config.contentTypes, function (contentType) {
            contentType.nameExp = !!contentType.nameTemplate
                ? $interpolate(contentType.nameTemplate)
                : undefined;
        });

        $scope.editIconTitle = '';
        $scope.moveIconTitle = '';
        $scope.deleteIconTitle = '';

        // localize the edit icon title
        localizationService.localize('general_edit').then(function (value) {
            $scope.editIconTitle = value;
        });

        // localize the delete icon title
        localizationService.localize('general_delete').then(function (value) {
            $scope.deleteIconTitle = value;
        });

        // localize the move icon title
        localizationService.localize('actions_move').then(function (value) {
            $scope.moveIconTitle = value;
        });

        $scope.nodes = [];
        $scope.currentNode = undefined;
        $scope.realCurrentNode = undefined;
        $scope.scaffolds = undefined;
        $scope.sorting = false;

        $scope.minItems = $scope.model.config.minItems || 0;
        $scope.maxItems = $scope.model.config.maxItems || 0;

        if ($scope.maxItems == 0)
            $scope.maxItems = 1000;

        $scope.singleMode = $scope.minItems == 1 && $scope.maxItems == 1;
        $scope.showIcons = $scope.model.config.showIcons || true;
        $scope.wideMode = $scope.model.config.hideLabel == "1";

        $scope.overlayMenu = {
            show: false,
            style: {}
        };

        // helper to force the current form into the dirty state
        $scope.setDirty = function () {
            if ($scope.propertyForm) {
                $scope.propertyForm.$setDirty();
            }
        };

        $scope.addNode = function (alias) {
            var scaffold = $scope.getScaffold(alias);

            var newNode = initNode(scaffold, null);

            $scope.currentNode = newNode;
            $scope.setDirty();

            $scope.closeNodeTypePicker();
        };

        $scope.openNodeTypePicker = function (event) {
            if ($scope.nodes.length >= $scope.maxItems) {
                return;
            }

            // this could be used for future limiting on node types
            $scope.overlayMenu.scaffolds = [];
            _.each($scope.scaffolds, function (scaffold) {
                $scope.overlayMenu.scaffolds.push({
                    alias: scaffold.contentTypeAlias,
                    name: scaffold.contentTypeName,
                    icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
                });
            });

            if ($scope.overlayMenu.scaffolds.length == 0) {
                return;
            }

            if ($scope.overlayMenu.scaffolds.length == 1) {
                // only one scaffold type - no need to display the picker
                $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                return;
            }

            $scope.overlayMenu.show = true;
        };

        $scope.closeNodeTypePicker = function () {
            $scope.overlayMenu.show = false;
        };

        $scope.editNode = function (idx) {
            if ($scope.currentNode && $scope.currentNode.key == $scope.nodes[idx].key) {
                $scope.currentNode = undefined;
            } else {
                $scope.currentNode = $scope.nodes[idx];
            }
        };

        $scope.deleteNode = function (idx) {
            if ($scope.nodes.length > $scope.model.config.minItems) {
                if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes == 1) {
                    localizationService.localize('content_nestedContentDeleteItem').then(function (value) {
                        if (confirm(value)) {
                            $scope.nodes.splice(idx, 1);
                            $scope.setDirty();
                            updateModel();
                        }
                    });
                } else {
                    $scope.nodes.splice(idx, 1);
                    $scope.setDirty();
                    updateModel();
                }
            }
        };

        $scope.getName = function (idx) {

            var name = "Item " + (idx + 1);

            if ($scope.model.value[idx]) {

                var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);

                if (contentType != null && contentType.nameExp) {
                    // Run the expression against the stored dictionary value, NOT the node object
                    var item = $scope.model.value[idx];

                    // Add a temporary index property
                    item['$index'] = (idx + 1);

                    var newName = contentType.nameExp(item);
                    if (newName && (newName = $.trim(newName))) {
                        name = newName;
                    }

                    // Delete the index property as we don't want to persist it
                    delete item['$index'];
                }

            }

            // Update the nodes actual name value
            if ($scope.nodes[idx].name !== name) {
                $scope.nodes[idx].name = name;
            }

            return name;
        };

        $scope.getIcon = function (idx) {
            var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
            return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
        }

        $scope.sortableOptions = {
            axis: 'y',
            cursor: "move",
            handle: ".nested-content__icon--move",
            start: function (ev, ui) {
                // Yea, yea, we shouldn't modify the dom, sue me
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceRemoveEditor', false, $(this).attr('id'));
                    $(this).css("visibility", "hidden");
                });
                $scope.$apply(function () {
                    $scope.sorting = true;
                });
            },
            update: function (ev, ui) {
                $scope.setDirty();
            },
            stop: function (ev, ui) {
                $("#nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
                    tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
                    $(this).css("visibility", "visible");
                });
                $scope.$apply(function () {
                    $scope.sorting = false;
                    updateModel();
                });
            }
        };

        $scope.getScaffold = function (alias) {
            return _.find($scope.scaffolds, function (scaffold) {
                return scaffold.contentTypeAlias == alias;
            });
        }

        $scope.getContentTypeConfig = function (alias) {
            return _.find($scope.model.config.contentTypes, function (contentType) {
                return contentType.ncAlias == alias;
            });
        }

        var notSupported = [
          "Umbraco.Tags",
          "Umbraco.UploadField",
          "Umbraco.ImageCropper"
        ];

        // Initialize
        var scaffoldsLoaded = 0;
        $scope.scaffolds = [];
        _.each($scope.model.config.contentTypes, function (contentType) {
            contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
                // remove all tabs except the specified tab
                var tab = _.find(scaffold.tabs, function (tab) {
                    return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == "");
                });
                scaffold.tabs = [];
                if (tab) {
                    scaffold.tabs.push(tab);

                    angular.forEach(tab.properties,
                      function (property) {
                          if (_.find(notSupported, function (x) { return x === property.editor; })) {
                              property.notSupported = true;
                              //TODO: Not supported message to be replaced with 'content_nestedContentEditorNotSupported' dictionary key. Currently not possible due to async/timing quirk.
                              property.notSupportedMessage = "Property " + property.label + " uses editor " + property.editor + " which is not supported by Nested Content.";
                          }
                      });
                }

                // Store the scaffold object
                $scope.scaffolds.push(scaffold);

                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            }, function (error) {
                scaffoldsLoaded++;
                initIfAllScaffoldsHaveLoaded();
            });
        });

        var initIfAllScaffoldsHaveLoaded = function () {
            // Initialize when all scaffolds have loaded
            if ($scope.model.config.contentTypes.length == scaffoldsLoaded) {
                // Because we're loading the scaffolds async one at a time, we need to
                // sort them explicitly according to the sort order defined by the data type.
                var contentTypeAliases = [];
                _.each($scope.model.config.contentTypes, function (contentType) {
                    contentTypeAliases.push(contentType.ncAlias);
                });
                $scope.scaffolds = $filter('orderBy')($scope.scaffolds, function (s) {
                    return contentTypeAliases.indexOf(s.contentTypeAlias);
                });

                // Convert stored nodes
                if ($scope.model.value) {
                    for (var i = 0; i < $scope.model.value.length; i++) {
                        var item = $scope.model.value[i];
                        var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
                        if (scaffold == null) {
                            // No such scaffold - the content type might have been deleted. We need to skip it.
                            continue;
                        }
                        initNode(scaffold, item);
                    }
                }

                // Enforce min items
                if ($scope.nodes.length < $scope.model.config.minItems) {
                    for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
                        $scope.addNode($scope.scaffolds[0].contentTypeAlias);
                    }
                }

                // If there is only one item, set it as current node
                if ($scope.singleMode || ($scope.nodes.length == 1 && $scope.maxItems == 1)) {
                    $scope.currentNode = $scope.nodes[0];
                }

                inited = true;
            }
        }

        var initNode = function (scaffold, item) {
            var node = angular.copy(scaffold);

            node.key = item && item.key ? item.key : UUID.generate();
            node.ncContentTypeAlias = scaffold.contentTypeAlias;

            for (var t = 0; t < node.tabs.length; t++) {
                var tab = node.tabs[t];
                for (var p = 0; p < tab.properties.length; p++) {
                    var prop = tab.properties[p];
                    prop.propertyAlias = prop.alias;
                    prop.alias = $scope.model.alias + "___" + prop.alias;
                    // Force validation to occur server side as this is the
                    // only way we can have consistancy between mandatory and
                    // regex validation messages. Not ideal, but it works.
                    prop.validation = {
                        mandatory: false,
                        pattern: ""
                    };
                    if (item) {
                        if (item[prop.propertyAlias]) {
                            prop.value = item[prop.propertyAlias];
                        }
                    }
                }
            }

            $scope.nodes.push(node);

            return node;
        }

        var updateModel = function () {
            if ($scope.realCurrentNode) {
                $scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
            }
            if (inited) {
                var newValues = [];
                for (var i = 0; i < $scope.nodes.length; i++) {
                    var node = $scope.nodes[i];
                    var newValue = {
                        key: node.key,
                        name: node.name,
                        ncContentTypeAlias: node.ncContentTypeAlias
                    };
                    for (var t = 0; t < node.tabs.length; t++) {
                        var tab = node.tabs[t];
                        for (var p = 0; p < tab.properties.length; p++) {
                            var prop = tab.properties[p];
                            if (typeof prop.value !== "function") {
                                newValue[prop.propertyAlias] = prop.value;
                            }
                        }
                    }
                    newValues.push(newValue);
                }
                $scope.model.value = newValues;
            }
        }

        $scope.$watch("currentNode", function (newVal) {
            updateModel();
            $scope.realCurrentNode = newVal;
        });

        var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
            updateModel();
        });

        $scope.$on('$destroy', function () {
            unsubscribe();
        });

        //TODO: Move this into a shared location?
        var UUID = (function () {
            var self = {};
            var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); }
            self.generate = function () {
                var d0 = Math.random() * 0xffffffff | 0;
                var d1 = Math.random() * 0xffffffff | 0;
                var d2 = Math.random() * 0xffffffff | 0;
                var d3 = Math.random() * 0xffffffff | 0;
                return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
                  lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
                  lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
                  lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
            }
            return self;
        })();
    }

]);
<|endoftext|>