<commit_before>/* Copyright 2009-2011 Hewlett-Packard Development Company, L.P. All rights reserved. */
var example = ""+
	"Typewriter\n"+
	"==========\n"+
	"\n"+
	"Welcome to *Typewriter*, a Markdown editor for the HP TouchPad.\n"+
	"\n"+
	"Markdown\n"+
	"--------\n"+
	" \n"+
	"Markdown lets you create richtext documents by entering text in a simple format that's easy to read and write. No bells and whistles, just you and your text.\n"+
	"\n"+
	"To create a document simply\n"+
	"\n"+
	" 1. Type Markdown text [in the editor][editor]\n"+
	" 2. Preview the document after [dragging up the toolbar][preview]\n"+
	" \n"+
	"Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email.  As [John Gruber] writes on the [Markdown site][markdown]:\n"+
	"\n"+
	"> The overriding design goal for Markdown's\n"+
	"> formatting syntax is to make it as readable \n"+
	"> as possible. The idea is that a\n"+
	"> Markdown-formatted document should be\n"+
	"> publishable as-is, as plain text, without\n"+
	"> looking like it's been marked up with tags\n"+
	"> or formatting instructions.\n"+
	"\n"+
	"This document is written in Markdown; you are currently seeing the plaintext version. To get a feel for Markdown's syntax, type some text into the window and *pull up the toolbar*.\n"+
	"\n"+
	"What's next?\n"+
	"----------\n"+
	"\n"+
	"Go right ahead, [clear the editor][clear] and start typing. And don't mind the style, just mind your words. It'll come out alright by itself.\n"+
	"\n"+
	"  [john gruber]: http://daringfireball.net/\n"+
	"  [markdown]: http://daringfireball.net/projects/markdown/\n"+
	"  [editor]: #editor\n"+
	"  [preview]: #preview\n"+
	"  [clear]: #clear";

enyo.kind({
	name: "EditorPanel",
	kind: enyo.HFlexBox,
	components: [
		{name: "slidingPane", kind: "HSlidingPane", flex: 1, multiViewMinHeight:1, components: [
			{name: "top",flex: 1, kind:"HSlidingView",
				components: [
					{kind: "Header", components: [
						{kind:"Image", "src":"images/icon.png"},
						{content: "Typewriter", "style": "padding-left: 10px;"},
						{kind: "Spacer", flex: 11},
						{kind: "ToolButtonGroup", components: [
							{caption: "Save File", onclick: "saveFile"},
							{caption: "Open File...", onclick: "openFile"}
						]},
						{kind: "Spacer"}
					]},
					{kind: "VFlexBox", flex: 1, components: [
						{kind: "HFlexBox", flex: 1, components: [
							{className: "desk-left", flex: 1},
							{kind: "BasicScroller",
								name:"editorScroller",
								flex: 10,
								autoHorizontal: false,
								horizontal: false,
								className: "output-scroller",
								components: [
									{kind: "BasicRichText",
										name: "editor",
										flex: 10,
										value: example,
										richContent: false,
										className: "editor-input",
										onblur: "generateMarkdown"
									}
								]
							},
							{className: "desk-right", flex: 1}
						]}
					]}
			]},
			{name: "bottom", kind:"HSlidingView", height: "62px", flex:0, //peekHeight: 54,
				onResize: "barMoved",
				components: [
					{kind: "Header", className: "enyo-toolbar fake-toolbar", components: [
						{kind: "GrabButton", className: "HGrabButton"},
						{kind: "Spacer", flex: 18},
						/*{kind: "ToolButtonGroup", className: "enyo-toolbutton-dark", components: [
							{caption: "Headline", name:"markupheadline", className: "markuphelper", onclick:"markupHeadline" },
							{caption: "List", name:"markuplist", className: "markuphelper", onclick:"markupList" },
							{caption: "Link", name:"markuplink", className: "markuphelper", onclick:"markupLink" },
							{caption: "Quote", name:"markupquote", className: "markuphelper", onclick:"markupQuote" },
							{caption: "Code", name:"markupcode", className: "markuphelper", onclick:"markupCode" },
							{caption: "Image", name:"markupimage", className: "markuphelper", onclick:"markupImage" },
						]},
						{kind: "Spacer", flex: 10},
						*/
						{kind: "ToolButtonGroup", className: "enyo-toolbutton-dark", components: [
							{caption: "Syntax", name:"helpmarkdown", onclick: "helpMarkdown"},
							{caption: "Typewriter", name:"helptypewriter", onclick: "helpTypewriter"},
							{caption: "Print", name:"print", onclick: "printDocument"}
						]},
						{kind: "Spacer"},
						{kind: "GrabButton", className: "HGrabButton Right"},
					]},
					{kind: "VFlexBox", flex: 1, components: [
						{kind: "HFlexBox", flex: 1, components: [
							{className: "desk-left", flex: 1},
							{kind: "BasicScroller",
								name:"previewScroller",
								flex: 10,
								autoHorizontal: false,
								horizontal: false,
								className: "output-scroller",
								components: [
									{kind: "HtmlContent",
										name: "preview",
										className: "output-preview",
										onLinkClick: "htmlContentLinkClick"
									}
								]
							},
							{className: "desk-right", flex: 1}
						]}
					]}
			]}
		]},
		{name:'filePicker', kind: "FilePicker", fileType:["image"], allowMultiSelect:false, onPickFile: "handleFile"},
		{kind: "PrintDialog",
			duplexOption: true,
			frameToPrint: {name:"preview", landscape:false},
			appName: "Typewriter"
		},
		{name: 'markdownHelper', kind: "Popup", defaultKind: "Item", components: [
				{className: "enyo-first", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"Headline 1\n" +
							"==========\n" +
							"\n" +
							"Headline 2\n" +
							"----------\n" +
							"\n" +
							"### Headline 3"
						},
						{kind: "Label", content:"Headlines"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"Paragraphs are separated by an empty line\n" +
							"\n" +
							"Linebreaks are preceded by two spaces at the end of the line"
						},
						{kind: "Label", content:"Paragraphs"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							" 1. Numbered Listitem\n" +
							" 2. Numbered Listitem \n" +
							"   * Nested unordered List"
						},
						{kind: "Label", content:"Lists"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"> A Quote\n" +
							"> > A nested Quote"
						},
						{kind: "Label", content:"Quotes"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"----"
						},
						{kind: "Label", content:"Horizontal Rules"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"[link name][alias]\n" +
							"\n" +
							"  [alias]: http://www.google.de/"
						},
						{kind: "Label", content:"Links"}
					]}
				]},
				{className: "enyo-last", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"    /* indented by 4 or more spaces */\n"+
							"    function(x,y)..."
						},
						{kind: "Label", content:"Code"}
					]}
				]},

		]},
		{name: 'typewriterHelper', kind: "Popup", defaultKind:"Item", components: [
				{className: "enyo-first", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"Title of Work\n" +
							"=============\n" +
							"\n" +
							"This text is styled to resemble a cover page\n" +
							"\n" +
							"----\n" +
							"\n" +
							"The horizontal ruler (above) makes this become regular text"
						},
						{kind: "Label", content:"Cover Page"}
					]}
				]},
				{className: "enyo-last", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
					"Title of Work\n" +
					"=============\n" +
					"\n" +
					"This text is styled to resemble a cover page\n" +
					"\n" +
					"First Chapter\n" +
					"-------------\n" +
					"\n" +
					"This text is not on the cover page but the first chapter"
						},
						{kind: "Label", content:"Cover Page"}
					]}
				]},
		]}
	],

	/* PREVIEW HANDLING */

	barMoved: function(event) {
		if(event.slidePosition == 0) { // down position
			this.position = "down";
			this.$.editorScroller.setScrollTop(this.$.previewScroller.scrollTop/this.$.previewScroller.getBoundaries().bottom*this.$.editorScroller.getBoundaries().bottom);
			//this.$.editor.forceFocus(); // buggy with on screen keyboard
			this.$.print.addClass("enyo-button-disabled");
			this.$.print.disabled = true;

			//this.$$.select(".markuphelper").each(function(item) {
			//	item.removeClass("enyo-button-disabled");
			//	item.disabled = false;
			//});

			/*
			this.$.markupheadline.removeClass("enyo-button-disabled");
			this.$.markuplist.removeClass("enyo-button-disabled");
			this.$.markuplink.removeClass("enyo-button-disabled");
			this.$.markupquote.removeClass("enyo-button-disabled");
			this.$.markupcode.removeClass("enyo-button-disabled");
			this.$.markupimage.removeClass("enyo-button-disabled");
			this.$.markupheadline.disabled = false;
			this.$.markuplist.disabled = false;
			this.$.markuplink.disabled = false;
			this.$.markupquote.disabled = false;
			this.$.markupcode.disabled = false;
			this.$.markupimage.disabled = false;
			*/
		}
		else {  // up position
			this.position = "up";
			this.$.previewScroller.setScrollTop(this.$.editorScroller.scrollTop/this.$.editorScroller.getBoundaries().bottom*this.$.previewScroller.getBoundaries().bottom);
			this.$.print.removeClass("enyo-button-disabled");
			this.$.print.disabled = false;

			//this.$$.select(".markuphelper").each(function(item) {
			//	item.addClass("enyo-button-disabled");
			//	item.disabled = true;
			//});

			/*
			this.$.markupheadline.addClass("enyo-button-disabled");
			this.$.markuplist.addClass("enyo-button-disabled");
			this.$.markuplink.addClass("enyo-button-disabled");
			this.$.markupquote.addClass("enyo-button-disabled");
			this.$.markupcode.addClass("enyo-button-disabled");
			this.$.markupimage.addClass("enyo-button-disabled");
			this.$.markupheadline.disabled = true;
			this.$.markuplist.disabled = true;
			this.$.markuplink.disabled = true;
			this.$.markupquote.disabled = true;
			this.$.markupcode.disabled = true;
			this.$.markupimage.disabled = true;
			*/
		}

		//this.generateMarkdown();
	},
	generateMarkdown: function() {
		var converter = new Showdown.converter();

		var value = this.$.editor.getValue();
		this.$.preview.setContent(converter.makeHtml(value));
		//this.$.htmlContent.setContent(converter.makeHtml(example));
	},
	htmlContentLinkClick: function(sender, url) {
		var splits = url.split(/#/).slice(-1).pop();
		switch(splits) {
			case "clear":
				this.$.editor.setValue("");
			case "editor":
				this.$.slidingPane.selectView(this.$.top);
				return false;
				break;
			case "preview":
				this.$.slidingPane.selectView(this.$.bottom);
				return false;
				break;
			default:
				var r = new enyo.PalmService();
				r.service = "palm://com.palm.applicationManager/";
				r.method = "open";
				r.call({target: url});
		}
	},

	/* PRINT HANDLING */

	printDocument: function() {
		if(this.position == "down") {
			alert("Preview first!");
		}
		else {
			this.$.printDialog.openAtCenter();
		}
	},

	/* FILE HANDLING */

	openFile: function() {
		this.$.filePicker.pickFile();
	},
	handleFile: function(inSender, msg) {
		this.$.editor.setValue(enyo.json.stringify(msg));
	},

	/* MARKUP CALLBACKS */

	markupHeadline: function() {
	},
	markupList: function() {
	},
	markupLink: function() {
	},
	markupQuote: function() {
	},
	markupCode: function() {
	},
	markupImage: function() {
	},

	helpMarkdown: function() {
		this.$.markdownHelper.openAtControl(this.$.helpmarkdown, {left: 170});
	},
	helpTypewriter: function() {
		this.$.typewriterHelper.openAtControl(this.$.helptypewriter, {left: 220});
	},

	/* CONSTRUCTOR */

	ready: function() {
		this.generateMarkdown();
		this.$.editor.forceFocus();
	}
});
<commit_msg>Verbessertes Keyboard-Verhalten<commit_after>/* Copyright 2009-2011 Hewlett-Packard Development Company, L.P. All rights reserved. */
var example = ""+
	"Typewriter\n"+
	"==========\n"+
	"\n"+
	"Welcome to *Typewriter*, a Markdown editor for the HP TouchPad.\n"+
	"\n"+
	"Markdown\n"+
	"--------\n"+
	" \n"+
	"Markdown lets you create richtext documents by entering text in a simple format that's easy to read and write. No bells and whistles, just you and your text.\n"+
	"\n"+
	"To create a document simply\n"+
	"\n"+
	" 1. Type Markdown text [in the editor][editor]\n"+
	" 2. Preview the document after [dragging up the toolbar][preview]\n"+
	" \n"+
	"Markdown is a lightweight markup language based on the formatting conventions that people naturally use in email.  As [John Gruber] writes on the [Markdown site][markdown]:\n"+
	"\n"+
	"> The overriding design goal for Markdown's\n"+
	"> formatting syntax is to make it as readable \n"+
	"> as possible. The idea is that a\n"+
	"> Markdown-formatted document should be\n"+
	"> publishable as-is, as plain text, without\n"+
	"> looking like it's been marked up with tags\n"+
	"> or formatting instructions.\n"+
	"\n"+
	"This document is written in Markdown; you are currently seeing the plaintext version. To get a feel for Markdown's syntax, type some text into the window and *pull up the toolbar*.\n"+
	"\n"+
	"What's next?\n"+
	"----------\n"+
	"\n"+
	"Go right ahead, [clear the editor][clear] and start typing. And don't mind the style, just mind your words. It'll come out alright by itself.\n"+
	"\n"+
	"  [john gruber]: http://daringfireball.net/\n"+
	"  [markdown]: http://daringfireball.net/projects/markdown/\n"+
	"  [editor]: #editor\n"+
	"  [preview]: #preview\n"+
	"  [clear]: #clear";

