<commit_before>ASTool.SpiderIndexController = Em.ObjectController.extend(ASTool.BaseControllerMixin,
	ASTool.DocumentViewDataSource, ASTool.DocumentViewListener, {

	needs: ['application', 'project_index'],

	navigationLabelBinding: 'content.name',

	documentView: null,

	saving: false,

	newStartUrl: '',

	newExcludePattern: '',

	browseHistory: [],

	pageMap: {},

	loadedPageFp: null,

	autoloadTemplate: null,

	pendingFetches: [],

	itemDefinitions: null,

	hasStartUrl: function() {
		return !this.get('newStartUrl');
	}.property('newStartUrl'),

	hasExcludePattern: function() {
		return !this.get('newExcludePattern');
	}.property('newExcludePattern'),

	hasFollowPattern: function() {
		return !this.get('newFollowPattern');
	}.property('newFollowPattern'),

	displayEditPatterns: function() {
		return this.get('links_to_follow') == 'patterns';
	}.property('links_to_follow'),

	displayNofollow: function() {
		return this.content.get('links_to_follow') != 'none';
	}.property('model.links_to_follow'),

	_showLinks: false,

	showLinks: function(key, show) {
		if (arguments.length > 1) {
            if (show) {
                this.set('_showLinks', true);
            } else {
                this.set('_showLinks', false);
            }
        }
        return  this.get('_showLinks');
	}.property('_showLinks'),

	showItems: true,

	addTemplateDisabled: function() {
		return !this.get('loadedPageFp');
	}.property('loadedPageFp'),

	browseBackDisabled: function() {
		return this.get('browseHistory').length <= 1;
	}.property('browseHistory.@each'),

	reloadDisabled: function() {
		return !this.get('loadedPageFp');
	}.property('loadedPageFp'),

	showItemsDisabled: function() {
		var loadedPageFp = this.get('loadedPageFp');
		if (this.pageMap[loadedPageFp]) {
			return !loadedPageFp ? true : !this.pageMap[loadedPageFp].items.length;
		}
		return true;
	}.property('loadedPageFp'),

	showNoItemsExtracted: function() {
		return this.get('loadedPageFp') && Em.isEmpty(this.get('extractedItems'));
	}.property('loadedPageFp'),

	itemsButtonLabel: function() {
		return this.get('showItems') ? "Hide Items " : "Show Items";
	}.property('showItems'),

	links_to_follow: function(key, follow) {
		// The spider spec only supports 'patterns' or 'none' for the
		// 'links_to_follow' attribute; 'all' is only used for UI purposes.
		var model = this.get('model');
		var retVal = follow;
		if (arguments.length > 1) {
			if (follow != 'patterns') {
				model.get('exclude_patterns').setObjects([]);
				model.get('follow_patterns').setObjects([]);
			}
            model.set('links_to_follow', follow == 'none' ? 'none' : 'patterns');
        } else {
        	retVal = model.get('links_to_follow');
	        if (retVal == 'patterns' &&
	        	Em.isEmpty(model.get('follow_patterns')) &&
				Em.isEmpty(model.get('exclude_patterns'))) {
	        	retVal = 'all';
	        }
    	}
    	return retVal;
	}.property('model.links_to_follow',
	 		   'model.follow_patterns',
	 		   'model.exclude_patterns'),

	extractedItems: function() {
		var items = [];
		var loadedPageFp = this.get('loadedPageFp');
		if (this.pageMap[loadedPageFp]) {
			items = this.pageMap[loadedPageFp].items;
			if (items) {
				items = items.toArray();
			}
		}
		var extractedItems = items.map(function(item) {
			var templatePageId = item['_template'];
			var template = this.get('templates').findBy('page_id', templatePageId);
			var itemDefinition = this.get('itemDefinitions').findBy('name', template.get('scrapes'));
			return ASTool.ExtractedItem.create({ extracted: item, definition: itemDefinition });
		}.bind(this));
		return extractedItems;
	}.property('loadedPageFp'),

	spiderDomains: function() {
		var spiderDomains = new Em.Set();
		this.get('content.start_urls').forEach(function(startUrl) {
			spiderDomains.add(URI.parse(startUrl)['hostname']);
		});
		return spiderDomains;
	}.property('content.start_urls.@each'),

	sprites: function() {
		if (!this.get('loadedPageFp') || !this.get('showLinks')) {
			return [];
		}
		var currentPageData = this.get('pageMap')[this.get('loadedPageFp')];
		var allLinks = $($('#scraped-doc-iframe').contents().get(0).links);
		var followedLinks = currentPageData.links;
		var sprites = [];
		allLinks.each(function(i, link) {
			var followed = followedLinks.indexOf(link.href) >= 0 &&
				this.get('spiderDomains').contains(URI.parse(link.href)['hostname']);
			sprites.pushObject(ASTool.ElementSprite.create({
				element: link,
				hasShadow: false,
				fillColor: followed ? 'rgba(0,255,0,0.3)' : 'rgba(255,0,0,0.3)',
				strokeColor: followed ? 'rgba(0,255,0,0.3)' : 'rgba(255,0,0,0.3)' }));
		}.bind(this));
		return sprites;
	}.property('loadedPageFp', 'showLinks', 'spiderDomains'),

	currentUrl: function() {
		if (!Em.isEmpty(this.get('pendingFetches'))) {
			return 'Fetching page...';
		} else if (this.get('loadedPageFp')) {
			return this.get('pageMap')[this.get('loadedPageFp')].url;;
		} else {
			return 'No page loaded';
		}
	}.property('loadedPageFp', 'pendingFetches.@each'),

	editTemplate: function(template) {
		this.transitionToRoute('template', template);
	},

	fetchPage: function(url, parentFp, skipHistory) {
		this.set('loadedPageFp', null);
		var documentView = this.get('documentView');
		documentView.showLoading();
		var fetchId = ASTool.guid();
		this.get('pendingFetches').pushObject(fetchId);
		this.get('slyd').fetchDocument(url, this.get('content.name'), parentFp).
			then(function(data) {
				if (this.get('pendingFetches').indexOf(fetchId) == -1) {
					// This fetch has been cancelled.
					return;
				}
				if (!data.error) {
					data.url = url;
					if (!skipHistory) {
						this.get('browseHistory').pushObject(data.fp);
					}
					documentView.displayDocument(data.page,
						function(docIframe){
							documentView.hideLoading();
							this.get('pendingFetches').removeObject(fetchId);
							this.get('documentView').reset();
							this.get('documentView').config({ mode: 'browse',
											  listener: this,
											  dataSource: this });
							this.set('loadedPageFp', data.fp);
							this.get('pageMap')[data.fp] = data;
						}.bind(this)
					);
				} else {
					documentView.hideLoading();
					this.get('pendingFetches').removeObject(fetchId);
					documentView.showError(data.error);
				}
			}.bind(this)
		);
	},

	displayPage: function(fp) {
		this.set('loadedPageFp', null);
		var documentView = this.get('documentView');
		documentView.displayDocument(this.get('pageMap')[fp].page,
			function(){
				this.get('documentView').reset();
				this.get('documentView').config({ mode: 'browse',
					listener: this,
					dataSource: this });
				this.set('loadedPageFp', fp);
			}.bind(this));
	},

	addTemplate: function() {
		var page = this.get('pageMap')[this.get('loadedPageFp')];
		var template = ASTool.Template.create(
			{ name: ASTool.shortGuid(),
			  extractors: {},
			  annotated_body: page.page,
			  original_body: page.original,
			  page_id: page.fp,
			  url: page.url });
		this.get('content.templates').pushObject(template);
		this.saveSpider().then(
			function() {
				this.editTemplate(template);
			}.bind(this)
		);
	},

	addStartUrl: function(url) {
		var parsedUrl = URI.parse(url);

		if (!parsedUrl.protocol) {
			parsedUrl.protocol = 'http';
			url = URI.build(parsedUrl);
		}
		this.content.get('start_urls').pushObject(url);
		return url;
	},

	addExcludePattern: function(pattern) {
		this.get('content.exclude_patterns').pushObject(pattern);
	},

	deleteExcludePattern: function(pattern) {
		this.get('content.exclude_patterns').removeObject(pattern);
	},

	addFollowPattern: function(pattern) {
		this.get('content.follow_patterns').pushObject(pattern);
	},

	deleteFollowPattern: function(pattern) {
		this.get('content.follow_patterns').removeObject(pattern);
	},

	autoFetch: function() {
		if (this.get('loadedPageFp') && this.get('showLinks')) {
			this.saveSpider().then(function() {
				this.fetchPage(this.get('pageMap')[this.get('loadedPageFp')].url, null, true);
			}.bind(this));
		}
	}.observes('follow_patterns.@each',
			   'exclude_patterns.@each',
			   'links_to_follow'),

	saveSpider: function() {
		this.set('saving', true);
		return this.get('slyd').saveSpider(this.get('content')).then(function() {
			this.set('saving', false);
		}.bind(this));
	},

	reset: function() {
		this.set('browseHistory', []);
		this.set('pageMap', {});
	}.observes('content'),

	actions: {

		editTemplate: function(template) {
			this.editTemplate(template);
		},

		addTemplate: function() {
			this.addTemplate();
		},

		deleteTemplate: function(template) {
			this.content.get('templates').removeObject(template);
		},

		saveSpider: function() {
			this.saveSpider().then(function() {
				if (this.get('loadedPageFp')) {
					this.fetchPage(
						this.get('pageMap')[this.get('loadedPageFp')].url, null, true);
				}
			}.bind(this));
		},

		fetchPage: function(url) {
			// Cancel all pending fetches.
			this.get('pendingFetches').setObjects([]);
			this.saveSpider().then(function() {
				this.fetchPage(url);
			}.bind(this));
		},

		reload: function() {
			this.fetchPage(this.get('pageMap')[this.get('loadedPageFp')].url, null, true);
		},

		browseBack: function() {
			var history = this.get('browseHistory');
			history.removeAt(history.length - 1);
			var lastPageFp = history.get('lastObject');
			this.displayPage(lastPageFp);
		},

		addStartUrl: function() {
			this.addStartUrl(this.get('newStartUrl'));
			this.set('newStartUrl', '');
		},

		deleteStartUrl: function(url) {
			this.content.get('start_urls').removeObject(url);
		},

		addExcludePattern: function() {
			this.addExcludePattern(this.get('newExcludePattern'));
			this.set('newExcludePattern', '');
		},

		deleteExcludePattern: function(pattern) {
			this.deleteExcludePattern(pattern);
		},

		editExcludePattern: function(oldVal, newVal) {
			this.deleteExcludePattern(oldVal);
			this.addExcludePattern(newVal);
		},

		addFollowPattern: function() {
			this.addFollowPattern(this.get('newFollowPattern'));
			this.set('newFollowPattern', '');
		},

		deleteFollowPattern: function(pattern) {
			this.deleteFollowPattern(pattern);
		},

		editFollowPattern: function(oldVal, newVal) {
			this.deleteFollowPattern(oldVal);
			this.addFollowPattern(newVal);
		},

		toggleShowItems: function() {
			this.set('showItems', !this.get('showItems'));
		},

		rename: function(oldName, newName) {
			var spidersForProject = this.get('controllers.project_index.content');
			newName = this.getUnusedName(newName, spidersForProject);
			this.set('content.name', newName);
			this.get('slyd').renameSpider(oldName, newName).then(
				function() {
					this.replaceRoute('spider', this.get('content'));
				}.bind(this),
				function(reason) {
					this.set('name', oldName);
					alert('The name ' + newName + ' is not a valid spider name.');
				}.bind(this)
			);
		},
	},

	documentActions: {

		linkClicked: function(url) {
			var parsedUrl = URI.parse(url);
			var parsedCurrentUrl = URI.parse(this.get('pageMap')[this.get('loadedPageFp')].url);

			if (!parsedUrl.protocol) {
				if (url.indexOf('/') == 0) {
					parsedCurrentUrl.path = parsedUrl.path.substring(1);
				} else {
					parsedCurrentUrl.path += parsedUrl.path;
				}
				url = URI.build(parsedCurrentUrl);
			}
			// TODO: remove save on fetch.
			this.get('documentView').showLoading();
			this.saveSpider().then(function() {
				this.fetchPage(url, this.get('loadedPageFp'));
			}.bind(this));
		}
	},

	willEnter: function() {
		this.set('loadedPageFp', null);
		this.get('documentView').config({ mode: 'browse',
										  listener: this,
										  dataSource: this });
		this.get('documentView').showSpider();
		var newSpiderSite = this.get('controllers.application.siteWizard')
		if (newSpiderSite) {
			Ember.run.next(this, function() {
				this.fetchPage(this.addStartUrl(newSpiderSite));
				this.set('controllers.application.siteWizard', null);
				this.saveSpider();
			});
		}
		if (this.get('autoloadTemplate')) {
			Ember.run.next(this, function() {
				this.saveSpider().then(function() {
					this.fetchPage(this.get('autoloadTemplate.url'), null, true);
					this.set('autoloadTemplate', null);
				}.bind(this));
			});
		}
	},

	willLeave: function() {
		this.get('pendingFetches').setObjects([]);
		this.get('documentView').hideLoading();
	},
});<commit_msg>- Fixes GH issue #26<commit_after>ASTool.SpiderIndexController = Em.ObjectController.extend(ASTool.BaseControllerMixin,
	ASTool.DocumentViewDataSource, ASTool.DocumentViewListener, {

	needs: ['application', 'project_index'],

	navigationLabelBinding: 'content.name',

	documentView: null,

	saving: false,

	newStartUrl: '',

	newExcludePattern: '',

	browseHistory: [],

	pageMap: {},

	loadedPageFp: null,

	autoloadTemplate: null,

	pendingFetches: [],

	itemDefinitions: null,

	hasStartUrl: function() {
		return !this.get('newStartUrl');
	}.property('newStartUrl'),

	hasExcludePattern: function() {
		return !this.get('newExcludePattern');
	}.property('newExcludePattern'),

	hasFollowPattern: function() {
		return !this.get('newFollowPattern');
	}.property('newFollowPattern'),

	displayEditPatterns: function() {
		return this.get('links_to_follow') == 'patterns';
	}.property('links_to_follow'),

	displayNofollow: function() {
		return this.content.get('links_to_follow') != 'none';
	}.property('model.links_to_follow'),

	_showLinks: false,

	showLinks: function(key, show) {
		if (arguments.length > 1) {
            if (show) {
                this.set('_showLinks', true);
            } else {
                this.set('_showLinks', false);
            }
        }
        return  this.get('_showLinks');
	}.property('_showLinks'),

	showItems: true,

	addTemplateDisabled: function() {
		return !this.get('loadedPageFp');
	}.property('loadedPageFp'),

	browseBackDisabled: function() {
		return this.get('browseHistory').length <= 1;
	}.property('browseHistory.@each'),

	reloadDisabled: function() {
		return !this.get('loadedPageFp');
	}.property('loadedPageFp'),

	showItemsDisabled: function() {
		var loadedPageFp = this.get('loadedPageFp');
		if (this.pageMap[loadedPageFp]) {
			return !loadedPageFp ? true : !this.pageMap[loadedPageFp].items.length;
		}
		return true;
	}.property('loadedPageFp'),

	showNoItemsExtracted: function() {
		return this.get('loadedPageFp') && Em.isEmpty(this.get('extractedItems'));
	}.property('loadedPageFp'),

	itemsButtonLabel: function() {
		return this.get('showItems') ? "Hide Items " : "Show Items";
	}.property('showItems'),

	links_to_follow: function(key, follow) {
		// The spider spec only supports 'patterns' or 'none' for the
		// 'links_to_follow' attribute; 'all' is only used for UI purposes.
		var model = this.get('model');
		var retVal = follow;
		if (arguments.length > 1) {
			if (follow != 'patterns') {
				model.get('exclude_patterns').setObjects([]);
				model.get('follow_patterns').setObjects([]);
			}
            model.set('links_to_follow', follow == 'none' ? 'none' : 'patterns');
        } else {
        	retVal = model.get('links_to_follow');
	        if (retVal == 'patterns' &&
	        	Em.isEmpty(model.get('follow_patterns')) &&
				Em.isEmpty(model.get('exclude_patterns'))) {
	        	retVal = 'all';
	        }
    	}
    	return retVal;
	}.property('model.links_to_follow',
	 		   'model.follow_patterns',
	 		   'model.exclude_patterns'),

	extractedItems: function() {
		var items = [];
		var loadedPageFp = this.get('loadedPageFp');
		if (this.pageMap[loadedPageFp]) {
			items = this.pageMap[loadedPageFp].items;
			if (items) {
				items = items.toArray();
			}
		}
		var extractedItems = items.map(function(item) {
			var templatePageId = item['_template'];
			var template = this.get('templates').findBy('page_id', templatePageId);
			var itemDefinition = this.get('itemDefinitions').findBy('name', template.get('scrapes'));
			return ASTool.ExtractedItem.create({ extracted: item, definition: itemDefinition });
		}.bind(this));
		return extractedItems;
	}.property('loadedPageFp'),

	spiderDomains: function() {
		var spiderDomains = new Em.Set();
		this.get('content.start_urls').forEach(function(startUrl) {
			spiderDomains.add(URI.parse(startUrl)['hostname']);
		});
		return spiderDomains;
	}.property('content.start_urls.@each'),

	sprites: function() {
		if (!this.get('loadedPageFp') || !this.get('showLinks')) {
			return [];
		}
		var currentPageData = this.get('pageMap')[this.get('loadedPageFp')];
		var allLinks = $($('#scraped-doc-iframe').contents().get(0).links);
		var followedLinks = currentPageData.links;
		var sprites = [];
		allLinks.each(function(i, link) {
			var followed = followedLinks.indexOf(link.href) >= 0 &&
				this.get('spiderDomains').contains(URI.parse(link.href)['hostname']);
			sprites.pushObject(ASTool.ElementSprite.create({
				element: link,
				hasShadow: false,
				fillColor: followed ? 'rgba(0,255,0,0.3)' : 'rgba(255,0,0,0.3)',
				strokeColor: followed ? 'rgba(0,255,0,0.3)' : 'rgba(255,0,0,0.3)' }));
		}.bind(this));
		return sprites;
	}.property('loadedPageFp', 'showLinks', 'spiderDomains'),

	currentUrl: function() {
		if (!Em.isEmpty(this.get('pendingFetches'))) {
			return 'Fetching page...';
		} else if (this.get('loadedPageFp')) {
			return this.get('pageMap')[this.get('loadedPageFp')].url;;
		} else {
			return 'No page loaded';
		}
	}.property('loadedPageFp', 'pendingFetches.@each'),

	editTemplate: function(template) {
		this.transitionToRoute('template', template);
	},

	fetchPage: function(url, parentFp, skipHistory) {
		this.set('loadedPageFp', null);
		var documentView = this.get('documentView');
		documentView.showLoading();
		var fetchId = ASTool.guid();
		this.get('pendingFetches').pushObject(fetchId);
		this.get('slyd').fetchDocument(url, this.get('content.name'), parentFp).
			then(function(data) {
				if (this.get('pendingFetches').indexOf(fetchId) == -1) {
					// This fetch has been cancelled.
					return;
				}
				if (!data.error) {
					data.url = url;
					if (!skipHistory) {
						this.get('browseHistory').pushObject(data.fp);
					}
					documentView.displayDocument(data.page,
						function(docIframe){
							documentView.hideLoading();
							this.get('pendingFetches').removeObject(fetchId);
							this.get('documentView').reset();
							this.get('documentView').config({ mode: 'browse',
											  listener: this,
											  dataSource: this });
							this.set('loadedPageFp', data.fp);
							this.get('pageMap')[data.fp] = data;
						}.bind(this)
					);
				} else {
					documentView.hideLoading();
					this.get('pendingFetches').removeObject(fetchId);
					documentView.showError(data.error);
				}
			}.bind(this)
		);
	},

	displayPage: function(fp) {
		this.set('loadedPageFp', null);
		var documentView = this.get('documentView');
		documentView.displayDocument(this.get('pageMap')[fp].page,
			function(){
				this.get('documentView').reset();
				this.get('documentView').config({ mode: 'browse',
					listener: this,
					dataSource: this });
				this.set('loadedPageFp', fp);
			}.bind(this));
	},

	addTemplate: function() {
		var page = this.get('pageMap')[this.get('loadedPageFp')];
		var template = ASTool.Template.create(
			{ name: ASTool.shortGuid(),
			  extractors: {},
			  annotated_body: page.page,
			  original_body: page.original,
			  page_id: page.fp,
			  url: page.url });
		this.get('content.templates').pushObject(template);
		this.saveSpider().then(
			function() {
				this.editTemplate(template);
			}.bind(this)
		);
	},

	addStartUrl: function(url) {
		var parsedUrl = URI.parse(url);

		if (!parsedUrl.protocol) {
			parsedUrl.protocol = 'http';
			url = URI.build(parsedUrl);
		}
		this.content.get('start_urls').pushObject(url);
		return url;
	},

	addExcludePattern: function(pattern) {
		this.get('content.exclude_patterns').pushObject(pattern);
	},

	deleteExcludePattern: function(pattern) {
		this.get('content.exclude_patterns').removeObject(pattern);
	},

	addFollowPattern: function(pattern) {
		this.get('content.follow_patterns').pushObject(pattern);
	},

	deleteFollowPattern: function(pattern) {
		this.get('content.follow_patterns').removeObject(pattern);
	},

	autoFetch: function() {
		if (this.get('loadedPageFp') && this.get('showLinks')) {
			this.saveSpider().then(function() {
				this.fetchPage(this.get('pageMap')[this.get('loadedPageFp')].url, null, true);
			}.bind(this));
		}
	}.observes('follow_patterns.@each',
			   'exclude_patterns.@each',
			   'links_to_follow'),

	saveSpider: function() {
		this.set('saving', true);
		return this.get('slyd').saveSpider(this.get('content')).then(function() {
			this.set('saving', false);
		}.bind(this));
	},

	reset: function() {
		this.set('browseHistory', []);
		this.set('pageMap', {});
	}.observes('content'),

	actions: {

		editTemplate: function(template) {
			this.editTemplate(template);
		},

		addTemplate: function() {
			this.addTemplate();
		},

		deleteTemplate: function(template) {
			this.content.get('templates').removeObject(template);
		},

		saveSpider: function() {
			this.saveSpider().then(function() {
				if (this.get('loadedPageFp')) {
					this.fetchPage(
						this.get('pageMap')[this.get('loadedPageFp')].url, null, true);
				}
			}.bind(this));
		},

		fetchPage: function(url) {
			// Cancel all pending fetches.
			this.get('pendingFetches').setObjects([]);
			this.saveSpider().then(function() {
				this.fetchPage(url);
			}.bind(this));
		},

		reload: function() {
			this.fetchPage(this.get('pageMap')[this.get('loadedPageFp')].url, null, true);
		},

		browseBack: function() {
			var history = this.get('browseHistory');
			history.removeAt(history.length - 1);
			var lastPageFp = history.get('lastObject');
			this.displayPage(lastPageFp);
		},

		addStartUrl: function() {
			this.addStartUrl(this.get('newStartUrl'));
			this.set('newStartUrl', '');
		},

		deleteStartUrl: function(url) {
			this.content.get('start_urls').removeObject(url);
		},

		addExcludePattern: function() {
			this.addExcludePattern(this.get('newExcludePattern'));
			this.set('newExcludePattern', '');
		},

		deleteExcludePattern: function(pattern) {
			this.deleteExcludePattern(pattern);
		},

		editExcludePattern: function(oldVal, newVal) {
			this.deleteExcludePattern(oldVal);
			this.addExcludePattern(newVal);
		},

		addFollowPattern: function() {
			this.addFollowPattern(this.get('newFollowPattern'));
			this.set('newFollowPattern', '');
		},

		deleteFollowPattern: function(pattern) {
			this.deleteFollowPattern(pattern);
		},

		editFollowPattern: function(oldVal, newVal) {
			this.deleteFollowPattern(oldVal);
			this.addFollowPattern(newVal);
		},

		toggleShowItems: function() {
			this.set('showItems', !this.get('showItems'));
		},

		rename: function(oldName, newName) {
			var spidersForProject = this.get('controllers.project_index.content');
			newName = this.getUnusedName(newName, spidersForProject);
			this.set('content.name', newName);
			this.get('slyd').renameSpider(oldName, newName).then(
				function() {
					this.replaceRoute('spider', this.get('content'));
				}.bind(this),
				function(reason) {
					this.set('name', oldName);
					alert('The name ' + newName + ' is not a valid spider name.');
				}.bind(this)
			);
		},
	},

	documentActions: {

		linkClicked: function(url) {
			var parsedUrl = URI.parse(url);
			var parsedCurrentUrl = URI.parse(this.get('pageMap')[this.get('loadedPageFp')].url);

			if (!parsedUrl.protocol) {
				if (url.indexOf('/') == 0) {
					parsedCurrentUrl.path = parsedUrl.path.substring(1);
				} else {
					parsedCurrentUrl.path += parsedUrl.path;
				}
				parsedCurrentUrl.query = parsedUrl.query;
				url = URI.build(parsedCurrentUrl);
			}
			// TODO: remove save on fetch.
			this.get('documentView').showLoading();
			this.saveSpider().then(function() {
				this.fetchPage(url, this.get('loadedPageFp'));
			}.bind(this));
		}
	},

	willEnter: function() {
		this.set('loadedPageFp', null);
		this.get('documentView').config({ mode: 'browse',
										  listener: this,
										  dataSource: this });
		this.get('documentView').showSpider();
		var newSpiderSite = this.get('controllers.application.siteWizard')
		if (newSpiderSite) {
			Ember.run.next(this, function() {
				this.fetchPage(this.addStartUrl(newSpiderSite));
				this.set('controllers.application.siteWizard', null);
				this.saveSpider();
			});
		}
		if (this.get('autoloadTemplate')) {
			Ember.run.next(this, function() {
				this.saveSpider().then(function() {
					this.fetchPage(this.get('autoloadTemplate.url'), null, true);
					this.set('autoloadTemplate', null);
				}.bind(this));
			});
		}
	},

	willLeave: function() {
		this.get('pendingFetches').setObjects([]);
		this.get('documentView').hideLoading();
	},
});<|endoftext|>


<commit_before>SirTrevor.Blocks.Video = (function(){

  var video_regexes = {
    'vimeo': /http[s]?:\/\/(?:www.)?(?:(vimeo).com\/(.*))/,
    'youtube': /http[s]?:\/\/(?:www.)?(?:(youtu(?:be)?).(?:be|com)\/(?:watch\?v=)?([^&]*)(?:&(?:.))?)/,
    'vine': /http[s]?:\/\/(?:www.)?(?:(vine.co\/v\/)([^\/]*))/
  };

  var embed_strings = {
    'youtube': "",
    'vimeo': "",
    'vine': ""
  };

  return SirTrevor.Block.extend({

    type: 'Video',

    droppable: true,
    pastable: true,

    icon_name: 'video',

    loadData: function(data){
      if (data.source === 'vine') {
        this.$editor.addClass('st-block__editor--with-square-media');
      } else {
        this.$editor.addClass('st-block__editor--with-sixteen-by-nine-media');
      }

      var embed_string = embed_strings[data.source]
        .replace('{{protocol}}', window.location.protocol)
        .replace('{{remote_id}}', data.remote_id)
        .replace('{{width}}', this.$editor.width()); // for videos that can't resize automatically like vine

      this.$editor.html(embed_string);
    },

    onContentPasted: function(event){
      // Content pasted. Delegate to the drop parse method
      var input = $(event.target),
          val = input.val();

      // Pass this to the same handler as onDrop
      this.handleDropPaste(val);
    },

    handleDropPaste: function(url){

      if(_.isURI(url))
      {
        _.each(video_regexes, function(element, index) {
          var match = element.exec(url);
          if(match !== null && match[2] !== undefined) {
            console.log(match);
            var data = {};
            data.source = index;
            data.remote_id = match[2];
            // save the data
            this.setAndLoadData(data);
          }
        }.bind(this));
      }

    },

    onDrop: function(transferData){
      var url = transferData.getData('text/plain');
      this.handleDropPaste(url);
    }
  });

})();
<commit_msg>remove console.log. whoops<commit_after>SirTrevor.Blocks.Video = (function(){

  var video_regexes = {
    'vimeo': /http[s]?:\/\/(?:www.)?(?:(vimeo).com\/(.*))/,
    'youtube': /http[s]?:\/\/(?:www.)?(?:(youtu(?:be)?).(?:be|com)\/(?:watch\?v=)?([^&]*)(?:&(?:.))?)/,
    'vine': /http[s]?:\/\/(?:www.)?(?:(vine.co\/v\/)([^\/]*))/
  };

  var embed_strings = {
    'youtube': "",
    'vimeo': "",
    'vine': ""
  };

  return SirTrevor.Block.extend({

    type: 'Video',

    droppable: true,
    pastable: true,

    icon_name: 'video',

    loadData: function(data){
      if (data.source === 'vine') {
        this.$editor.addClass('st-block__editor--with-square-media');
      } else {
        this.$editor.addClass('st-block__editor--with-sixteen-by-nine-media');
      }

      var embed_string = embed_strings[data.source]
        .replace('{{protocol}}', window.location.protocol)
        .replace('{{remote_id}}', data.remote_id)
        .replace('{{width}}', this.$editor.width()); // for videos that can't resize automatically like vine

      this.$editor.html(embed_string);
    },

    onContentPasted: function(event){
      // Content pasted. Delegate to the drop parse method
      var input = $(event.target),
          val = input.val();

      // Pass this to the same handler as onDrop
      this.handleDropPaste(val);
    },

    handleDropPaste: function(url){

      if(_.isURI(url))
      {
        _.each(video_regexes, function(element, index) {
          var match = element.exec(url);
          if(match !== null && match[2] !== undefined) {
            var data = {};
            data.source = index;
            data.remote_id = match[2];
            // save the data
            this.setAndLoadData(data);
          }
        }.bind(this));
      }

    },

    onDrop: function(transferData){
      var url = transferData.getData('text/plain');
      this.handleDropPaste(url);
    }
  });

})();
<|endoftext|>