enyo.kind({
	name: "EditorPanel",
	kind: enyo.HFlexBox,
	components: [
		{name: "slidingPane", kind: "HSlidingPane", flex: 1, multiViewMinHeight:1, components: [
			{name: "top",flex: 1, kind:"HSlidingView",
				components: [
					{kind: "Header", components: [
						{kind:"Image", "src":"images/icon.png"},
						{content: "Typewriter", "style": "padding-left: 10px;"},
						{kind: "Spacer", flex: 11},
						{kind: "ToolButtonGroup", components: [
							{caption: "Save File", onclick: "saveFile"},
							{caption: "Open File...", onclick: "openFile"}
						]},
						{kind: "Spacer"}
					]},
					{kind: "VFlexBox", flex: 1, components: [
						{kind: "HFlexBox", flex: 1, components: [
							{className: "desk-left", flex: 1},
							{kind: "BasicScroller",
								name:"editorScroller",
								flex: 10,
								autoHorizontal: false,
								horizontal: false,
								className: "output-scroller",
								components: [
									{kind: "BasicRichText",
										name: "editor",
										flex: 10,
										value: example,
										richContent: false,
										className: "editor-input",
										onblur: "generateMarkdown",
										onfocus: "showKeyboard"
									}
								]
							},
							{className: "desk-right", flex: 1}
						]}
					]}
			]},
			{name: "bottom", kind:"HSlidingView", height: "62px", flex:0, //peekHeight: 54,
				onResize: "barMoved",
				components: [
					{kind: "Header", className: "enyo-toolbar fake-toolbar", components: [
						{kind: "GrabButton", className: "HGrabButton"},
						{kind: "Spacer", flex: 18},
						/*{kind: "ToolButtonGroup", className: "enyo-toolbutton-dark", components: [
							{caption: "Headline", name:"markupheadline", className: "markuphelper", onclick:"markupHeadline" },
							{caption: "List", name:"markuplist", className: "markuphelper", onclick:"markupList" },
							{caption: "Link", name:"markuplink", className: "markuphelper", onclick:"markupLink" },
							{caption: "Quote", name:"markupquote", className: "markuphelper", onclick:"markupQuote" },
							{caption: "Code", name:"markupcode", className: "markuphelper", onclick:"markupCode" },
							{caption: "Image", name:"markupimage", className: "markuphelper", onclick:"markupImage" },
						]},
						{kind: "Spacer", flex: 10},
						*/
						{kind: "ToolButtonGroup", className: "enyo-toolbutton-dark", components: [
							{caption: "Syntax", name:"helpmarkdown", onclick: "helpMarkdown"},
							{caption: "Typewriter", name:"helptypewriter", onclick: "helpTypewriter"},
							{caption: "Print", name:"print", onclick: "printDocument"}
						]},
						{kind: "Spacer"},
						{kind: "GrabButton", className: "HGrabButton Right"},
					]},
					{kind: "VFlexBox", flex: 1, components: [
						{kind: "HFlexBox", flex: 1, components: [
							{className: "desk-left", flex: 1},
							{kind: "BasicScroller",
								name:"previewScroller",
								flex: 10,
								autoHorizontal: false,
								horizontal: false,
								className: "output-scroller",
								components: [
									{kind: "HtmlContent",
										name: "preview",
										className: "output-preview",
										onLinkClick: "htmlContentLinkClick"
									}
								]
							},
							{className: "desk-right", flex: 1}
						]}
					]}
			]}
		]},
		{name:'filePicker', kind: "FilePicker", fileType:["image"], allowMultiSelect:false, onPickFile: "handleFile"},
		{kind: "PrintDialog",
			duplexOption: true,
			frameToPrint: {name:"preview", landscape:false},
			appName: "Typewriter"
		},
		{name: 'markdownHelper', kind: "Popup", defaultKind: "Item", components: [
				{className: "enyo-first", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"Headline 1\n" +
							"==========\n" +
							"\n" +
							"Headline 2\n" +
							"----------\n" +
							"\n" +
							"### Headline 3"
						},
						{kind: "Label", content:"Headlines"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"Paragraphs are separated by an empty line\n" +
							"\n" +
							"Linebreaks are preceded by two spaces at the end of the line"
						},
						{kind: "Label", content:"Paragraphs"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							" 1. Numbered Listitem\n" +
							" 2. Numbered Listitem \n" +
							"   * Nested unordered List"
						},
						{kind: "Label", content:"Lists"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"> A Quote\n" +
							"> > A nested Quote"
						},
						{kind: "Label", content:"Quotes"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"----"
						},
						{kind: "Label", content:"Horizontal Rules"}
					]}
				]},
				{components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"[link name][alias]\n" +
							"\n" +
							"  [alias]: http://www.google.de/"
						},
						{kind: "Label", content:"Links"}
					]}
				]},
				{className: "enyo-last", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"    /* indented by 4 or more spaces */\n"+
							"    function(x,y)..."
						},
						{kind: "Label", content:"Code"}
					]}
				]},

		]},
		{name: 'typewriterHelper', kind: "Popup", defaultKind:"Item", components: [
				{className: "enyo-first", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
							"Title of Work\n" +
							"=============\n" +
							"\n" +
							"This text is styled to resemble a cover page\n" +
							"\n" +
							"----\n" +
							"\n" +
							"The horizontal ruler (above) makes this become regular text"
						},
						{kind: "Label", content:"Cover Page"}
					]}
				]},
				{className: "enyo-last", components: [
					{kind: "HFlexBox", components: [
						{className: "markdownHelper", flex: 1, content: ""+
					"Title of Work\n" +
					"=============\n" +
					"\n" +
					"This text is styled to resemble a cover page\n" +
					"\n" +
					"First Chapter\n" +
					"-------------\n" +
					"\n" +
					"This text is not on the cover page but the first chapter"
						},
						{kind: "Label", content:"Cover Page"}
					]}
				]},
		]}
	],

	/* PREVIEW HANDLING */

	barMoved: function(event) {
		if(event.slidePosition == 0) { // down position
			this.position = "down";
			this.$.editorScroller.setScrollTop(this.$.previewScroller.scrollTop/this.$.previewScroller.getBoundaries().bottom*this.$.editorScroller.getBoundaries().bottom);
			//this.$.editor.forceFocus(); // buggy with on screen keyboard
			this.$.print.addClass("enyo-button-disabled");
			this.$.print.disabled = true;

			//this.$$.select(".markuphelper").each(function(item) {
			//	item.removeClass("enyo-button-disabled");
			//	item.disabled = false;
			//});

			/*
			this.$.markupheadline.removeClass("enyo-button-disabled");
			this.$.markuplist.removeClass("enyo-button-disabled");
			this.$.markuplink.removeClass("enyo-button-disabled");
			this.$.markupquote.removeClass("enyo-button-disabled");
			this.$.markupcode.removeClass("enyo-button-disabled");
			this.$.markupimage.removeClass("enyo-button-disabled");
			this.$.markupheadline.disabled = false;
			this.$.markuplist.disabled = false;
			this.$.markuplink.disabled = false;
			this.$.markupquote.disabled = false;
			this.$.markupcode.disabled = false;
			this.$.markupimage.disabled = false;
			*/
		}
		else {  // up position
			this.position = "up";
			this.$.previewScroller.setScrollTop(this.$.editorScroller.scrollTop/this.$.editorScroller.getBoundaries().bottom*this.$.previewScroller.getBoundaries().bottom);
			this.$.print.removeClass("enyo-button-disabled");
			this.$.print.disabled = false;

			//this.$$.select(".markuphelper").each(function(item) {
			//	item.addClass("enyo-button-disabled");
			//	item.disabled = true;
			//});

			/*
			this.$.markupheadline.addClass("enyo-button-disabled");
			this.$.markuplist.addClass("enyo-button-disabled");
			this.$.markuplink.addClass("enyo-button-disabled");
			this.$.markupquote.addClass("enyo-button-disabled");
			this.$.markupcode.addClass("enyo-button-disabled");
			this.$.markupimage.addClass("enyo-button-disabled");
			this.$.markupheadline.disabled = true;
			this.$.markuplist.disabled = true;
			this.$.markuplink.disabled = true;
			this.$.markupquote.disabled = true;
			this.$.markupcode.disabled = true;
			this.$.markupimage.disabled = true;
			*/
		}

		//this.generateMarkdown();
	},
	generateMarkdown: function() {
		var converter = new Showdown.converter();

		var value = this.$.editor.getValue();
		this.$.preview.setContent(converter.makeHtml(value));
		//this.$.htmlContent.setContent(converter.makeHtml(example));
	},
	htmlContentLinkClick: function(sender, url) {
		var splits = url.split(/#/).slice(-1).pop();
		switch(splits) {
			case "clear":
				this.$.editor.setValue("");
			case "editor":
				this.$.slidingPane.selectView(this.$.top);
				return false;
				break;
			case "preview":
				this.$.slidingPane.selectView(this.$.bottom);
				return false;
				break;
			default:
				var r = new enyo.PalmService();
				r.service = "palm://com.palm.applicationManager/";
				r.method = "open";
				r.call({target: url});
		}
	},

	/* PRINT HANDLING */

	printDocument: function() {
		if(this.position == "down") {
			alert("Preview first!");
		}
		else {
			this.$.printDialog.openAtCenter();
		}
	},

	/* FILE HANDLING */

	openFile: function() {
		this.$.filePicker.pickFile();
	},
	handleFile: function(inSender, msg) {
		this.$.editor.setValue(enyo.json.stringify(msg));
	},

	/* MARKUP CALLBACKS */

	markupHeadline: function() {
	},
	markupList: function() {
	},
	markupLink: function() {
	},
	markupQuote: function() {
	},
	markupCode: function() {
	},
	markupImage: function() {
	},

	helpMarkdown: function() {
		this.$.markdownHelper.openAtControl(this.$.helpmarkdown, {left: 170});
	},
	helpTypewriter: function() {
		this.$.typewriterHelper.openAtControl(this.$.helptypewriter, {left: 220});
	},
	showKeyboard: function() {
		enyo.keyboard.show();
	},

	/* CONSTRUCTOR */

	ready: function() {
		enyo.keyboard.setManualMode(true);
		enyo.keyboard.show();
		this.generateMarkdown();
		this.$.editor.forceFocus();
	}
});
<|endoftext|>


<commit_before>/* @flow */
/* eslint-disable no-console */

import crypto from 'crypto';
import route from 'koa-route';
import winston from 'winston';
import { bus, config } from '../../core-server';
import * as pg from '../../lib/pg';
import promisify from '../../lib/promisify';

/*
	IMPLEMENTATION DETAILS:
		- extract path from the long URL { regex (/https?:\/\/[^\/]+/) }.
		- Short URL will be 6 characters url safe base64 hash.
		- In case of a collision, the first 6 characters will be a substring of the base64 hash
		  and the 7th character will be appended from the chars string in a sequential manner.
		- In case of getShortURL api call, simply hash and try to insert it to the DB.
		- If everything goes well it means we have successfully created the short url.
		- In case of an error there are two possibilities:
		  - There is a collision.
		  - The long url already exists.
		- Do a LIKE query with the (0..6) substring of the hash.
		- On getting back results, check whether the long URL of the results returned from
		  the DB matches the long URL passed as parameter.
		- If any of them does, return the corresponding short URL.
		- In any other case, we have a collision. We can resolve it by appending 7th character which
		  is 1 place higher than the highest 7th position character of the short URLS from the result.
*/

const chars = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
const doReadQuery = promisify(pg.read.bind(pg, config.connStr));
const doWriteQuery = promisify(pg.write.bind(pg, config.connStr));

const isShortURL = (shortURL: string): boolean => {
	if (shortURL.length === 6 || shortURL.length === 7) {
		return /^[A-Za-z0-9\-_]+$/.test(shortURL);
	}
	return false;
};

const getPathFromURL = (longURL: string): string => {
	return longURL.replace(/https?:\/\/[^\/]+/, '');
};

const makeURLSafeHash = (pathFromLongURL: string): string => {
	const md5Hash = crypto.createHash('md5').update(pathFromLongURL).digest('base64');
	// regex to make url safe hash, converts '+' to '-', '/' to '_', removes "=" from the end
	const urlSafeHash = md5Hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
	return urlSafeHash.slice(0, 6);
};

const insertLongURL = (shortURL: string, longURL: string): Promise<Array<{rowCount: number}>> => {
	return doWriteQuery([ {
		$: `INSERT INTO urls (
			shorturl, longurl
		) VALUES (
			&{shortURL}, &{longURL}
		)`,
		shortURL,
		longURL
	} ]);
};

export const getShortURL = (longURL: string): Promise<string> => {
	const pathFromLongURL = getPathFromURL(longURL);
	const shortURL = makeURLSafeHash(pathFromLongURL);
	// Try inserting long URL and its hash as the short URL.
	return insertLongURL(shortURL, pathFromLongURL)
		// in case of a successful insertion, simply return the hash.
		.then((result) => {
			// result: [{.., rowcount: _, ...}, ..]
			if (result[0].rowCount === 1) {
				winston.info('Insert operation performed successfully.');
				return shortURL;
			}
			return null;
		})
		// in case of an error, take proper measures.
		.catch((error) => {
			winston.error(`something went wrong while perfoming insertion, ${error}`);
			return doReadQuery({
				$: 'SELECT shorturl, longurl FROM urls WHERE shorturl LIKE &{shortURL}',
				shortURL: shortURL + '%'
			})
			.then((urlsList) => {
				// urlsList: [{shorturl: _, longurl: _}, ...]
				let maxIndex = 0;
				for (const urls of urlsList) {
					if (urls.longurl === pathFromLongURL) {
						winston.info('The long url already exists');
						return urls.shorturl;
					} else if (urls.shorturl.length === 7) {
						const indexInChar = chars.indexOf(urls.shorturl[6]);
						if (indexInChar >= maxIndex) {
							if (indexInChar < chars.length - 1) {
								maxIndex = indexInChar + 1;
							} else if (indexInChar === chars.length - 1) {
								maxIndex = -1;
							}
						}
					}
				}
				return maxIndex >= 0 ? shortURL + chars[maxIndex] : null;
			});
		});
};

export const getLongURL = (shortURL: string): Promise<string> => {
	return doReadQuery({
		$: 'SELECT * FROM urls WHERE shorturl = &{shortURL}',
		shortURL
	})
	.then((result) => {
		if (result.length > 0) {
			return doWriteQuery([ {
				$: 'UPDATE urls SET count = count + 1 WHERE shorturl = &{shortURL}',
				shortURL
			} ])
			.then(() => {
				return result[0].longurl;
			});
		} else {
			return null;
		}
	});
};

/*
	Demo api call
		getShortURL('https://ict4kids.files.wordpress.com/2013/05/mrc-2.png')
			.then((result) => {
				console.log(result);
			})
			.catch((error) => {
				console.error(`something went wrong while getting the short URL, ${error}`);
			});

		getLongURL('28XYe8')
			.then((result) => {
				console.log(result);
			})
			.catch((error) => {
				console.error(`something went wrong while getting the long URL, ${error}`);
			});
*/

bus.on('http/init', app => {
	app.use(route.get('*', function *() {
		const path = this.request.url.slice(1).match(/^[^\/?]*/, '')[0];
		try {
			if (isShortURL(path)) {
				const longURL = yield getLongURL(path);
				if (longURL) {
					this.response.redirect(longURL);
				}
			}
		} catch (e) {
			// Do nothing !
		}
	}));
});
<commit_msg>minor changes<commit_after>/* @flow */
/* eslint-disable no-console */

import crypto from 'crypto';
import route from 'koa-route';
import winston from 'winston';
import { bus, config } from '../../core-server';
import * as pg from '../../lib/pg';
import promisify from '../../lib/promisify';

/*
	IMPLEMENTATION DETAILS:
		- extract path from the long URL { regex (/https?:\/\/[^\/]+/) }.
		- Short URL will be 6 characters url safe base64 hash.
		- In case of a collision, the first 6 characters will be a substring of the base64 hash
		  and the 7th character will be appended from the chars string in a sequential manner.
		- In case of getShortURL api call, simply hash and try to insert it to the DB.
		- If everything goes well it means we have successfully created the short url.
		- In case of an error there are two possibilities:
		  - There is a collision.
		  - The long url already exists.
		- Do a LIKE query with the (0..6) substring of the hash.
		- On getting back results, check whether the long URL of the results returned from
		  the DB matches the long URL passed as parameter.
		- If any of them does, return the corresponding short URL.
		- In any other case, we have a collision. We can resolve it by appending 7th character which
		  is 1 place higher than the highest 7th position character of the short URLS from the result.
*/

const chars = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
const doReadQuery = promisify(pg.read.bind(pg, config.connStr));
const doWriteQuery = promisify(pg.write.bind(pg, config.connStr));

const isShortURL = (shortURL: string): boolean => {
	if (shortURL.length === 6 || shortURL.length === 7) {
		return /^[A-Za-z0-9\-_]+$/.test(shortURL);
	}
	return false;
};

const getPathFromURL = (longURL: string): string => {
	return longURL.replace(/https?:\/\/[^\/]+/, '');
};

const makeURLSafeHash = (pathFromLongURL: string): string => {
	const md5Hash = crypto.createHash('md5').update(pathFromLongURL).digest('base64');
	// regex to make url safe hash, converts '+' to '-', '/' to '_', removes "=" from the end
	const urlSafeHash = md5Hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
	return urlSafeHash.slice(0, 6);
};

const insertLongURL = (shortURL: string, longURL: string): Promise<Array<{rowCount: number}>> => {
	return doWriteQuery([ {
		$: `INSERT INTO urls (
			shorturl, longurl
		) VALUES (
			&{shortURL}, &{longURL}
		)`,
		shortURL,
		longURL
	} ]);
};