<commit_before>angular.module('bubbleCloud', [])

.directive('bubbleCloud', function () {

    return {

        restrict: 'E',

        scope: {

            // Either an array of data objects (with groups, labels, and values),
            // or a dictionary of groups of data objects (with labels and values)
            data: '=',

            // Set to 'true' to automatically update when data does
            watch: '@',

            // The attribute containing a data object's value
            valueAttr: '@',

            // The attribute containing a data object's label (optional)
            labelAttr: '@',

            // The attribute containing a data object's group
            // (required if data is an array)
            groupAttr: '@',

            // A function which takes a group name and returns the desired
            // fill color. Optional. The default is d3.scale.category20c()
            fillColorFn: '@',

            // A function which takes a group name and returns the desired
            // label color. Optional. The default is black.
            labelColorFn: '@',

            // Overall diameter of the chart, in pixels
            diameter: '@',

            // A function name to publish into the parent scope, to allow
            // reloading the chart (optional)
            renderChartFn: '=?',

        },

        link: function (scope, element, attrs, ctrl) {

            // Set size of element
            element.css('height', scope.diameter + 'px');

            // Publish renderChart into the parent scope
            scope.renderChartFn = ctrl.renderChart;

            // Watch the data, if desired
            if (scope.watch === 'true') {
                scope.$watch('data', ctrl.renderChart, true);
            }

            // Set up the controller
            ctrl.init(element.find('svg'));

            // Render the chart
            ctrl.renderChart();
        },

        template: '<svg id="mychart"></svg>',

        controller: 'chartController',

    };

})

.controller('chartController', function ($scope) {

    // Return a flattened array of objects of this form:
    //
    //   { group: 'Rock', name: 'Rolling Stones, The', value: 12 }
    //
    function get_flattened_data () {

        var data = $scope.data;

        if (_(data).isArray()) {

            var groupAttr = $scope.groupAttr;
            if (! groupAttr) throw new Error('group-attr is required on <bubble>');

            data = _($scope.data).groupBy(function (item) { return item[groupAttr]; });
        }

        var flattened_data = [];

        _(data).each(function (items, group) {

            _(items).each(function (item) {
                flattened_data.push({ group: group, object: item });
            });

        });

        return flattened_data;

    }

    // Initialize the controller with the given SVG element
    // (wrapped in an array)
    this.init = function (svg_element) {

        var valueAttr = $scope.valueAttr;
        if (! valueAttr) throw new Error('value-attr is required on <bubble>');

        var diameter = parseInt($scope.diameter);

        svg_element
            .attr('width', diameter)
            .attr('height', diameter)
            .attr('class', 'bubble');

        $scope.selection = d3.selectAll(svg_element);

        // Create a pack layout
        $scope.pack_layout = d3.layout.pack()
            .sort(null)
            .value(function (datum) {
                return datum.object[valueAttr];
            })
            .size([diameter, diameter])
            .padding(1.5);

        if ($scope.fillColorFn) {
            var fillColorFn = $scope.$parent[$scope.fillColorFn];
            if (! _(fillColorFn).isFunction())
                throw new Error('fill-color-fn attr must be a function in the parent scope');
            $scope.fill_color_fn = fillColorFn;
        } else {
            $scope.fill_color_fn = d3.scale.category20c();
        }

        if ($scope.labelColorFn) {
            var labelColorFn = $scope.$parent[$scope.labelColorFn];
            if (! _(labelColorFn).isFunction())
                throw new Error('label-color-fn attr must be a function in the parent scope');
            $scope.label_color_fn = labelColorFn;
        } else {
            $scope.label_color_fn = function () { return 'black'; };
        }

        $scope.label_format_fn = d3.format(',d');
    };

    // Get the latest data and render the chart
    this.renderChart = function () {

        // Get the latest data

        var data = { children: get_flattened_data() };

        // Lay out the data

        var node = $scope.selection.selectAll('.node')
            .data($scope.pack_layout.nodes(data).filter(function(d) { return !d.children; }));

        // Handle added nodes

        var enter = node.enter().append('g')
            .attr('class', 'node');
        enter.append('title');
        enter.append('circle');
        enter.append('text')
            .attr('dy', '.3em')
            .style('text-anchor', 'middle')

        // Handle each node

        var valueAttr = $scope.valueAttr;
        var labelAttr = $scope.labelAttr;
        var label_format_fn = $scope.label_format_fn;
        var fill_color_fn = $scope.fill_color_fn;
        var label_color_fn = $scope.label_color_fn;

        node.attr('transform', function (datum) {
            return 'translate(' + datum.x + ',' + datum.y + ')';
        });

        node.select('title')
            .text(function (datum) {
                return datum.object[labelAttr] + ': ' + label_format_fn(datum.object[valueAttr]);
            });

        node.select('circle')
            .attr('r', function (datum) {
                return datum.r;
            })
            .style('fill', function (datum) {
                return fill_color_fn(datum.group);
            });

        node.select('text')
            .style('fill', function (datum) {
                return label_color_fn(datum.group);
            })
            .text(function (datum) {
                var label = datum.object[labelAttr];
                return label ? label.substring(0, datum.r / 3) : '';
            });

        // Handle removed nodes

        node.exit().remove();
    };

})

;
<commit_msg>custom tool tip function<commit_after>angular.module('bubbleCloud', [])

.directive('bubbleCloud', function () {

    return {

        restrict: 'E',

        scope: {

            // Either an array of data objects (with groups, labels, and values),
            // or a dictionary of groups of data objects (with labels and values)
            data: '=',

            // Set to 'true' to automatically update when data does
            watch: '@',

            // The attribute containing a data object's value
            valueAttr: '@',

            // The attribute containing a data object's label (optional)
            labelAttr: '@',

            // The attribute containing a data object's group
            // (required if data is an array)
            groupAttr: '@',

            // A function which takes a group name and returns the desired
            // fill color. Optional. The default is d3.scale.category20c()
            fillColorFn: '@',

            // A function which takes a group name and returns the desired
            // label color. Optional. The default is black.
            labelColorFn: '@',

            // Overall diameter of the chart, in pixels
            diameter: '@',

            // A function name to publish into the parent scope, to allow
            // reloading the chart (optional)
            renderChartFn: '=?',

	    // A function which allows to provide the custom tooltip
	    toolTipFn: '@',

        },

        link: function (scope, element, attrs, ctrl) {

            // Set size of element
            element.css('height', scope.diameter + 'px');

            // Publish renderChart into the parent scope
            scope.renderChartFn = ctrl.renderChart;

            // Watch the data, if desired
            if (scope.watch === 'true') {
                scope.$watch('data', ctrl.renderChart, true);
            }

            // Set up the controller
            ctrl.init(element.find('svg'));

            // Render the chart
            ctrl.renderChart();
        },

        template: '<svg id="mychart"></svg>',

        controller: 'chartController',

    };

})

.controller('chartController', function ($scope) {

    // Return a flattened array of objects of this form:
    //
    //   { group: 'Rock', name: 'Rolling Stones, The', value: 12 }
    //
    function get_flattened_data () {

        var data = $scope.data;

        if (_(data).isArray()) {

            var groupAttr = $scope.groupAttr;
            if (! groupAttr) throw new Error('group-attr is required on <bubble>');

            data = _($scope.data).groupBy(function (item) { return item[groupAttr]; });
        }

        var flattened_data = [];

        _(data).each(function (items, group) {

            _(items).each(function (item) {
                flattened_data.push({ group: group, object: item });
            });

        });

        return flattened_data;

    }
/*
    function tool_tip_function(datum) {
        return datum.object[labelAttr] + ': ' + label_format_fn(datum.object[valueAttr]);
    };
*/
    // Initialize the controller with the given SVG element
    // (wrapped in an array)
    this.init = function (svg_element) {

        var valueAttr = $scope.valueAttr;
        if (! valueAttr) throw new Error('value-attr is required on <bubble>');

        var diameter = parseInt($scope.diameter);

        svg_element
            .attr('width', diameter)
            .attr('height', diameter)
            .attr('class', 'bubble');

        $scope.selection = d3.selectAll(svg_element);

        // Create a pack layout
        $scope.pack_layout = d3.layout.pack()
            .sort(null)
            .value(function (datum) {
                return datum.object[valueAttr];
            })
            .size([diameter, diameter])
            .padding(1.5);

        if ($scope.fillColorFn) {
            var fillColorFn = $scope.$parent[$scope.fillColorFn];
            if (! _(fillColorFn).isFunction())
                throw new Error('fill-color-fn attr must be a function in the parent scope');
            $scope.fill_color_fn = fillColorFn;
        } else {
            $scope.fill_color_fn = d3.scale.category20c();
        }

        if ($scope.labelColorFn) {
            var labelColorFn = $scope.$parent[$scope.labelColorFn];
            if (! _(labelColorFn).isFunction())
                throw new Error('label-color-fn attr must be a function in the parent scope');
            $scope.label_color_fn = labelColorFn;
        } else {
            $scope.label_color_fn = function () { return 'black'; };
        }

        if ($scope.toolTipFn) {
            var toolTipFn = $scope.$parent[$scope.toolTipFn];
            if (! _(toolTipFn).isFunction())
                throw new Error('tool-tip-fn attr must be a function in the parent scope');
            $scope.tool_tip_fn = toolTipFn;
        }

        $scope.label_format_fn = d3.format(',d');
    };

    // Get the latest data and render the chart
    this.renderChart = function () {

        // Get the latest data

        var data = { children: get_flattened_data() };

        // Lay out the data

        var node = $scope.selection.selectAll('.node')
            .data($scope.pack_layout.nodes(data).filter(function(d) { return !d.children; }));

        // Handle added nodes

        var enter = node.enter().append('g')
            .attr('class', 'node');
        enter.append('title');
        enter.append('circle');
        enter.append('text')
            .attr('dy', '.3em')
            .style('text-anchor', 'middle')

        // Handle each node

        var valueAttr = $scope.valueAttr;
        var labelAttr = $scope.labelAttr;
        var label_format_fn = $scope.label_format_fn;
        var fill_color_fn = $scope.fill_color_fn;
        var label_color_fn = $scope.label_color_fn;
        var tool_tip_fn = $scope.tool_tip_fn;

        node.attr('transform', function (datum) {
            return 'translate(' + datum.x + ',' + datum.y + ')';
        });

        node.select('title')
            .text(function (datum) {
                //return datum.object[labelAttr] + ': ' + label_format_fn(datum.object[valueAttr]);
                return tool_tip_fn(datum);
            });

        node.select('circle')
            .attr('r', function (datum) {
                return datum.r;
            })
            .style('fill', function (datum) {
                return fill_color_fn(datum.group);
            });

        node.select('text')
            .style('fill', function (datum) {
                return label_color_fn(datum.group);
            })
            .text(function (datum) {
                var label = datum.object[labelAttr];
                return label ? label.substring(0, datum.r / 3) : '';
            });

        // Handle removed nodes

        node.exit().remove();
    };

})

;
<|endoftext|>


<commit_before>var sprites = [];
var arrows, item, bucket, emitter, cloud, pipe1, pipe2, pipe3, pipe4;
var rainDirection = 1;
var rainSaved = 0;
var velocity = 10;
var rainAmount = 1;
var rainLifespan = 1000;
var MaxTime = 20;
var rainDrop;
var GameScoreText,TimerText;
var splashEmitter;

function bucketCollide(bucket, particle) {
  rainSaved += 1;
  particle.kill();
  GameScoreText.text = scoreText();
  splashEmitter.x = bucket.x + 60;
  splashEmitter.y = bucket.y;
  splashEmitter.explode(500, 20);
  console.log(particle);
}

function scoreText() {
  return "Rain Saved: " + rainSaved
}

function drawScore() {
  GameScoreText = game.add.text(10, 35, scoreText());
  GameScoreText.fill = "#FFF";
  GameScoreText.fontSize = 14;
}

function drawTimer() {
  TimerText = game.add.text(750, 35, MaxTime);
  TimerText.fill = "yellow";
  TimerText.fontSize = 16;
}

function drawGameOver() {
  game.physics.arcade.isPaused = true;
  GameScoreText.fontSize = 20;
  var text = game.add.text(game.world.centerX, game.world.centerY, "GAME OVER");
  text.anchor.set(0.5);
  text.fontSize = 80;
  text.fill = "#ffffff";
  emitter.on = false;
}

function update() {
  if (game.physics.arcade.isPaused) {
    return;
  }

  var timeLeft = MaxTime - Math.floor(game.time.now / 1000);
  if (timeLeft <= 5) {
    TimerText.fill = "red";
  }
  if (timeLeft <= 0) {
    drawGameOver();
  }
  TimerText.text = timeLeft;

  if (arrows.right.isDown) {
    bucket.x += velocity;
  }
  if (arrows.left.isDown) {
    bucket.x -= velocity;
  }

  game.physics.arcade.collide(emitter, bucket, bucketCollide);

  if (emitter.x + 50 > game.world.width) {
    rainDirection = -1;
  } else if (emitter.x + 50 < 0) {
    rainDirection = 1;
  }
  // emitter.x = emitter.x + rainDirection;
  // cloud.x += rainDirection;
  // rainDrop
}

function preload() {
  game.load.image('bucket', 'img/bucket.png');
  game.load.image("cloud", "img/cloud.png");
  game.load.image("pipe", "img/pipe.png");
  var rainDrop = makeRainDrop(20);
  emitter = game.add.emitter(game.world.centerX, 30, rainAmount);
  emitter.width = 500;
  emitter.makeParticles(rainDrop, 0, emitter.maxParticles, true, false);
  emitter.gravity = 800;
  emitter.minParticleScale = 1;
  emitter.maxParticleScale = 1;
  emitter.setXSpeed(0,5);
  emitter.setYSpeed(350, 500);
  emitter.start(false, rainLifespan, 10);

  splashEmitter = game.add.emitter(0, 0, 40);
  splashEmitter.width = 50;
  splashEmitter.makeParticles(makeRainDrop(10), 0);
  splashEmitter.minParticleScale = 0.5;
  splashEmitter.maxParticleScale = 1;
  splashEmitter.setYSpeed(-250, 0);
  splashEmitter.setXSpeed(-50, 50);
  splashEmitter.gravity = 400;

  arrows = game.input.keyboard.createCursorKeys();
}

function makePipes() {
  var pipeGroup = game.add.group();
  pipe1 = pipeGroup.create(5, 0, "pipe");
  // pipe = game.add.sprite(0, 0, "pipe");
  pipe2 = pipeGroup.create(pipe1.x + pipe1.width - 3, 0, "pipe");
  pipe3 = pipeGroup.create(pipe2.x + pipe2.width - 3, 0, "pipe");
  pipe4 = pipeGroup.create(pipe3.x + pipe3.width - 3, 0, "pipe");
}

function create() {
  game.physics.startSystem(Phaser.Physics.ARCADE);
  bucket = game.add.sprite(30, 480, 'bucket')
  // cloud = game.add.sprite(emitter.x - 100, 0, "cloud");
  makePipes();
  game.physics.arcade.enable(bucket);
  // bucket.body.bounce.set(0);
  bucket.body.collideWorldBounds = true;
  bucket.body.immovable = true;
  game.physics.arcade.enable(emitter);
  drawScore();
  drawTimer();
  // emitter.body.bounce.set(0);
  // emitter.body.mass = 0;
  // rainDrop.body.mass = 0;

  // emitter.body.collideWorldBounds = true;
}