export const getShortURL = (longURL: string): Promise<string> => {
	const pathFromLongURL = getPathFromURL(longURL);
	const shortURL = makeURLSafeHash(pathFromLongURL);
	// Try inserting long URL and its hash as the short URL.
	return insertLongURL(shortURL, pathFromLongURL)
		// in case of a successful insertion, simply return the hash.
		.then((result) => {
			// result: [{.., rowcount: _, ...}, ..]
			if (result[0].rowCount === 1) {
				winston.info('Insert operation performed successfully.');
				return shortURL;
			}
			return null;
		})
		// in case of an error, take proper measures.
		.catch((error) => {
			winston.info(`something went wrong while perfoming insertion, ${error}`);
			return doReadQuery({
				$: 'SELECT shorturl, longurl FROM urls WHERE shorturl LIKE &{shortURL}',
				shortURL: shortURL + '%'
			})
			.then((links) => {
				// links: [{shorturl: _, longurl: _}, ...]
				let maxIndex = 0;
				for (const link of links) {
					if (link.longurl === pathFromLongURL) {
						winston.info('The long url already exists');
						return link.shorturl;
					} else if (link.shorturl.length === 7) {
						const indexInChar = chars.indexOf(link.shorturl[6]);
						if (indexInChar >= maxIndex) {
							if (indexInChar < chars.length - 1) {
								maxIndex = indexInChar + 1;
							} else if (indexInChar === chars.length - 1) {
								throw new Error(`Max collisions exceeded for hash: ${shortURL}`);
							}
						}
					}
				}
				return insertLongURL(shortURL + chars[maxIndex], pathFromLongURL)
					.then(() => {
						return shortURL + chars[maxIndex];
					});
			});
		});
};

export const getLongURL = (shortURL: string): Promise<string> => {
	return doReadQuery({
		$: 'SELECT * FROM urls WHERE shorturl = &{shortURL}',
		shortURL
	})
	.then((result) => {
		if (result.length > 0) {
			return doWriteQuery([ {
				$: 'UPDATE urls SET count = count + 1 WHERE shorturl = &{shortURL}',
				shortURL
			} ])
			.then(() => {
				return result[0].longurl;
			});
		} else {
			return null;
		}
	});
};

/*
	Demo api call
		getShortURL('https://ict4kids.files.wordpress.com/2013/05/mrc-2.png')
			.then((result) => {
				console.log(result);
			})
			.catch((error) => {
				console.error(`something went wrong while getting the short URL, ${error}`);
			});

		getLongURL('28XYe8')
			.then((result) => {
				console.log(result);
			})
			.catch((error) => {
				console.error(`something went wrong while getting the long URL, ${error}`);
			});
*/

bus.on('http/init', app => {
	app.use(route.get('*', function *() {
		const path = this.request.url.slice(1).match(/^[^\/?]*/, '')[0];
		try {
			if (isShortURL(path)) {
				const longURL = yield getLongURL(path);
				if (longURL) {
					this.response.redirect(longURL);
				}
			}
		} catch (e) {
			// Do nothing !
		}
	}));
});
<|endoftext|>


<commit_before>const R = require('ramda')
const { EOL } = require('os')
const pRetry = require('p-retry')
const deepMerge = require('deepmerge')
const tempWrite = require('temp-write')
const writeJsonFile = require('write-json-file')
const TerminalUtils = require('constellate-dev-utils/modules/terminal')
const ChildProcessUtils = require('constellate-dev-utils/modules/childProcess')

module.exports = function nowDeploy(deployPath, options, project) {
  if (process.env.NOW_USERNAME == null) {
    TerminalUtils.error(
      'In order to deploy to "now" you must supply your "now" username via a NOW_USERNAME environment variable.',
    )
    process.exit(1)
  }

  if (process.env.NOW_TOKEN == null) {
    TerminalUtils.error(
      'In order to deploy to "now" you must supply your "now" API token via a NOW_TOKEN environment variable.',
    )
    process.exit(1)
  }

  if (R.isNil(options.alias) || R.isEmpty(options.alias)) {
    TerminalUtils.error('You must supply an "alias" for the "now" deploy plugin.')
    process.exit(1)
  }

  return {
    deploy: async () => {
      try {
        ChildProcessUtils.execSync('now', ['-v'])
      } catch (err) {
        TerminalUtils.error(
          'You need to have the "now" CLI installed on your machine and available on your PATH in order to deploy to now.',
        )
        throw err
      }

      const deploymentName = project.packageName

      const alias = options.alias
      const envVars = options.passThroughEnvVars
        ? options.passThroughEnvVars.reduce(
            (acc, cur) => [...acc, '-e', `${cur}=${process.env[cur]}`],
            [],
          )
        : []

      const nowConfigPath = tempWrite.sync()
      const nowConfig = deepMerge(
        deepMerge(
          // Defaults
          {
            forwardNpm: true,
            public: false,
          },
          // User overrides
          options.nowConfig || {},
        ),
        // Fixed - these must be provided via the env for forced safety
        {
          token: process.env.NOW_TOKEN,
          user: {
            username: process.env.NOW_USERNAME,
          },
        },
      )
      writeJsonFile.sync(nowConfigPath, nowConfig)

      const args = [
        'deploy',
        '-n',
        'deploymentName',
        ...envVars,
        '-c',
        nowConfigPath,
        '-C',
        '-t',
        process.env.NOW_TOKEN,
      ]

      TerminalUtils.verbose(`Executing now with args:${EOL}\t[${args}]`)
      TerminalUtils.verbose(`Target deploy path:${EOL}\t${deployPath}`)

      TerminalUtils.info(`Deploying ${project.name} to now....`)
      const deploymentUrl = await ChildProcessUtils.exec('now', args, { cwd: deployPath })
      TerminalUtils.verbose(`Now deployment for ${project.name} created at ${deploymentUrl}`)

      TerminalUtils.info(`Setting alias for new deployment of ${project.name} to ${alias}....`)
      const setAlias = async () => {
        await new Promise(resolve => setTimeout(resolve, 5000))
        await ChildProcessUtils.exec('now', ['alias', 'set', deploymentUrl, alias])
      }
      await pRetry(setAlias, { retries: 12 })

      const minScale = R.path(['scale', 'min'], options) || '1'
      const maxScale = R.path(['scale', 'max'], options)
      TerminalUtils.info(
        `Setting the scale factor for new deployment of ${project.name} to ${minScale} ${maxScale ||
          ''}....`,
      )
      const setScale = async () => {
        TerminalUtils.verbose('Trying to set scale factor for deployment')
        await new Promise(resolve => setTimeout(resolve, 5000))
        await ChildProcessUtils.exec(
          'now',
          ['scale', deploymentUrl, minScale, maxScale].filter(x => x != null),
        )
      }
      await pRetry(setScale, { retries: 12 })

      if (options.aliasRules) {
        TerminalUtils.info('Applying alias rules...')
        const aliasRulesPath = tempWrite.sync()
        writeJsonFile.sync(aliasRulesPath, { rules: options.aliasRules })
        await ChildProcessUtils.exec('now', ['alias', alias, '-r', aliasRulesPath])
      }

      if (!options.disableRemovePrevious) {
        // Removes previous deployments 👍
        await ChildProcessUtils.exec('now', ['rm', deploymentName, '--safe'])
      }

      TerminalUtils.success(`${project.name} has been successfully deployed`)
    },
  }
}
<commit_msg>Fixes now plugin.<commit_after>const R = require('ramda')
const { EOL } = require('os')
const pRetry = require('p-retry')
const deepMerge = require('deepmerge')
const tempWrite = require('temp-write')
const writeJsonFile = require('write-json-file')
const TerminalUtils = require('constellate-dev-utils/modules/terminal')
const ChildProcessUtils = require('constellate-dev-utils/modules/childProcess')

module.exports = function nowDeploy(deployPath, options, project) {
  if (process.env.NOW_USERNAME == null) {
    TerminalUtils.error(
      'In order to deploy to "now" you must supply your "now" username via a NOW_USERNAME environment variable.',
    )
    process.exit(1)
  }

  if (process.env.NOW_TOKEN == null) {
    TerminalUtils.error(
      'In order to deploy to "now" you must supply your "now" API token via a NOW_TOKEN environment variable.',
    )
    process.exit(1)
  }

  if (R.isNil(options.alias) || R.isEmpty(options.alias)) {
    TerminalUtils.error('You must supply an "alias" for the "now" deploy plugin.')
    process.exit(1)
  }

  return {
    deploy: async () => {
      try {
        ChildProcessUtils.execSync('now', ['-v'])
      } catch (err) {
        TerminalUtils.error(
          'You need to have the "now" CLI installed on your machine and available on your PATH in order to deploy to now.',
        )
        throw err
      }

      const deploymentName = project.packageName

      const alias = options.alias
      const envVars = options.passThroughEnvVars
        ? options.passThroughEnvVars.reduce(
            (acc, cur) => [...acc, '-e', `${cur}=${process.env[cur]}`],
            [],
          )
        : []

      const nowConfigPath = tempWrite.sync()
      const nowConfig = deepMerge(
        deepMerge(
          // Defaults
          {
            forwardNpm: true,
            public: false,
          },
          // User overrides
          options.nowConfig || {},
        ),
        // Fixed - these must be provided via the env for forced safety
        {
          token: process.env.NOW_TOKEN,
          user: {
            username: process.env.NOW_USERNAME,
          },
        },
      )
      writeJsonFile.sync(nowConfigPath, nowConfig)

      const args = [
        'deploy',
        '-n',
        deploymentName,
        ...envVars,
        '-c',
        nowConfigPath,
        '-C',
        '-t',
        process.env.NOW_TOKEN,
      ]

      TerminalUtils.verbose(`Executing now with args:${EOL}\t[${args}]`)
      TerminalUtils.verbose(`Target deploy path:${EOL}\t${deployPath}`)

      TerminalUtils.info(`Deploying ${project.name} to now....`)
      const deploymentUrl = await ChildProcessUtils.exec('now', args, { cwd: deployPath })
      TerminalUtils.verbose(`Now deployment for ${project.name} created at ${deploymentUrl}`)

      TerminalUtils.info(`Setting alias for new deployment of ${project.name} to ${alias}....`)
      const setAlias = async () => {
        await new Promise(resolve => setTimeout(resolve, 5000))
        await ChildProcessUtils.exec('now', ['alias', 'set', deploymentUrl, alias])
      }
      await pRetry(setAlias, { retries: 12 })

      const minScale = R.path(['scale', 'min'], options) || '1'
      const maxScale = R.path(['scale', 'max'], options)
      TerminalUtils.info(
        `Setting the scale factor for new deployment of ${project.name} to ${minScale} ${maxScale ||
          ''}....`,
      )
      const setScale = async () => {
        TerminalUtils.verbose('Trying to set scale factor for deployment')
        await new Promise(resolve => setTimeout(resolve, 5000))
        await ChildProcessUtils.exec(
          'now',
          ['scale', deploymentUrl, minScale, maxScale].filter(x => x != null),
        )
      }
      await pRetry(setScale, { retries: 12 })

      if (options.aliasRules) {
        TerminalUtils.info('Applying alias rules...')
        const aliasRulesPath = tempWrite.sync()
        writeJsonFile.sync(aliasRulesPath, { rules: options.aliasRules })
        await ChildProcessUtils.exec('now', ['alias', alias, '-r', aliasRulesPath])
      }

      if (!options.disableRemovePrevious) {
        // Removes previous deployments 👍
        await ChildProcessUtils.exec('now', ['rm', deploymentName, '--safe'])
      }

      TerminalUtils.success(`${project.name} has been successfully deployed`)
    },
  }
}
<|endoftext|>