function makeRainDrop(radius) {
  var graphics = game.add.graphics(10, 10);
  graphics.moveTo(10, 10);
  // graphics.lineStyle(1, 0xffffff, 0);
  // graphics.lineTo(width, 10);
  graphics.beginFill(0xB6DAFF);
  graphics.drawCircle(50,50,radius);
  graphics.endFill();
  txt = graphics.generateTexture();
  graphics.destroy();
  return txt;
  // var s = game.add.sprite(x, y, txt);
  // game.physics.arcade.enable(s);
  // s.body.immovable = true;
  // return s;
}
<commit_msg>Cleanup<commit_after>var sprites = [];
var arrows, item, bucket, emitter, cloud, pipe1, pipe2, pipe3, pipe4;
var rainDirection = 1;
var rainSaved = 0;
var velocity = 12;
var rainAmount = 1;
var rainLifespan = 1500;
var GameTime = 20;
var rainDrop;
var GameScoreText,TimerText;
var splashEmitter;

var MaxSplashParticles = 40;
var SingleSplashParticles = 20;
var SplashLifespan = 500;
var SplashYRange = 0;
var SplashXRange = 0;

var LeakMaxSpeed = 500;

function tick() {
  if (arrows.right.isDown) {
    bucket.x += velocity;
  }
  if (arrows.left.isDown) {
    bucket.x -= velocity;
  }
}

function bucketCollide(bucket, particle) {
  rainSaved += 1;
  particle.kill();
  GameScoreText.text = scoreText();
  splashEmitter.x = bucket.x + 60;
  splashEmitter.y = bucket.y;
  splashEmitter.explode(SplashLifespan, SingleSplashParticles);
}

function scoreText() {
  return "Water Saved: " + rainSaved
}

function drawScore() {
  GameScoreText = game.add.text(10, 35, scoreText());
  GameScoreText.fill = "#FFF";
  GameScoreText.fontSize = 14;
}

function drawTimer() {
  TimerText = game.add.text(750, 35, GameTime);
  TimerText.fill = "yellow";
  TimerText.fontSize = 16;
}

function drawGameOver() {
  game.physics.arcade.isPaused = true;
  GameScoreText.fontSize = 20;
  var text = game.add.text(game.world.centerX, game.world.centerY, "GAME OVER");
  text.anchor.set(0.5);
  text.fontSize = 80;
  text.fill = "#ffffff";
  emitter.on = false;
}

function update() {
  if (game.physics.arcade.isPaused) {
    return;
  }

  var timeLeft = GameTime - Math.floor(game.time.now / 1000);
  if (timeLeft <= 5) {
    TimerText.fill = "red";
  }
  if (timeLeft <= 0) {
    drawGameOver();
  }
  TimerText.text = timeLeft;

  tick();
  game.physics.arcade.collide(emitter, bucket, bucketCollide);
}

function preload() {
  game.load.image('bucket', 'img/bucket.png');
  game.load.image("pipe", "img/pipe.png");
  var rainDrop = makeRainDrop(20);
  emitter = game.add.emitter(game.world.centerX, 30, rainAmount);
  emitter.width = 400;
  emitter.makeParticles(rainDrop, 0, emitter.maxParticles, true, false);
  emitter.gravity = 800;
  emitter.minParticleScale = 1;
  emitter.maxParticleScale = 1;
  emitter.setXSpeed(0,5);
  emitter.setYSpeed(350, LeakMaxSpeed);
  emitter.start(false, rainLifespan, 10);

  splashEmitter = game.add.emitter(0, 0, 40);
  splashEmitter.width = 50;
  splashEmitter.makeParticles(makeRainDrop(10), 0);
  splashEmitter.minParticleScale = 0.4;
  splashEmitter.maxParticleScale = 1;
  splashEmitter.setYSpeed(-250 - SplashYRange, 0 + SplashYRange);
  splashEmitter.setXSpeed(-50 - SplashXRange, 50 + SplashXRange);
  splashEmitter.gravity = 400;

  arrows = game.input.keyboard.createCursorKeys();
}

function makePipes() {
  var pipeGroup = game.add.group();
  pipe1 = pipeGroup.create(5, 0, "pipe");
  pipe2 = pipeGroup.create(pipe1.x + pipe1.width - 3, 0, "pipe");
  pipe3 = pipeGroup.create(pipe2.x + pipe2.width - 3, 0, "pipe");
  pipe4 = pipeGroup.create(pipe3.x + pipe3.width - 3, 0, "pipe");
}

function create() {
  game.physics.startSystem(Phaser.Physics.ARCADE);
  bucket = game.add.sprite(30, 480, 'bucket')
  makePipes();
  game.physics.arcade.enable(bucket);
  bucket.body.collideWorldBounds = true;
  bucket.body.immovable = true;
  game.physics.arcade.enable(emitter);
  drawScore();
  drawTimer();
}

function makeRainDrop(radius) {
  var graphics = game.add.graphics(10, 10);
  graphics.moveTo(10, 10);
  graphics.beginFill(0xB6DAFF);
  graphics.drawCircle(10,10,radius);
  graphics.endFill();
  txt = graphics.generateTexture();
  graphics.destroy();
  return txt;
}
<|endoftext|>


<commit_before>var _ = require("lodash");

var wrapInOr = function(states){
  if(_.size(states) === 1){
    return _.head(states);
  }
  return ["or", _.head(states), wrapInOr(_.tail(states))];
};

var toLispArgs = function(ast, traverse){
  return _.map(ast.args, traverse);
};

var event_ops = {
  "or": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var stm = {};

      var a = evalEELisp(args[0], start, end);
      var b = evalEELisp(args[1], start, end);

      _.each(_.uniq(_.keys(a).concat(_.keys(b))), function(state){
        if(_.has(a, state)){
          _.each(a[state], function(transition){
            if(!_.has(stm, state)){
              stm[state] = [];
            }
            stm[state].push(transition);
          });
        }else{
          //
        }
        if(_.has(b, state)){
          _.each(b[state], function(transition){
            if(!_.has(stm, state)){
              stm[state] = [];
            }
            stm[state].push(transition);
          });
        }else{
          _.each(b["start"], function(transition){
            if(!_.has(stm, state)){
              stm[state] = [];
            }
            stm[state].push(transition);
          });
        }
      });
      return stm;
    }
  },
  "and": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var a = evalEELisp(args[0], "aaa-START", "aaa-END");
      var b = evalEELisp(args[1], "bbb-START", "bbb-END");

      var s1 = newState();
      var s2 = newState();

      var stm = {};
      stm[start] = [];
      stm[s1] = [];
      stm[s2] = [];

      _.each(a["aaa-START"], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === "aaa-END"){
          stm[start].push([condition, s1]);
          stm[s2].push([condition, end]);
        }else if(next_state === "aaa-START"){
          stm[s2].push([condition, s2]);
        }
      });

      _.each(b["bbb-START"], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === "bbb-END"){
          stm[start].push([condition, s2]);
          stm[s1].push([condition, end]);
        }else if(next_state === "bbb-START"){
          stm[s1].push([condition, s1]);
        }
      });

      return stm;
    }
  }
};

module.exports = function(ast, comp, e){
  if(ast.kind !== "when"){
    throw new Error("RuleSelect.kind not supported: " + ast.kind);
  }
  var ee_id = 0;
  var graph = {};
  var eventexprs = {};

  var onEE = function(ast){
    var domain = ast.event_domain.value;
    var type = ast.event_type.value;
    var id = "expr_" + (ee_id++);

    _.set(graph, [domain, type, id], true);

    eventexprs[id] = comp(ast);
    return id;
  };

  var traverse = function(ast){
    if(ast.type === "EventExpression"){
      return onEE(ast);
    }else if(ast.type === "EventOperator"){
      if(_.has(event_ops, ast.op)){
        return [ast.op].concat(event_ops[ast.op].toLispArgs(ast, traverse));
      }
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
    throw new Error("invalid event ast node: " + ast.type);
  };

  var newState = (function(){
    var i = 0;
    return function(){
      var id = "state_" + i;
      i++;
      return id;
    };
  }());

  var evalEELisp = function(lisp, start, end){
    if(_.isString(lisp)){
      var stm = {};
      stm[start] = [
        [lisp, end]
      ];
      return stm;
    }
    if(_.has(event_ops, lisp[0])){
      return event_ops[lisp[0]].mkStateMachine(start, end, lisp.slice(1), newState, evalEELisp);
    }else{
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
  };

  var lisp = traverse(ast.event);
  var state_machine = evalEELisp(lisp, "start", "end");

  //add all the loop-back conditions
  _.each(state_machine, function(arr, key){
    var away_paths = _.uniq(_.compact(_.map(arr, function(transition){
      var condition = transition[0];
      var next_state = transition[1];
      if(!_.isString(condition) && (next_state === key)){
        return;//ignore this
      }
      return condition;
    })));

    state_machine[key].push([
        [
          "not",
          wrapInOr(away_paths)
        ],
        key
    ]);
  });

  return e("obj", {
    graph: e("json", graph),
    eventexprs: e("obj", eventexprs),
    state_machine: e("json", state_machine)
  });
};
<commit_msg>cleanup<commit_after>var _ = require("lodash");

var wrapInOr = function(states){
  if(_.size(states) === 1){
    return _.head(states);
  }
  return ["or", _.head(states), wrapInOr(_.tail(states))];
};

var toLispArgs = function(ast, traverse){
  return _.map(ast.args, traverse);
};

var event_ops = {
  "or": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var stm = {};

      var stmPush = function(state, transition){
        if(!_.has(stm, state)){
          stm[state] = [];
        }
        stm[state].push(transition);
      };

      var a = evalEELisp(args[0], start, end);
      var b = evalEELisp(args[1], start, end);

      _.each(_.uniq(_.keys(a).concat(_.keys(b))), function(state){
        var iter = _.partial(stmPush, state);
        if(_.has(a, state)){
          _.each(a[state], iter);
        }else{
          _.each(a["start"], iter);
        }
        if(_.has(b, state)){
          _.each(b[state], iter);
        }else{
          _.each(b["start"], iter);
        }
      });
      return stm;
    }
  },
  "and": {
    toLispArgs: toLispArgs,
    mkStateMachine: function(start, end, args, newState, evalEELisp){
      var a = evalEELisp(args[0], "aaa-START", "aaa-END");
      var b = evalEELisp(args[1], "bbb-START", "bbb-END");

      var s1 = newState();
      var s2 = newState();

      var stm = {};
      stm[start] = [];
      stm[s1] = [];
      stm[s2] = [];

      _.each(a["aaa-START"], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === "aaa-END"){
          stm[start].push([condition, s1]);
          stm[s2].push([condition, end]);
        }else if(next_state === "aaa-START"){
          stm[s2].push([condition, s2]);
        }
      });

      _.each(b["bbb-START"], function(transition){
        var condition = transition[0];
        var next_state = transition[1];
        if(next_state === "bbb-END"){
          stm[start].push([condition, s2]);
          stm[s1].push([condition, end]);
        }else if(next_state === "bbb-START"){
          stm[s1].push([condition, s1]);
        }
      });

      return stm;
    }
  }
};