<commit_before>module.exports = function(mongoose) {
  'use strict';

  var maxDocs = -1;

  var initialize = function(options) {
    if (options) {
      maxDocs = options.maxDocs || maxDocs;
    }
  };

  mongoose.Query.prototype.page = function(options, callback) {
    var defaults = {
        from: 0,
        limit: maxDocs,
        page: 1
      },
      query = this,
      wrap = {};

    options = options || defaults;
    options.limit = (options && options.limit ? options.limit : defaults.limit);
    options.page = (options && options.page ? parseFloat(options.page) - 1 : parseFloat(defaults.page) - 1);
    options.from = options.limit * options.page;

    if (maxDocs > 0 && (options.limit > maxDocs || options.limit === 0)) {
      options.limit = maxDocs;
    }

    query.model.count(query._conditions, function(err, total) {
      if (err) {
        return callback(err, null);
      }

      query
        .skip(options.from)
        .limit(options.limit)
        .exec(function(err, results) {
          if (err) {
            return callback(err);
          }

          options.page = options.page + 1;

          var to= options.from + options.limit;

          if (total === 0) {
            options.from = to = 0;
          } else if (to > total) {
            to = total;
          }

          wrap = {
            options: options,
            results: results || [],
            total: total,
            show_prev : options.from <= 1 ? false : true,
            show_next : to >= total ? false : true
          };

          callback(err, wrap);
        });
    });
  };

  return {
    initialize: initialize
  };
};
<commit_msg>parseInt limit attribute<commit_after>module.exports = function(mongoose) {
  'use strict';

  var maxDocs = -1;

  var initialize = function(options) {
    if (options) {
      maxDocs = options.maxDocs || maxDocs;
    }
  };

  mongoose.Query.prototype.page = function(options, callback) {
    var defaults = {
        from: 0,
        limit: maxDocs,
        page: 1
      },
      query = this,
      wrap = {};

    options = options || defaults;
    options.limit = parseInt(options && options.limit ? options.limit : defaults.limit);
    options.page = (options && options.page ? parseFloat(options.page) - 1 : parseFloat(defaults.page) - 1);
    options.from = options.limit * options.page;

    if (maxDocs > 0 && (options.limit > maxDocs || options.limit === 0)) {
      options.limit = maxDocs;
    }

    query.model.count(query._conditions, function(err, total) {
      if (err) {
        return callback(err, null);
      }

      query
        .skip(options.from)
        .limit(options.limit)
        .exec(function(err, results) {
          if (err) {
            return callback(err);
          }

          options.page = options.page + 1;

          var to= options.from + options.limit;

          if (total === 0) {
            options.from = to = 0;
          } else if (to > total) {
            to = total;
          }

          wrap = {
            options: options,
            results: results || [],
            total: total,
            show_prev : options.from <= 1 ? false : true,
            show_next : to >= total ? false : true
          };

          callback(err, wrap);
        });
    });
  };

  return {
    initialize: initialize
  };
};
<|endoftext|>


<commit_before>enyo.kind({
	name: "moon.ListActions",
	classes: "moon-list-actions",
	kind: "enyo.GroupItem",
	published: {
		//* If true, the drawer is expanded, showing this item's contents.
		open: false,
		/**
			If true, the drawer will automatically close when the user
			selects a menu item.
		*/
		autoCollapse: false,
		/**
			List of actions to be displayed.
		*/
		listActions: []
	},
	handlers: {
		onActivate: "optionSelected",
		onSpotlightDown:"spotlightDown",
		onSpotlightUp:"spotlightUp",
		onSpotlightLeft:"spotlightLeft",
		onSpotlightRight:"spotlightRight"
	},
	components:[
		{name:"activator", kind: "moon.Button", classes: "moon-list-actions-activator", spotlight:true, ontap: "expandContract", onSpotlightSelect: "expandContract"},
	    {name: "drawerPopup", kind: "enyo.Popup", classes:"moon-list-actions-drawer-popup", floating: false, autoDismiss: false, components: [
			{name: "drawer", kind: "ListActionDrawer", onStep: "drawerAnimationStep", onEnd: "drawerAnimationEnd", open:false, components: [
        	    {name:"closeButton", kind: "moon.Button", classes:"moon-list-actions-close", ontap:"expandContract", onSpotlightSelect: "expandContract"},
				{classes: "moon-list-actions-client-container", components: [
				    {name:"listActions", kind: "moon.Scroller", classes:"moon-list-actions-scroller", thumb:false, components: [
						{name:"listActionsContainer", classes:"moon-list-actions-container", onRequestScrollIntoView:"scrollIntoView"}
					]}
				]}
			]}
		]}
	],
	create: function() {
		this.inherited(arguments);
		this.openChanged();
		this.listActionsChanged();
	},
	rendered: function() {
		this.inherited(arguments);
		var clientRect = this.parent.node.getBoundingClientRect();
		this.$.listActions.applyStyle('height', clientRect.bottom + "px");
		this.$.listActions.resized();
	},
	listActionsChanged: function() {
		for (option in this.listActions) {
			this.$.listActionsContainer.createComponents([
				{
					classes: "moon-list-actions-menu",
					components: this.listActions[option].components,
					action: this.listActions[option].action ? this.listActions[option].action : ""
				}
			]);
		}
	},
	//* If closed, open drawer and highlight first spottable child
	expandContract: function(inSender, inEvent) {
	    if (inSender.name === inEvent.originator.name) {
	        if (this.disabled) {
    			return true;
    		}
    		if(!this.getOpen()) {
				this.configurePopup();
    			this.setOpen(true);
    			enyo.Spotlight.spot(enyo.Spotlight.getFirstChild(this.$.listActions));
    		} else {
    			this.setOpen(false);
				enyo.Spotlight.spot(this.$.activator);
				this.$.closeButton.undepress(); //why is this needed?
    		}
    		this.resetScrollers();
    		return true;
	    }
	    return false;

	},
	configurePopup: function() {
		var clientRect = this.parent.node.getBoundingClientRect();
		this.$.drawerPopup.applyStyle('width', clientRect.width + "px");
		this.$.drawerPopup.applyStyle('height', clientRect.height + "px");
	    this.$.drawerPopup.setShowing(true);
		this.$.drawerPopup.applyStyle('top', (clientRect.top - this.node.getBoundingClientRect().top)  + "px");
	},
	//* Facade for drawer
	openChanged: function() {
		this.$.drawer.setOpen(this.getOpen());
		this.refresh();
	},
	optionSelected: function(inSender, inEvent) {
		if (inEvent.toggledControl && inEvent.toggledControl.checked) {
			//decorate the event with the control's group action name
			var controls = this.$.listActionsContainer.getControls();
			for (index in controls) {
				if (enyo.Spotlight.Util.isChild(controls[index], inEvent.toggledControl)) {
					inEvent.action = controls[index].action;
					break;
				}
			}

			if (this.autoCollapse) {
				setTimeout(enyo.bind(this, function() {
					this.setOpen(false);
					this.resetScrollers();
					enyo.Spotlight.spot(this.$.activator);
				}), 300);
			}
		}
	},
	resetScrollers: function() {
		//if stacked scroll to the top of the main drawer scroller, otherwise scroll to the top of the individual menu scrollers
		if (this.stacked) {
			this.$.listActions.scrollTo(0,0);
			//reset focus to the first item in the main scroller
			var child = enyo.Spotlight.getFirstChild(this.$.listActions);
			enyo.Spotlight.Decorator.Container.setLastFocusedChild(child, enyo.Spotlight.getFirstChild(child));
		} else {
			var optionGroup = this.$.listActionsContainer.getControls();
			for (var i=0;i<optionGroup.length;i++) {
				var controls = optionGroup[i].getControls();
				for (var j=0;j<controls.length;j++) {
					if (controls[j].kind == "moon.Scroller") {
						controls[j].scrollTo(0,0);
						//reset focus to the first item in each scroller
						enyo.Spotlight.Decorator.Container.setLastFocusedChild(controls[j],
							enyo.Spotlight.getFirstChild(controls[j]));
					}
				}
			}
		}
	},
	/**
		Everytime the drawer animates, bubble the requestScrollIntoView event.
		This makes for a smoother expansion animation when inside of a scroller,
		as the height of the scroller changes with the drawer expansion.
	*/
	drawerAnimationStep: function() {
		this.bubble("onRequestScrollIntoView");
	},
	drawerAnimationEnd: function() {
		//refresh the list option menus when the drawer opens
		if (this.$.drawer.hasNode() && this.open) {
			this.refresh();
		} else {
			this.$.drawerPopup.setShowing(false);
		}
		this.setActive(this.getOpen());
	},
	resizeHandler: function() {
		//don't refresh while animating
		if (!this.$.drawer.$.animator.isAnimating()){
			this.refresh();
			this.$.listActions.render();
		}
	},
	refresh: function() {
		var i, j, controls;
		if (this.$.drawer.hasNode()) {
			var br = this.$.drawer.hasNode().getBoundingClientRect();
			//get the total width of all option menus
			var width = 0;
			var optionGroup = this.$.listActionsContainer.getControls();
			for (i=0;i<optionGroup.length;i++) {
				width += optionGroup[i].hasNode().getBoundingClientRect().width;
			}

			//if the option menus don't all fit horizontally, stack them & allow the main drawer scroller to scroll all of them
			if (width > br.width) {
				for (i=0;i<optionGroup.length;i++) {
					optionGroup[i].applyStyle("display","block");
					controls = optionGroup[i].getControls();
					for (j=0;j<controls.length;j++) {
						if (controls[j].kind == "moon.Scroller") {
							controls[j].applyStyle("max-height", "none");
						}
					}
				}
				this.stacked = true;
			} else {
				//if all the menus fit horizontally, then adjust their individual scrollers so they self scroll
				for (i=0;i<optionGroup.length;i++) {
					optionGroup[i].applyStyle("display","inline-block");
					controls = optionGroup[i].getControls();
					for (j=0;j<controls.length;j++) {
						if (controls[j].kind == "moon.Scroller") {
							//make the scroller the height of the drawer scroller - the items "heading" height - heading bottom margin height
							controls[j].applyStyle("max-height", (this.$.listActions.hasNode().getBoundingClientRect().height
							- controls[0].hasNode().getBoundingClientRect().height
							- parseInt(controls[0].getComputedStyleValue('margin-bottom').replace('px')))
							+ "px");
						}
					}
				}
				this.stacked = false;
			}
		}
	},
	//* When spotlight reaches the left or right of an option menu, prevent user from continuing past the edge.
	spotlightDown: function(inSender, inEvent) {
		var s = enyo.Spotlight.getSiblings(inEvent.originator);
		//prevent navigation past last item, handle stacked & non-stacked cases + close button
		if (!this.stacked && s.selfPosition == (s.siblings.length-1)) {
			return true;
		} else {
			var listActionItems = this.$.listActionsContainer.getControls();
			var last = listActionItems[listActionItems.length-1];
			if (enyo.Spotlight.Util.isChild(last, inEvent.originator) && s.selfPosition == (s.siblings.length-1)) {
				return true;
			}
		}

		if (inEvent.originator == this.$.closeButton) {
			return true;
		}
	},
	spotlightUp: function(inSender, inEvent) {
		var s = enyo.Spotlight.getSiblings(inEvent.originator);
		//if current item is at the top of a menu OR is an expandable picker
		if (inEvent.originator.kind === "moon.ExpandablePicker" || s.selfPosition === 0) {
			//if the menus are not stacked OR the current item is the first in the stacked menu overall, close the drawer & focus the activator
			if (!this.stacked || inEvent.originator == enyo.Spotlight.getFirstChild(this.$.listActionsContainer)) {
				enyo.Spotlight.spot(this.$.closeButton);
				return true;
			}
		}

		if (inEvent.originator == this.$.closeButton) {
			return true;
		}
	},
	//* When spotlight reaches the bottom or top of an option menu, prevent user from continuing downward.
	spotlightLeft: function(inSender, inEvent) {
		if (this.stacked && inEvent.originator != this.$.closeButton && inEvent.originator != this.$.activator) {
			return true;
		} else {
			//if it's coming from the left-most column then stop the left event
			var listActionItems = this.$.listActionsContainer.getControls();
			var first = listActionItems[0];
			if (enyo.Spotlight.Util.isChild(first, inEvent.originator)) {
				return true;
			}
		}
	},
	spotlightRight: function(inSender, inEvent) {
		if (inEvent.originator == this.$.closeButton) {
			return true;
		} else if (this.stacked) {
			enyo.Spotlight.spot(this.$.closeButton);
		} else {
			//if it's coming from the right-most column then focus the close button
			var listActionItems = this.$.listActionsContainer.getControls();
			var last = listActionItems[listActionItems.length-1];
			if (enyo.Spotlight.Util.isChild(last, inEvent.originator)) {
				enyo.Spotlight.spot(this.$.closeButton);
			}
		}
	},
	//when menus are laid out horizontally prevent the onRequestScrollIntoView event from bubbling past the menus container
	//- prevents scroll bouncing for scroller in scroller
	scrollIntoView: function(inSender, inEvent) {
		if (!this.stacked) {
			return true;
		}
	}
});

enyo.kind({
	name: "enyo.ListActionDrawer",
	kind: "enyo.Drawer",
	openChanged: function() {
		this.$.client.show();
		if (this.hasNode()) {
			if (this.$.animator.isAnimating()) {
				this.$.animator.reverse();
			} else {
				var v = this.orient == "v";
				var d = v ? "height" : "width";
				var p = v ? "top" : "left";
				// unfixing the height/width is needed to properly
				// measure the scrollHeight/Width DOM property, but
				// can cause a momentary flash of content on some browsers
				this.applyStyle(d, null);
				var s = this.hasNode()[v ? "scrollHeight" : "scrollWidth"];
				s = this.node.getBoundingClientRect().height;
				if (this.animated) {
					this.$.animator.play({
						startValue: this.open ? s : 0,
						endValue: this.open ? 0 : s,
						dimension: d,
						position: p
					});
				} else {
					// directly run last frame if not animating
					this.animatorEnd();
				}
			}
		} else {
			this.$.client.setShowing(this.open);
		}
	},
	animatorStep: function(inSender) {
		// while the client inside the drawer adjusts its position to move out of the visible area
		var cn = this.$.client.hasNode();
		if (cn) {
			var p = inSender.position;
			var o = (this.open ? inSender.endValue : inSender.startValue);
			cn.style[p] = this.$.client.domStyles[p] = (inSender.value + o) + "px";
		}
		if (this.container) {
			this.container.resized();
		}
	}
});<commit_msg>Adjusted to work with updated moon.Header Enyo-DCO-1.1-Signed-off-by: Steven Feaster steven.feaster@lge.com<commit_after>enyo.kind({
	name: "moon.ListActions",
	classes: "moon-list-actions",
	kind: "enyo.GroupItem",
	published: {
		//* If true, the drawer is expanded, showing this item's contents.
		open: false,
		/**
			If true, the drawer will automatically close when the user
			selects a menu item.
		*/
		autoCollapse: false,
		/**
			List of actions to be displayed.
		*/
		listActions: []
	},
	handlers: {
		onActivate: "optionSelected",
		onSpotlightDown:"spotlightDown",
		onSpotlightUp:"spotlightUp",
		onSpotlightLeft:"spotlightLeft",
		onSpotlightRight:"spotlightRight"
	},
	components:[
		{name:"activator", kind: "moon.Button", classes: "moon-list-actions-activator", spotlight:true, ontap: "expandContract", onSpotlightSelect: "expandContract"},
	    {name: "drawerPopup", kind: "enyo.Popup", classes:"moon-list-actions-drawer-popup", floating: false, autoDismiss: false, components: [
			{name: "drawer", kind: "ListActionDrawer", onStep: "drawerAnimationStep", onEnd: "drawerAnimationEnd", open:false, components: [
        	    {name:"closeButton", kind: "moon.Button", classes:"moon-list-actions-close", ontap:"expandContract", onSpotlightSelect: "expandContract"},
				{classes: "moon-list-actions-client-container", components: [
				    {name:"listActions", kind: "moon.Scroller", classes:"moon-list-actions-scroller", thumb:false, components: [
						{name:"listActionsContainer", classes:"moon-list-actions-container", onRequestScrollIntoView:"scrollIntoView"}
					]}
				]}
			]}
		]}
	],
	create: function() {
		this.inherited(arguments);
		this.openChanged();
		this.listActionsChanged();
	},
	rendered: function() {
		this.inherited(arguments);
		var clientRect = this.parent.node.getBoundingClientRect();
		this.$.listActions.applyStyle('height', clientRect.bottom + "px");
		this.$.listActions.resized();
	},
	listActionsChanged: function() {
		for (option in this.listActions) {
			this.$.listActionsContainer.createComponents([
				{
					classes: "moon-list-actions-menu",
					components: this.listActions[option].components,
					action: this.listActions[option].action ? this.listActions[option].action : ""
				}
			]);
		}
	},
	//* If closed, open drawer and highlight first spottable child
	expandContract: function(inSender, inEvent) {
	    if (inSender.name === inEvent.originator.name) {
	        if (this.disabled) {
    			return true;
    		}
    		if(!this.getOpen()) {
				this.configurePopup();
    			this.setOpen(true);
    			enyo.Spotlight.spot(enyo.Spotlight.getFirstChild(this.$.listActions));
    		} else {
    			this.setOpen(false);
				enyo.Spotlight.spot(this.$.activator);
				this.$.closeButton.undepress(); //why is this needed?
    		}
    		this.resetScrollers();
    		return true;
	    }
	    return false;

	},
	configurePopup: function() {
		var clientRect = this.parent.parent.node.getBoundingClientRect();
		this.$.drawerPopup.applyStyle('width', clientRect.width + "px");
		this.$.drawerPopup.applyStyle('height', clientRect.height + "px");
	    this.$.drawerPopup.setShowing(true);
		this.$.drawerPopup.applyStyle('left', (clientRect.left - this.node.getBoundingClientRect().left)  + "px");
		this.$.drawerPopup.applyStyle('top', (clientRect.top - this.node.getBoundingClientRect().top)  + "px");
	},
	//* Facade for drawer
	openChanged: function() {
		this.$.drawer.setOpen(this.getOpen());
		this.refresh();
	},
	optionSelected: function(inSender, inEvent) {
		if (inEvent.toggledControl && inEvent.toggledControl.checked) {
			//decorate the event with the control's group action name
			var controls = this.$.listActionsContainer.getControls();
			for (index in controls) {
				if (enyo.Spotlight.Util.isChild(controls[index], inEvent.toggledControl)) {
					inEvent.action = controls[index].action;
					break;
				}
			}

			if (this.autoCollapse) {
				setTimeout(enyo.bind(this, function() {
					this.setOpen(false);
					this.resetScrollers();
					enyo.Spotlight.spot(this.$.activator);
				}), 300);
			}
		}
	},
	resetScrollers: function() {
		//if stacked scroll to the top of the main drawer scroller, otherwise scroll to the top of the individual menu scrollers
		if (this.stacked) {
			this.$.listActions.scrollTo(0,0);
			//reset focus to the first item in the main scroller
			var child = enyo.Spotlight.getFirstChild(this.$.listActions);
			enyo.Spotlight.Decorator.Container.setLastFocusedChild(child, enyo.Spotlight.getFirstChild(child));
		} else {
			var optionGroup = this.$.listActionsContainer.getControls();
			for (var i=0;i<optionGroup.length;i++) {
				var controls = optionGroup[i].getControls();
				for (var j=0;j<controls.length;j++) {
					if (controls[j].kind == "moon.Scroller") {
						controls[j].scrollTo(0,0);
						//reset focus to the first item in each scroller
						enyo.Spotlight.Decorator.Container.setLastFocusedChild(controls[j],
							enyo.Spotlight.getFirstChild(controls[j]));
					}
				}
			}
		}
	},
	/**
		Everytime the drawer animates, bubble the requestScrollIntoView event.
		This makes for a smoother expansion animation when inside of a scroller,
		as the height of the scroller changes with the drawer expansion.
	*/
	drawerAnimationStep: function() {
		this.bubble("onRequestScrollIntoView");
	},
	drawerAnimationEnd: function() {
		//refresh the list option menus when the drawer opens
		if (this.$.drawer.hasNode() && this.open) {
			this.refresh();
		} else {
			this.$.drawerPopup.setShowing(false);
		}
		this.setActive(this.getOpen());
	},
	resizeHandler: function() {
		//don't refresh while animating
		if (!this.$.drawer.$.animator.isAnimating()){
			this.refresh();
			this.$.listActions.render();
		}
	},
	refresh: function() {
		var i, j, controls;
		if (this.$.drawer.hasNode()) {
			var br = this.$.drawer.hasNode().getBoundingClientRect();
			//get the total width of all option menus
			var width = 0;
			var optionGroup = this.$.listActionsContainer.getControls();
			for (i=0;i<optionGroup.length;i++) {
				width += optionGroup[i].hasNode().getBoundingClientRect().width;
			}

			//if the option menus don't all fit horizontally, stack them & allow the main drawer scroller to scroll all of them
			if (width > br.width) {
				for (i=0;i<optionGroup.length;i++) {
					optionGroup[i].applyStyle("display","block");
					controls = optionGroup[i].getControls();
					for (j=0;j<controls.length;j++) {
						if (controls[j].kind == "moon.Scroller") {
							controls[j].applyStyle("max-height", "none");
						}
					}
				}
				this.stacked = true;
			} else {
				//if all the menus fit horizontally, then adjust their individual scrollers so they self scroll
				for (i=0;i<optionGroup.length;i++) {
					optionGroup[i].applyStyle("display","inline-block");
					controls = optionGroup[i].getControls();
					for (j=0;j<controls.length;j++) {
						if (controls[j].kind == "moon.Scroller") {
							//make the scroller the height of the drawer scroller - the items "heading" height - heading bottom margin height
							controls[j].applyStyle("max-height", (this.$.listActions.hasNode().getBoundingClientRect().height
							- controls[0].hasNode().getBoundingClientRect().height
							- parseInt(controls[0].getComputedStyleValue('margin-bottom').replace('px')))
							+ "px");
						}
					}
				}
				this.stacked = false;
			}
		}
	},
	//* When spotlight reaches the left or right of an option menu, prevent user from continuing past the edge.
	spotlightDown: function(inSender, inEvent) {
		var s = enyo.Spotlight.getSiblings(inEvent.originator);
		//prevent navigation past last item, handle stacked & non-stacked cases + close button
		if (!this.stacked && s.selfPosition == (s.siblings.length-1)) {
			return true;
		} else {
			var listActionItems = this.$.listActionsContainer.getControls();
			var last = listActionItems[listActionItems.length-1];
			if (enyo.Spotlight.Util.isChild(last, inEvent.originator) && s.selfPosition == (s.siblings.length-1)) {
				return true;
			}
		}

		if (inEvent.originator == this.$.closeButton) {
			return true;
		}
	},
	spotlightUp: function(inSender, inEvent) {
		var s = enyo.Spotlight.getSiblings(inEvent.originator);
		//if current item is at the top of a menu OR is an expandable picker
		if (inEvent.originator.kind === "moon.ExpandablePicker" || s.selfPosition === 0) {
			//if the menus are not stacked OR the current item is the first in the stacked menu overall, close the drawer & focus the activator
			if (!this.stacked || inEvent.originator == enyo.Spotlight.getFirstChild(this.$.listActionsContainer)) {
				enyo.Spotlight.spot(this.$.closeButton);
				return true;
			}
		}

		if (inEvent.originator == this.$.closeButton) {
			return true;
		}
	},
	//* When spotlight reaches the bottom or top of an option menu, prevent user from continuing downward.
	spotlightLeft: function(inSender, inEvent) {
		if (this.stacked && inEvent.originator != this.$.closeButton && inEvent.originator != this.$.activator) {
			return true;
		} else {
			//if it's coming from the left-most column then stop the left event
			var listActionItems = this.$.listActionsContainer.getControls();
			var first = listActionItems[0];
			if (enyo.Spotlight.Util.isChild(first, inEvent.originator)) {
				return true;
			}
		}
	},
	spotlightRight: function(inSender, inEvent) {
		if (inEvent.originator == this.$.closeButton) {
			return true;
		} else if (this.stacked) {
			enyo.Spotlight.spot(this.$.closeButton);
		} else {
			//if it's coming from the right-most column then focus the close button
			var listActionItems = this.$.listActionsContainer.getControls();
			var last = listActionItems[listActionItems.length-1];
			if (enyo.Spotlight.Util.isChild(last, inEvent.originator)) {
				enyo.Spotlight.spot(this.$.closeButton);
			}
		}
	},
	//when menus are laid out horizontally prevent the onRequestScrollIntoView event from bubbling past the menus container
	//- prevents scroll bouncing for scroller in scroller
	scrollIntoView: function(inSender, inEvent) {
		if (!this.stacked) {
			return true;
		}
	}
});