module.exports = function(ast, comp, e){
  if(ast.kind !== "when"){
    throw new Error("RuleSelect.kind not supported: " + ast.kind);
  }
  var ee_id = 0;
  var graph = {};
  var eventexprs = {};

  var onEE = function(ast){
    var domain = ast.event_domain.value;
    var type = ast.event_type.value;
    var id = "expr_" + (ee_id++);

    _.set(graph, [domain, type, id], true);

    eventexprs[id] = comp(ast);
    return id;
  };

  var traverse = function(ast){
    if(ast.type === "EventExpression"){
      return onEE(ast);
    }else if(ast.type === "EventOperator"){
      if(_.has(event_ops, ast.op)){
        return [ast.op].concat(event_ops[ast.op].toLispArgs(ast, traverse));
      }
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
    throw new Error("invalid event ast node: " + ast.type);
  };

  var newState = (function(){
    var i = 0;
    return function(){
      var id = "state_" + i;
      i++;
      return id;
    };
  }());

  var evalEELisp = function(lisp, start, end){
    if(_.isString(lisp)){
      var stm = {};
      stm[start] = [
        [lisp, end]
      ];
      return stm;
    }
    if(_.has(event_ops, lisp[0])){
      return event_ops[lisp[0]].mkStateMachine(start, end, lisp.slice(1), newState, evalEELisp);
    }else{
      throw new Error("EventOperator.op not supported: " + ast.op);
    }
  };

  var lisp = traverse(ast.event);
  var state_machine = evalEELisp(lisp, "start", "end");

  //add all the loop-back conditions
  _.each(state_machine, function(arr, key){
    var away_paths = _.uniq(_.compact(_.map(arr, function(transition){
      var condition = transition[0];
      var next_state = transition[1];
      if(!_.isString(condition) && (next_state === key)){
        return;//ignore this
      }
      return condition;
    })));

    state_machine[key].push([
        [
          "not",
          wrapInOr(away_paths)
        ],
        key
    ]);
  });

  return e("obj", {
    graph: e("json", graph),
    eventexprs: e("obj", eventexprs),
    state_machine: e("json", state_machine)
  });
};
<|endoftext|>


<commit_before>import m from 'mithril';
import h from '../h';
import adminItem from './admin-item';

const adminList = {
    controller(args) {
        const list = args.vm.list;

        if (!list.collection().length && list.firstPage) {
            list.firstPage().then(null, (serverError) => {
                args.vm.error(serverError.message);
            });
        }
    },
    view(ctrl, args) {
        const list = args.vm.list,
            error = args.vm.error,
            label = args.label || '';

        return m('.w-section.section', [
            m('.w-container',
                error() ?
                m('.card.card-error.u-radius.fontweight-bold', error()) : [
                    m('.w-row.u-marginbottom-20', [
                        m('.w-col.w-col-9', [
                            m('.fontsize-base',
                                list.isLoading() ?
                              `Carregando ${label.toLowerCase()}...` : [
                                  m('.w-row', [
                                      m('.w-col.w-col-3', [
                                          m('.fontweight-semibold', list.total()),
                                          ` ${label.toLowerCase()} encontrados`
                                      ]),
                                      (args.vm && args.vm.hasInputAction ? m('.w-col-9.w-col', args.vm.inputActions()) : '')
                                  ])
                              ]
                            )
                        ])
                    ]),
                    m('#admin-contributions-list.w-container', [
                        list.collection().map(item => m.component(adminItem, {
                            listItem: args.listItem,
                            listDetail: args.listDetail,
                            listWrapper: args.vm,
                            item,
                            key: item.id
                        })),
                        m('.w-section.section', [
                            m('.w-container', [
                                m('.w-row', [
                                    m('.w-col.w-col-2.w-col-push-5', [
                                        list.isLoading() ?
                                        h.loader() :
                                        m('button#load-more.btn.btn-medium.btn-terciary', {
                                            onclick: list.nextPage
                                        }, 'Carregar mais'),
                                    ])
                                ])
                            ])
                        ])
                    ])
                ]
            )
        ]);
    }
};

export default adminList;
<commit_msg>adminItem should be replaceable<commit_after>import m from 'mithril';
import h from '../h';
import adminItem from './admin-item';

const adminList = {
    controller(args) {
        const list = args.vm.list;

        if (!list.collection().length && list.firstPage) {
            list.firstPage().then(null, (serverError) => {
                args.vm.error(serverError.message);
            });
        }
    },
    view(ctrl, args) {
        const list = args.vm.list,
              error = args.vm.error,
              label = args.label || '',
              itemComponent = args.itemComponent || adminItem;

        return m('.w-section.section', [
            m('.w-container',
                error() ?
                m('.card.card-error.u-radius.fontweight-bold', error()) : [
                    m('.w-row.u-marginbottom-20', [
                        m('.w-col.w-col-9', [
                            m('.fontsize-base',
                                list.isLoading() ?
                              `Carregando ${label.toLowerCase()}...` : [
                                  m('.w-row', [
                                      m('.w-col.w-col-3', [
                                          m('.fontweight-semibold', list.total()),
                                          ` ${label.toLowerCase()} encontrados`
                                      ]),
                                      (args.vm && args.vm.hasInputAction ? m('.w-col-9.w-col', args.vm.inputActions()) : '')
                                  ])
                              ]
                            )
                        ])
                    ]),
                    m('#admin-contributions-list.w-container', [
                        list.collection().map(item => m.component(itemComponent, {
                            listItem: args.listItem,
                            listDetail: args.listDetail,
                            listWrapper: args.vm,
                            item,
                            key: item.id
                        })),
                        m('.w-section.section', [
                            m('.w-container', [
                                m('.w-row', [
                                    m('.w-col.w-col-2.w-col-push-5', [
                                        list.isLoading() ?
                                        h.loader() :
                                        m('button#load-more.btn.btn-medium.btn-terciary', {
                                            onclick: list.nextPage
                                        }, 'Carregar mais'),
                                    ])
                                ])
                            ])
                        ])
                    ])
                ]
            )
        ]);
    }
};

export default adminList;
<|endoftext|>


<commit_before>"use strict";

// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawCircle(ctx, x, y, radius);

// Export only these methods
var drawCurve, drawConstruction, drawBezierNodes;

(function () {

    function calcBspline(t, nodes, knots) {
        var k = 0;
        for (var l = 3; l < (knots.length - 2); l++) {
            if (knots[l].val() >= t) break;
            k++;
        }
        var k1 = knots[k + 0].val();
        var k2 = knots[k + 1].val();
        var k3 = knots[k + 2].val();
        var k4 = knots[k + 3].val();
        var k5 = knots[k + 4].val();
        var k6 = knots[k + 5].val();

        var p1 = nodes[k + 0].pos();
        var p2 = nodes[k + 1].pos();
        var p3 = nodes[k + 2].pos();
        var p4 = nodes[k + 3].pos();

        var p23t = p1.multiply((k4 - t) / (k4 - k1)).add(p2.multiply((t - k1) / (k4 - k1)));
        var p34t = p2.multiply((k5 - t) / (k5 - k2)).add(p3.multiply((t - k2) / (k5 - k2)));
        var p45t = p3.multiply((k6 - t) / (k6 - k3)).add(p4.multiply((t - k3) / (k6 - k3)));

        var p3tt = p23t.multiply((k4 - t) / (k4 - k2)).add(p34t.multiply((t - k2) / (k4 - k2)));
        var p4tt = p34t.multiply((k5 - t) / (k5 - k3)).add(p45t.multiply((t - k3) / (k5 - k3)));

        return p3tt.multiply((k4 - t) / (k4 - k3)).add(p4tt.multiply((t - k3) / (k4 - k3)));
    }

    drawCurve = function (ctx, curve) {
        if (curve.nodes.length < 4) return;

        var lknot = curve.knots[2].val();
        var rknot = curve.knots[curve.knots.length - 3].val();

        var samples = 1000;
        var l = calcBspline(lknot, curve.nodes, curve.knots);

        for (var i = 1; i < samples; i++) {
            var t = lknot + (rknot - lknot) / samples * i;
            var r = calcBspline(t, curve.nodes, curve.knots);
            drawLine(ctx, l.x, l.y, r.x, r.y);
            l = r;
        }
    };

    drawConstruction = function (ctx, curve) {
        // TODO: implement this

    };

    drawBezierNodes = function (ctx, curve) {
        // TODO: implement this
    };

})();
<commit_msg>refactoring<commit_after>"use strict";

// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawLine(ctx, x1, y1, x2, y2);
// you can use: drawCircle(ctx, x, y, radius);

// Export only these methods
var drawCurve, drawConstruction, drawBezierNodes;

(function () {

    function calcBspline(t, nodes, knots) {
        var k = 0;
        for (var l = 3; l < (knots.length - 2); l++) {
            if (knots[l].val() >= t) break;
            k++;
        }
        var u1 = knots[k + 0].val();
        var u2 = knots[k + 1].val();
        var u3 = knots[k + 2].val();
        var u4 = knots[k + 3].val();
        var u5 = knots[k + 4].val();
        var u6 = knots[k + 5].val();

        var p10 = nodes[k + 0].pos();
        var p20 = nodes[k + 1].pos();
        var p30 = nodes[k + 2].pos();
        var p40 = nodes[k + 3].pos();

        var p21 = p10.multiply((u4 - t) / (u4 - u1)).add(p20.multiply((t - u1) / (u4 - u1)));
        var p31 = p20.multiply((u5 - t) / (u5 - u2)).add(p30.multiply((t - u2) / (u5 - u2)));
        var p41 = p30.multiply((u6 - t) / (u6 - u3)).add(p40.multiply((t - u3) / (u6 - u3)));

        var p32 = p21.multiply((u4 - t) / (u4 - u2)).add(p31.multiply((t - u2) / (u4 - u2)));
        var p42 = p31.multiply((u5 - t) / (u5 - u3)).add(p41.multiply((t - u3) / (u5 - u3)));

        return p32.multiply((u4 - t) / (u4 - u3)).add(p42.multiply((t - u3) / (u4 - u3)));
    }

    drawCurve = function (ctx, curve) {
        if (curve.nodes.length < 4) return;

        var lknot = curve.knots[2].val();
        var rknot = curve.knots[curve.knots.length - 3].val();

        var samples = 1000;
        var l = calcBspline(lknot, curve.nodes, curve.knots);

        for (var i = 1; i < samples; i++) {
            var t = lknot + (rknot - lknot) / samples * i;
            var r = calcBspline(t, curve.nodes, curve.knots);
            drawLine(ctx, l.x, l.y, r.x, r.y);
            l = r;
        }
    };

    drawConstruction = function (ctx, curve) {
        // TODO: implement this

    };

    drawBezierNodes = function (ctx, curve) {
        // TODO: implement this
    };

})();
<|endoftext|>


<commit_before>import Handlebars from 'handlebars'
import path from 'path'
import fse from 'fs-extra'

import {
  abeEngine,
  cmsData,
  cmsTemplates,
  config,
  abeExtend,
  Manager
} from '../'

/**
 * Page class
 * manage HTML generation for page template
 */
export default class Page {

  /**
   * Create new page object
   * @param  {Object} params req.params from express route
   * @param  {Object} i18n translation
   * @param  {Function} callback
   * @param  {Boolean} onlyHTML default = false, if true HTML content will contains abe attributes
   * @return {String} HTML page as string
   */
  constructor(templateId, template, json, onlyHTML = false) {
    // HOOKS beforePageJson
    json = abeExtend.hooks.instance.trigger('beforePageJson', json)

    if(typeof Handlebars.templates[templateId] !== 'undefined' &&
        Handlebars.templates[templateId] !== null &&
        config.files.templates.precompile
      ){

      template = Handlebars.templates[templateId]
      this.html = template(json, {data: {intl: config.intlData}})

      //console.log('precompile')

    } else {

      this._onlyHTML = onlyHTML
      this.template = template
      this.HbsTemplatePath = path.join(config.root, config.templates.url, 'hbs/'+templateId+'.hbs')

      abeEngine.instance.content = json

      // This pattern finds all abe tags which are not enclosed in a html tag attribute
      // it finds this one: <title>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</title>
      // it excludes this one: <meta name="description" content='{{abe type="text" key="meta_description" desc="Meta description" tab="Meta" order="4100"}}"/>
      this.abePattern = /[^"']({{abe.*?type=[\'|\"][text|rich|textarea]+[\'|\"][\s\S].*?}})/g

      // This pattern finds all abe tags enclosed in a HTML tag attribute
      this.abeAsAttributePattern = /( [A-Za-z0-9\-\_]+=["|']{1}{{abe.*?}})/g

      // This pattern finds all {{#each ...}}...{{/each}} blocks
      this.eachBlockPattern = />\s*(\{\{#each (\r|\t|\n|.)*?\/each\}\})/g

      // This pattern finds all {{#each ...}}...{{/each}} blocks
      this.blockPattern = /(\{\{#each.*\}\}[\s\S]*?\{\{\/each\}\})/g

      // Remove text with attribute "visible=false"
      this._removeHidden()

      if(!this._onlyHTML) {

        // Surrounds each Abe tag (which are text/rich/textarea and not in html attribute) with <abe> tag
        // ie. <title><abe>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</abe></title>
        this._encloseAbeTag()
      }
      else {
        this._removeHandlebarsRawFromHtml()
      }

      // je rajoute les index pour chaque bloc lié à un each
      this._indexEachBlocks()

      if(!this._onlyHTML){

        // Je maj les attributs associés aux Abe qui sont dans des attributs de tag HTML
        this._updateAbeAsAttribute()

        // je rajoute les attributs pour les tags Abe (qui ne sont pas dans un attribut HTML)
        this._updateAbeAsTag()

        // Don't know what it does...
        var source = config.source.name
        if(typeof json[source] !== 'undefined' && json[source] !== null) {
          var keys = Object.keys(json[source])

          for(var i in keys) {
            var replaceEach = new RegExp(`<!-- \\[\\[${keys[i]}\\]\\][\\s\\S]*?-->`, 'g')
            this.template = this.template.replace(replaceEach, '')

            var patAttrSource = new RegExp(' ([A-Za-z0-9\-\_]+)=["|\'].*?({{' + keys[i] + '}}).*?["|\']', 'g')
            var patAttrSourceMatch = this.template.match(patAttrSource)

            if(patAttrSourceMatch != null) {
              let checkEscapedRegex = /["|'](.*?)["|']/
              let patAttrSourceInside = new RegExp('(\\S+)=["\']?((?:.(?!["\']?\\s+(?:\\S+)=|[>"\']))+.)["\']?({{' + keys[i] + '}}).*?["|\']', 'g')
              Array.prototype.forEach.call(patAttrSourceMatch, (pat) => {
                let patAttrSourceCheck = patAttrSourceInside.exec(pat)
                if(patAttrSourceCheck != null) {

                  let checkEscaped = checkEscapedRegex.exec(patAttrSourceCheck[0])
                  if(checkEscaped != null && checkEscaped.length > 0) {
                    checkEscaped = escape(checkEscaped[1])
                    this.template = this.template.replace(
                      patAttrSourceCheck[0],
                      ` data-abe-attr="${patAttrSourceCheck[1]}" data-abe-attr-escaped="${checkEscaped}" data-abe="${keys[i]}" ${patAttrSourceCheck[0]}`
                    )
                  }
                }
              })
            }

            var eachSource = new RegExp(`({{#each ${keys[i]}}[\\s\\S a-z]*?{{\/each}})`, 'g')
            var matches = this.template.match(eachSource)
            if(typeof matches !== 'undefined' && matches !== null) {
              Array.prototype.forEach.call(matches, (match) => {
                this.template = this.template.replace(match, `${match}<!-- [[${keys[i]}]] ${cmsTemplates.encodeAbeTagAsComment(match)} -->`)
              })
            }
          }
        }
      }

      // We remove the {{abe type=data ...}} from the text
      this.template = cmsData.source.removeDataList(this.template)

      // It's time to replace the [index] by {{@index}} (concerning each blocks)
      this.template = this.template.replace(/\[index\]\./g, '{{@index}}-')

      if(config.files.templates.precompile){
        // Let's persist the precompiled template for future use (kind of cache)
        fse.writeFileSync(this.HbsTemplatePath, Handlebars.precompile(this.template), 'utf8')
        Manager.instance.addHbsTemplate(templateId)
      }

      // I compile the text
      var compiledTemplate = Handlebars.compile((!this._onlyHTML) ? cmsTemplates.insertDebugtoolUtilities(this.template) : this.template)

      // I create the html page ! yeah !!!
      this.html = compiledTemplate(json, {data: {intl: config.intlData}})
    }

    if(this._onlyHTML) {
      this.html = abeExtend.hooks.instance.trigger('afterPageSaveCompile', this.html, json)
    }else {
      this.html = abeExtend.hooks.instance.trigger('afterPageEditorCompile', this.html, json)
    }
  }

  _updateAbeAsAttribute() {
    var match
    while (match = this.abeAsAttributePattern.exec(this.template)) { // While regexp match {{attribut}}, ex: link, image ...
      if(cmsData.regex.isSingleAbe(match[0], this.template)){
        var more_attr = ''
        var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
        this.template = this.template.replace(
          new RegExp(match[0]),
          ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + (match[0].split('=')[0]).trim() + '"' +
          ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '"' +
          more_attr + match[0].replace('}}', ' has-abe=1}}')
        )
      }
    }

    return this
  }

  _updateAbeAsTag() {
    var match
    while (match = this.abePattern.exec(this.template)) {
      var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
      this.template = this.template.replace(
        cmsData.regex.escapeTextToRegex(match[0], 'g'),
        ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '" ' + match[0]
      )
    }

    return this
  }

  /**
   * [_indexEachBlocks description]
   * @param  {[type]} text   [description]
   * @param  {[type]} blocks [description]
   * @return {[type]}        [description]
   */
  _indexEachBlocks() {
    // create an array of {{each}} blocks
    var blocks = this._splitEachBlocks()

    Array.prototype.forEach.call(blocks, (block) => {
      var key = block.match(/#each (.*)\}\}/)
      key = key[1]
      var match

      if(!this._onlyHTML) {

        var voidData = {}
        voidData[key] = [{}]
        var blockCompiled = Handlebars.compile(block.replace(/{{abe (.*?)}}/g, '[[abe $1]]').replace(new RegExp(`\\.\\.\/${config.meta.name}`, 'g'), config.meta.name))
        var blockHtml = blockCompiled(voidData, {data: {intl: config.intlData}}).replace(/\[\[abe (.*?)\]\]/g, '{{abe $1}}')

        // je rajoute un data-abe-block avec index sur tous les tags html du bloc each
        var textEachWithIndex = block.replace(/(<(?![\/])[A-Za-z0-9!-]*)/g, '$1 data-abe-block="' + key + '{{@index}}"')

        // je remplace le block dans le texte par ça
        this.template = this.template.replace(block, textEachWithIndex + `<!-- [[${key}]] ${cmsTemplates.encodeAbeTagAsComment(blockHtml)} -->`)
      }

      // Pour chaque tag Abe, je mets en forme ce tag avec des data- supplémentaires
      while (match = this.abePattern.exec(block)) {
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
      }

      // Pour chaque tag Abe attribut de HTML, je mets en forme ce tag avec des data- supplémentaires sur le tag html parent
      while (match = this.abeAsAttributePattern.exec(block)) {
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
      }
    })

    return this
  }

  /**
   * create an array of {{#each}} blocks from the html document
   * @param  {String} html the html document
   * @return {Array}      the array of {{#each}} blocks
   */
  _splitEachBlocks() {
    var block
    var blocks = []

    while (block = this.blockPattern.exec(this.template)) {
      blocks.push(block[1])
    }

    return blocks
  }

  _insertAbeEach(theMatch, key, lastIndex) {
    var matchBlock = theMatch[0]
    if(cmsData.regex.isEachStatement(matchBlock)) return
    if(cmsData.regex.isBlockAbe(matchBlock)){
      var matchblockattr = (matchBlock.split('=')[0]).trim()
      var getattr = cmsData.regex.getAttr(matchBlock, 'key').replace('.', '[index].')
      var newMatchBlock = ((!this._onlyHTML) ?
                            (/=[\"\']\{\{(.*?)\}\}/g.test(matchBlock) ?
                                ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + matchblockattr + '"' :
                                '') +
                            ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="' + getattr + '" ' + matchBlock :
                            matchBlock)
          .replace(new RegExp('(key=[\'|"])' + key + '.', 'g'), '$1' + key + '[index].')
          .replace(/\{\{abe/, '{{abe dictionnary=\'' + key + '\'')

      this.template = this.template.replace(matchBlock, newMatchBlock)
    }

    return this
  }

  /**
   * add <abe> tag around html tag
   */
  _removeHidden() {
    this.template = this.template.replace(/(\{\{abe.*visible=[\'|\"]false.*\}\})/g, '')

    return this
  }

  /**
   * add <abe> tag around html tag
   * @param {String} text html string
   */
  _encloseAbeTag() {
    var match
    while (match = this.abePattern.exec(this.template)) {
      this.template = this.template.replace(cmsData.regex.escapeTextToRegex(match[1], 'g'), '<abe>' + match[1].trim() + '</abe>')
    }

    return this
  }

  /**
   * remove {{{{raw}}}}{{{{/raw}}}} from html
   */
  _removeHandlebarsRawFromHtml() {
    this.template = this.template.replace(/\{\{\{\{\/?raw\}\}\}\}/g, '')

    return this
  }
}<commit_msg>fix: json is not updt when abe attribut inside bloc each has reload=true<commit_after>import Handlebars from 'handlebars'
import path from 'path'
import fse from 'fs-extra'

import {
  abeEngine,
  cmsData,
  cmsTemplates,
  config,
  abeExtend,
  Manager
} from '../'

/**
 * Page class
 * manage HTML generation for page template
 */
export default class Page {

  /**
   * Create new page object
   * @param  {Object} params req.params from express route
   * @param  {Object} i18n translation
   * @param  {Function} callback
   * @param  {Boolean} onlyHTML default = false, if true HTML content will contains abe attributes
   * @return {String} HTML page as string
   */
  constructor(templateId, template, json, onlyHTML = false) {
    // HOOKS beforePageJson
    json = abeExtend.hooks.instance.trigger('beforePageJson', json)

    abeEngine.instance.content = json

    if(typeof Handlebars.templates[templateId] !== 'undefined' &&
        Handlebars.templates[templateId] !== null &&
        config.files.templates.precompile
      ){

      template = Handlebars.templates[templateId]
      this.html = template(json, {data: {intl: config.intlData}})

      //console.log('precompile')

    } else {

      this._onlyHTML = onlyHTML
      this.template = template
      this.HbsTemplatePath = path.join(config.root, config.templates.url, 'hbs/'+templateId+'.hbs')

      // This pattern finds all abe tags which are not enclosed in a html tag attribute
      // it finds this one: <title>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</title>
      // it excludes this one: <meta name="description" content='{{abe type="text" key="meta_description" desc="Meta description" tab="Meta" order="4100"}}"/>
      this.abePattern = /[^"']({{abe.*?type=[\'|\"][text|rich|textarea]+[\'|\"][\s\S].*?}})/g

      // This pattern finds all abe tags enclosed in a HTML tag attribute
      this.abeAsAttributePattern = /( [A-Za-z0-9\-\_]+=["|']{1}{{abe.*?}})/g

      // This pattern finds all {{#each ...}}...{{/each}} blocks
      this.eachBlockPattern = />\s*(\{\{#each (\r|\t|\n|.)*?\/each\}\})/g

      // This pattern finds all {{#each ...}}...{{/each}} blocks
      this.blockPattern = /(\{\{#each.*\}\}[\s\S]*?\{\{\/each\}\})/g

      // Remove text with attribute "visible=false"
      this._removeHidden()

      if(!this._onlyHTML) {

        // Surrounds each Abe tag (which are text/rich/textarea and not in html attribute) with <abe> tag
        // ie. <title><abe>{{abe type='text' key='meta_title' desc='Meta title' tab='Meta' order='4000'}}</abe></title>
        this._encloseAbeTag()
      }
      else {
        this._removeHandlebarsRawFromHtml()
      }

      // je rajoute les index pour chaque bloc lié à un each
      this._indexEachBlocks()

      if(!this._onlyHTML){

        // Je maj les attributs associés aux Abe qui sont dans des attributs de tag HTML
        this._updateAbeAsAttribute()

        // je rajoute les attributs pour les tags Abe (qui ne sont pas dans un attribut HTML)
        this._updateAbeAsTag()

        // Don't know what it does...
        var source = config.source.name
        if(typeof json[source] !== 'undefined' && json[source] !== null) {
          var keys = Object.keys(json[source])

          for(var i in keys) {
            var replaceEach = new RegExp(`<!-- \\[\\[${keys[i]}\\]\\][\\s\\S]*?-->`, 'g')
            this.template = this.template.replace(replaceEach, '')

            var patAttrSource = new RegExp(' ([A-Za-z0-9\-\_]+)=["|\'].*?({{' + keys[i] + '}}).*?["|\']', 'g')
            var patAttrSourceMatch = this.template.match(patAttrSource)

            if(patAttrSourceMatch != null) {
              let checkEscapedRegex = /["|'](.*?)["|']/
              let patAttrSourceInside = new RegExp('(\\S+)=["\']?((?:.(?!["\']?\\s+(?:\\S+)=|[>"\']))+.)["\']?({{' + keys[i] + '}}).*?["|\']', 'g')
              Array.prototype.forEach.call(patAttrSourceMatch, (pat) => {
                let patAttrSourceCheck = patAttrSourceInside.exec(pat)
                if(patAttrSourceCheck != null) {

                  let checkEscaped = checkEscapedRegex.exec(patAttrSourceCheck[0])
                  if(checkEscaped != null && checkEscaped.length > 0) {
                    checkEscaped = escape(checkEscaped[1])
                    this.template = this.template.replace(
                      patAttrSourceCheck[0],
                      ` data-abe-attr="${patAttrSourceCheck[1]}" data-abe-attr-escaped="${checkEscaped}" data-abe="${keys[i]}" ${patAttrSourceCheck[0]}`
                    )
                  }
                }
              })
            }

            var eachSource = new RegExp(`({{#each ${keys[i]}}[\\s\\S a-z]*?{{\/each}})`, 'g')
            var matches = this.template.match(eachSource)
            if(typeof matches !== 'undefined' && matches !== null) {
              Array.prototype.forEach.call(matches, (match) => {
                this.template = this.template.replace(match, `${match}<!-- [[${keys[i]}]] ${cmsTemplates.encodeAbeTagAsComment(match)} -->`)
              })
            }
          }
        }
      }

      // We remove the {{abe type=data ...}} from the text
      this.template = cmsData.source.removeDataList(this.template)

      // It's time to replace the [index] by {{@index}} (concerning each blocks)
      this.template = this.template.replace(/\[index\]\./g, '{{@index}}-')

      if(config.files.templates.precompile){
        // Let's persist the precompiled template for future use (kind of cache)
        fse.writeFileSync(this.HbsTemplatePath, Handlebars.precompile(this.template), 'utf8')
        Manager.instance.addHbsTemplate(templateId)
      }

      // I compile the text
      var compiledTemplate = Handlebars.compile((!this._onlyHTML) ? cmsTemplates.insertDebugtoolUtilities(this.template) : this.template)

      // I create the html page ! yeah !!!
      this.html = compiledTemplate(json, {data: {intl: config.intlData}})
    }

    if(this._onlyHTML) {
      this.html = abeExtend.hooks.instance.trigger('afterPageSaveCompile', this.html, json)
    }else {
      this.html = abeExtend.hooks.instance.trigger('afterPageEditorCompile', this.html, json)
    }
  }

  _updateAbeAsAttribute() {
    var match
    while (match = this.abeAsAttributePattern.exec(this.template)) { // While regexp match {{attribut}}, ex: link, image ...
      if(cmsData.regex.isSingleAbe(match[0], this.template)){
        var more_attr = ''
        var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
        this.template = this.template.replace(
          new RegExp(match[0]),
          ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + (match[0].split('=')[0]).trim() + '"' +
          ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '"' +
          more_attr + match[0].replace('}}', ' has-abe=1}}')
        )
      }
    }

    return this
  }

  _updateAbeAsTag() {
    var match
    while (match = this.abePattern.exec(this.template)) {
      var getattr = cmsData.regex.getAttr(match, 'key').replace(/\./g, '-')
      this.template = this.template.replace(
        cmsData.regex.escapeTextToRegex(match[0], 'g'),
        ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="'  + getattr + '" ' + match[0]
      )
    }

    return this
  }

  /**
   * [_indexEachBlocks description]
   * @param  {[type]} text   [description]
   * @param  {[type]} blocks [description]
   * @return {[type]}        [description]
   */
  _indexEachBlocks() {
    // create an array of {{each}} blocks
    var blocks = this._splitEachBlocks()

    Array.prototype.forEach.call(blocks, (block) => {
      var key = block.match(/#each (.*)\}\}/)
      key = key[1]
      var match

      if(!this._onlyHTML) {

        var voidData = {}
        voidData[key] = [{}]
        var blockCompiled = Handlebars.compile(block.replace(/{{abe (.*?)}}/g, '[[abe $1]]').replace(new RegExp(`\\.\\.\/${config.meta.name}`, 'g'), config.meta.name))
        var blockHtml = blockCompiled(voidData, {data: {intl: config.intlData}}).replace(/\[\[abe (.*?)\]\]/g, '{{abe $1}}')

        // je rajoute un data-abe-block avec index sur tous les tags html du bloc each
        var textEachWithIndex = block.replace(/(<(?![\/])[A-Za-z0-9!-]*)/g, '$1 data-abe-block="' + key + '{{@index}}"')

        // je remplace le block dans le texte par ça
        this.template = this.template.replace(block, textEachWithIndex + `<!-- [[${key}]] ${cmsTemplates.encodeAbeTagAsComment(blockHtml)} -->`)
      }

      // Pour chaque tag Abe, je mets en forme ce tag avec des data- supplémentaires
      while (match = this.abePattern.exec(block)) {
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
      }

      // Pour chaque tag Abe attribut de HTML, je mets en forme ce tag avec des data- supplémentaires sur le tag html parent
      while (match = this.abeAsAttributePattern.exec(block)) {
        this._insertAbeEach(match, key, this.eachBlockPattern.lastIndex - block.length)
      }
    })

    return this
  }

  /**
   * create an array of {{#each}} blocks from the html document
   * @param  {String} html the html document
   * @return {Array}      the array of {{#each}} blocks
   */
  _splitEachBlocks() {
    var block
    var blocks = []

    while (block = this.blockPattern.exec(this.template)) {
      blocks.push(block[1])
    }

    return blocks
  }

  _insertAbeEach(theMatch, key, lastIndex) {
    var matchBlock = theMatch[0]
    if(cmsData.regex.isEachStatement(matchBlock)) return
    if(cmsData.regex.isBlockAbe(matchBlock)){
      var matchblockattr = (matchBlock.split('=')[0]).trim()
      var getattr = cmsData.regex.getAttr(matchBlock, 'key').replace('.', '[index].')
      var newMatchBlock = ((!this._onlyHTML) ?
                            (/=[\"\']\{\{(.*?)\}\}/g.test(matchBlock) ?
                                ' data-abe-attr-' + cmsData.regex.validDataAbe(getattr) + '="'  + matchblockattr + '"' :
                                '') +
                            ' data-abe-' + cmsData.regex.validDataAbe(getattr) + '="' + getattr + '" ' + matchBlock :
                            matchBlock)
          .replace(new RegExp('(key=[\'|"])' + key + '.', 'g'), '$1' + key + '[index].')
          .replace(/\{\{abe/, '{{abe dictionnary=\'' + key + '\'')

      this.template = this.template.replace(matchBlock, newMatchBlock)
    }

    return this
  }

  /**
   * add <abe> tag around html tag
   */
  _removeHidden() {
    this.template = this.template.replace(/(\{\{abe.*visible=[\'|\"]false.*\}\})/g, '')

    return this
  }

  /**
   * add <abe> tag around html tag
   * @param {String} text html string
   */
  _encloseAbeTag() {
    var match
    while (match = this.abePattern.exec(this.template)) {
      this.template = this.template.replace(cmsData.regex.escapeTextToRegex(match[1], 'g'), '<abe>' + match[1].trim() + '</abe>')
    }

    return this
  }

  /**
   * remove {{{{raw}}}}{{{{/raw}}}} from html
   */
  _removeHandlebarsRawFromHtml() {
    this.template = this.template.replace(/\{\{\{\{\/?raw\}\}\}\}/g, '')

    return this
  }
}<|endoftext|>


<commit_before>import createSagaMiddleware from 'redux-saga'
import { applyMiddleware, createStore, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { addFormSubmitSagaTo as formSaga } from 'redux-form-submit-saga'
import rootReducer from './rootReducer'
import rootSaga from './rootSaga'

// Initialize devtools
const devTools = window.devToolsExtension ? window.devToolsExtension() : f => f

// Returns a store and accepts an initial state
const configureStore = (preloadedState, history) => {
  const sagaMiddleware = createSagaMiddleware()
  const store = createStore(
    connectRouter(history)(rootReducer),
    preloadedState,
    compose(applyMiddleware(sagaMiddleware, routerMiddleware(history)), devTools)
  )

  sagaMiddleware.run(rootSaga)
  sagaMiddleware.run(formSaga())

  return store
}

export default configureStore
<commit_msg>Update devtools initialization<commit_after>import createSagaMiddleware from 'redux-saga'
import { applyMiddleware, createStore, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import { addFormSubmitSagaTo as formSaga } from 'redux-form-submit-saga'
import rootReducer from './rootReducer'
import rootSaga from './rootSaga'

// Initialize devtools when available
// eslint-disable-next-line no-underscore-dangle
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

// Returns a store and accepts an initial state
const configureStore = (preloadedState, history) => {
  const sagaMiddleware = createSagaMiddleware()
  const store = createStore(
    connectRouter(history)(rootReducer),
    preloadedState,
    composeEnhancers(applyMiddleware(sagaMiddleware, routerMiddleware(history)))
  )

  sagaMiddleware.run(rootSaga)
  sagaMiddleware.run(formSaga())

  return store
}

export default configureStore
<|endoftext|>