enyo.kind({
	name: "enyo.ListActionDrawer",
	kind: "enyo.Drawer",
	openChanged: function() {
		this.$.client.show();
		if (this.hasNode()) {
			if (this.$.animator.isAnimating()) {
				this.$.animator.reverse();
			} else {
				var v = this.orient == "v";
				var d = v ? "height" : "width";
				var p = v ? "top" : "left";
				// unfixing the height/width is needed to properly
				// measure the scrollHeight/Width DOM property, but
				// can cause a momentary flash of content on some browsers
				this.applyStyle(d, null);
				var s = this.hasNode()[v ? "scrollHeight" : "scrollWidth"];
				s = this.node.getBoundingClientRect().height;
				if (this.animated) {
					this.$.animator.play({
						startValue: this.open ? s : 0,
						endValue: this.open ? 0 : s,
						dimension: d,
						position: p
					});
				} else {
					// directly run last frame if not animating
					this.animatorEnd();
				}
			}
		} else {
			this.$.client.setShowing(this.open);
		}
	},
	animatorStep: function(inSender) {
		// while the client inside the drawer adjusts its position to move out of the visible area
		var cn = this.$.client.hasNode();
		if (cn) {
			var p = inSender.position;
			var o = (this.open ? inSender.endValue : inSender.startValue);
			cn.style[p] = this.$.client.domStyles[p] = (inSender.value + o) + "px";
		}
		if (this.container) {
			this.container.resized();
		}
	}
});<|endoftext|>


<commit_before>/**
 * @venus-library jasmine
 * @venus-include ../../src/functions.js
 * @venus-include ../../src/RegistryInterface.js
 * @venus-include ../../src/Registry.js
 * @venus-include ../../src/QueueInterface.js
 * @venus-include ../../src/Queue.js
 * @venus-include ../../src/EntityInterface.js
 * @venus-include ../../src/Entity.js
 * @venus-include ../../src/Lib/Generator/Interface.js
 * @venus-include ../../src/Lib/Generator/UUID.js
 * @venus-include ../../src/Storage/EngineInterface.js
 * @venus-include ../../src/Storage/RepositoryInterface.js
 * @venus-include ../../src/Storage/Repository.js
 */

describe('storage repository', function () {

    Function.prototype.bind = Function.prototype.bind || function (context) {
        var self = this;

        return function () {
            return self.apply(context, arguments);
        };
    };

    var mockEngine = function () {},
        mockEntity = function (data) {
            Sy.Entity.call(this);
            this.set(data);
        };
    mockEngine.prototype = Object.create(Sy.Storage.EngineInterface.prototype, {

        read: {
            value: function (store, identifier, callback) {
                callback({uuid: identifier});
            }
        },

        create: {
            value: function (store, object, callback) {
                callback(object.uuid);
            }
        },
        update: {
            value: function (store, identifier, object, callback) {
                callback(object);
            }
        },
        remove: {
            value: function (store, identifier, callback) {
                callback(identifier);
            }
        },
        find: {
            value: function (store, args) {
                if (args.limit === 1) {
                    args.callback([{uuid: 'foo', foo: 'bar'}]);
                } else {
                    args.callback([
                        {uuid: 'foo', foo: 'bar'},
                        {uuid: 'baz', foo: 'bar'}
                    ]);
                }
            }
        }

    });
    mockEntity.prototype = Object.create(Sy.Entity.prototype);

    it('should return itself', function () {

        var repo = new Sy.Storage.Repository();

        expect(repo.setQueue(new Sy.QueueInterface())).toEqual(repo);
        expect(repo.setName('foo')).toEqual(repo);
        expect(repo.setGenerator(new Sy.Lib.Generator.Interface())).toEqual(repo);
        expect(repo.setEngine(new Sy.Storage.EngineInterface())).toEqual(repo);
        expect(repo.setEntityKey('uuid')).toEqual(repo);
        expect(repo.setEntityConstructor(Sy.EntityInterface)).toEqual(repo);
        expect(repo.setIndexes([])).toEqual(repo);
        expect(repo.persist(new Sy.EntityInterface())).toEqual(repo);
        expect(repo.remove(new Sy.EntityInterface())).toEqual(repo);

    });

    it('should throw if not handled entity type', function () {

        var repo = new Sy.Storage.Repository();

        repo.setEntityConstructor(Sy.EntityInterface);

        expect(function () {
            repo.persist({});
        }).toThrow('Entity not handled by the repository');

    });

    it('should add the entity to the queue in create state', function () {

        var repo = new Sy.Storage.Repository(),
            queue = new Sy.Queue(),
            entity = new Sy.Entity();

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(Sy.Entity);

        repo.persist(entity);

        expect(entity.get('uuid')).not.toEqual(undefined);
        expect(queue.has('create', entity.get('uuid'))).toBe(true);

        var uuid = entity.get('uuid');

        repo.persist(entity);

        expect(entity.get('uuid')).toEqual(uuid);
        expect(queue.has('create', uuid)).toBe(true);
        expect(queue.has('update', uuid)).toBe(false);

    });

    it('should add the entity to the queue in update state', function () {

        var repo = new Sy.Storage.Repository(),
            queue = new Sy.Queue(),
            entity = new Sy.Entity();

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(Sy.Entity);

        entity.set('uuid', 'bar');

        repo.persist(entity);

        expect(entity.get('uuid')).toEqual('bar');
        expect(queue.has('update', 'bar')).toBe(true);

    });

    it('should add the entity to the queue in remove state', function () {

        var repo = new Sy.Storage.Repository(),
            queue = new Sy.Queue(),
            entity = new Sy.Entity();

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(Sy.Entity);

        repo.persist(entity);

        expect(queue.has('create', entity.get('uuid'))).toBe(true);

        repo.remove(entity);

        expect(queue.has('create', entity.get('uuid'))).toBe(false);

        repo.persist(entity);

        expect(queue.has('update', entity.get('uuid'))).toBe(true);

        repo.remove(entity);

        expect(queue.has('update', entity.get('uuid'))).toBe(false);
        expect(queue.has('remove', entity.get('uuid'))).toBe(true);

    });

    it('should remove elements from queue when flushed', function () {

        runs(function () {
            var repo = new Sy.Storage.Repository(),
                eCreate = new Sy.Entity(),
                eUpdate = new Sy.Entity(),
                eRemove = new Sy.Entity();

            this.queue = new Sy.Queue();

            repo.setQueue(this.queue);
            repo.setGenerator(new Sy.Lib.Generator.UUID());
            repo.setEntityKey('uuid');
            repo.setEntityConstructor(Sy.Entity);
            repo.setEngine(new mockEngine());

            eUpdate.set('uuid', 'foo');
            eRemove.set('uuid', 'bar');

            repo.persist(eCreate);
            repo.persist(eUpdate);
            repo.remove(eRemove);

            repo.flush();
        });

        waits(200);

        runs(function () {
            expect(this.queue.get('create').length).toEqual(0);
            expect(this.queue.get('update').length).toEqual(0);
            expect(this.queue.get('remove').length).toEqual(0);
        });

    });

    it('should return an entity by its uuid', function () {

        var repo = new Sy.Storage.Repository(),
            queue = new Sy.Queue(),
            self = this;

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(mockEntity);
        repo.setEngine(new mockEngine());

        repo.findOneBy({
            index: 'uuid',
            value: 'bar',
            callback: function (entity) {
                expect(entity instanceof mockEntity).toBe(true);
                expect(entity.get('uuid')).toEqual('bar');
            }
        });

    });

    it('should return an entity by its index', function () {

        var repo = new Sy.Storage.Repository(),
            queue = new Sy.Queue(),
            self = this;

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(mockEntity);
        repo.setEngine(new mockEngine());
        repo.setIndexes(['foo']);

        repo.findOneBy({
            index: 'foo',
            value: 'bar',
            callback: function (array) {
                expect(array instanceof Array).toBe(true);
                expect(array.length).toEqual(1);
            }
        });

    });

    it('should return entities by index', function () {

        var repo = new Sy.Storage.Repository(),
            queue = new Sy.Queue(),
            self = this;

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(mockEntity);
        repo.setEngine(new mockEngine());
        repo.setIndexes(['foo']);

        repo.findBy({
            index: 'foo',
            value: 'bar',
            callback: function (array) {
                expect(array instanceof Array).toBe(true);
                expect(array.length).toEqual(2);
            }
        });

    });

});<commit_msg>usage of the queue factory to inject a queue in repository unit tests<commit_after>/**
 * @venus-library jasmine
 * @venus-include ../../src/functions.js
 * @venus-include ../../src/RegistryInterface.js
 * @venus-include ../../src/Registry.js
 * @venus-include ../../src/FactoryInterface.js
 * @venus-include ../../src/RegistryFactory.js
 * @venus-include ../../src/QueueInterface.js
 * @venus-include ../../src/Queue.js
 * @venus-include ../../src/QueueFactory.js
 * @venus-include ../../src/EntityInterface.js
 * @venus-include ../../src/Entity.js
 * @venus-include ../../src/Lib/Generator/Interface.js
 * @venus-include ../../src/Lib/Generator/UUID.js
 * @venus-include ../../src/Storage/EngineInterface.js
 * @venus-include ../../src/Storage/RepositoryInterface.js
 * @venus-include ../../src/Storage/Repository.js
 */

describe('storage repository', function () {

    Function.prototype.bind = Function.prototype.bind || function (context) {
        var self = this;

        return function () {
            return self.apply(context, arguments);
        };
    };

    var mockEngine = function () {},
        mockEntity = function (data) {
            Sy.Entity.call(this);
            this.set(data);
        },
        queueFactory = new Sy.QueueFactory();
    mockEngine.prototype = Object.create(Sy.Storage.EngineInterface.prototype, {

        read: {
            value: function (store, identifier, callback) {
                callback({uuid: identifier});
            }
        },

        create: {
            value: function (store, object, callback) {
                callback(object.uuid);
            }
        },
        update: {
            value: function (store, identifier, object, callback) {
                callback(object);
            }
        },
        remove: {
            value: function (store, identifier, callback) {
                callback(identifier);
            }
        },
        find: {
            value: function (store, args) {
                if (args.limit === 1) {
                    args.callback([{uuid: 'foo', foo: 'bar'}]);
                } else {
                    args.callback([
                        {uuid: 'foo', foo: 'bar'},
                        {uuid: 'baz', foo: 'bar'}
                    ]);
                }
            }
        }

    });
    mockEntity.prototype = Object.create(Sy.Entity.prototype);

    queueFactory.setRegistryFactory(
        new Sy.RegistryFactory()
    );

    it('should return itself', function () {

        var repo = new Sy.Storage.Repository();

        expect(repo.setQueue(new Sy.QueueInterface())).toEqual(repo);
        expect(repo.setName('foo')).toEqual(repo);
        expect(repo.setGenerator(new Sy.Lib.Generator.Interface())).toEqual(repo);
        expect(repo.setEngine(new Sy.Storage.EngineInterface())).toEqual(repo);
        expect(repo.setEntityKey('uuid')).toEqual(repo);
        expect(repo.setEntityConstructor(Sy.EntityInterface)).toEqual(repo);
        expect(repo.setIndexes([])).toEqual(repo);
        expect(repo.persist(new Sy.EntityInterface())).toEqual(repo);
        expect(repo.remove(new Sy.EntityInterface())).toEqual(repo);

    });

    it('should throw if not handled entity type', function () {

        var repo = new Sy.Storage.Repository();

        repo.setEntityConstructor(Sy.EntityInterface);

        expect(function () {
            repo.persist({});
        }).toThrow('Entity not handled by the repository');

    });

    it('should add the entity to the queue in create state', function () {

        var repo = new Sy.Storage.Repository(),
            queue = queueFactory.make(),
            entity = new Sy.Entity();

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(Sy.Entity);

        repo.persist(entity);

        expect(entity.get('uuid')).not.toEqual(undefined);
        expect(queue.has('create', entity.get('uuid'))).toBe(true);

        var uuid = entity.get('uuid');

        repo.persist(entity);

        expect(entity.get('uuid')).toEqual(uuid);
        expect(queue.has('create', uuid)).toBe(true);
        expect(queue.has('update', uuid)).toBe(false);

    });

    it('should add the entity to the queue in update state', function () {

        var repo = new Sy.Storage.Repository(),
            queue = queueFactory.make(),
            entity = new Sy.Entity();

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(Sy.Entity);

        entity.set('uuid', 'bar');

        repo.persist(entity);

        expect(entity.get('uuid')).toEqual('bar');
        expect(queue.has('update', 'bar')).toBe(true);

    });

    it('should add the entity to the queue in remove state', function () {

        var repo = new Sy.Storage.Repository(),
            queue = queueFactory.make(),
            entity = new Sy.Entity();

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(Sy.Entity);

        repo.persist(entity);

        expect(queue.has('create', entity.get('uuid'))).toBe(true);

        repo.remove(entity);

        expect(queue.has('create', entity.get('uuid'))).toBe(false);

        repo.persist(entity);

        expect(queue.has('update', entity.get('uuid'))).toBe(true);

        repo.remove(entity);

        expect(queue.has('update', entity.get('uuid'))).toBe(false);
        expect(queue.has('remove', entity.get('uuid'))).toBe(true);

    });

    it('should remove elements from queue when flushed', function () {

        runs(function () {
            var repo = new Sy.Storage.Repository(),
                eCreate = new Sy.Entity(),
                eUpdate = new Sy.Entity(),
                eRemove = new Sy.Entity();

            this.queue = queueFactory.make();

            repo.setQueue(this.queue);
            repo.setGenerator(new Sy.Lib.Generator.UUID());
            repo.setEntityKey('uuid');
            repo.setEntityConstructor(Sy.Entity);
            repo.setEngine(new mockEngine());

            eUpdate.set('uuid', 'foo');
            eRemove.set('uuid', 'bar');

            repo.persist(eCreate);
            repo.persist(eUpdate);
            repo.remove(eRemove);

            repo.flush();
        });

        waits(200);

        runs(function () {
            expect(this.queue.get('create').length).toEqual(0);
            expect(this.queue.get('update').length).toEqual(0);
            expect(this.queue.get('remove').length).toEqual(0);
        });

    });

    it('should return an entity by its uuid', function () {

        var repo = new Sy.Storage.Repository(),
            queue = queueFactory.make(),
            self = this;

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(mockEntity);
        repo.setEngine(new mockEngine());

        repo.findOneBy({
            index: 'uuid',
            value: 'bar',
            callback: function (entity) {
                expect(entity instanceof mockEntity).toBe(true);
                expect(entity.get('uuid')).toEqual('bar');
            }
        });

    });

    it('should return an entity by its index', function () {

        var repo = new Sy.Storage.Repository(),
            queue = queueFactory.make(),
            self = this;

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(mockEntity);
        repo.setEngine(new mockEngine());
        repo.setIndexes(['foo']);

        repo.findOneBy({
            index: 'foo',
            value: 'bar',
            callback: function (array) {
                expect(array instanceof Array).toBe(true);
                expect(array.length).toEqual(1);
            }
        });

    });

    it('should return entities by index', function () {

        var repo = new Sy.Storage.Repository(),
            queue = queueFactory.make(),
            self = this;

        repo.setQueue(queue);
        repo.setGenerator(new Sy.Lib.Generator.UUID());
        repo.setEntityKey('uuid');
        repo.setEntityConstructor(mockEntity);
        repo.setEngine(new mockEngine());
        repo.setIndexes(['foo']);

        repo.findBy({
            index: 'foo',
            value: 'bar',
            callback: function (array) {
                expect(array instanceof Array).toBe(true);
                expect(array.length).toEqual(2);
            }
        });

    });

});<|endoftext|>