Move marketplace ui to marketplace.js
diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js
index d54787a..c94edf4 100644
--- a/erpnext/public/js/hub/hub_factory.js
+++ b/erpnext/public/js/hub/hub_factory.js
@@ -14,7 +14,7 @@
 
 	make(page_name) {
 		const assets = [
-			'/assets/erpnext/js/hub/hub_listing.js'
+			'/assets/erpnext/js/hub/marketplace.js'
 		];
 
 		frappe.require(assets, () => {
diff --git a/erpnext/public/js/hub/hub_listing.js b/erpnext/public/js/hub/hub_listing.js
index eb55806..368c723 100644
--- a/erpnext/public/js/hub/hub_listing.js
+++ b/erpnext/public/js/hub/hub_listing.js
@@ -1,792 +1,3 @@
-frappe.provide('erpnext.hub');
-
-erpnext.hub.Marketplace = class Marketplace {
-	constructor({ parent }) {
-		this.$parent = $(parent);
-		this.page = parent.page;
-
-		frappe.db.get_doc('Hub Settings')
-			.then(doc => {
-				this.hub_settings = doc;
-				this.registered = doc.registered;
-
-				this.setup_header();
-				this.make_sidebar();
-				this.make_body();
-				this.setup_events();
-				this.refresh();
-			});
-	}
-
-	setup_header() {
-		this.page.set_title(__('Marketplace'));
-	}
-
-	setup_events() {
-		this.$parent.on('click', '[data-route]', (e) => {
-			const $target = $(e.currentTarget);
-			const route = $target.data().route;
-			frappe.set_route(route);
-
-			e.stopPropagation();
-		});
-	}
-
-	make_sidebar() {
-		this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs');
-
-		const user_specific_items_html = this.registered
-			? `<li class="hub-sidebar-item text-muted" data-route="marketplace/profile">
-					${__('Your Profile')}
-				</li>
-				<li class="hub-sidebar-item text-muted" data-route="marketplace/publish">
-					${__('Publish Products')}
-				</li>`
-
-			: `<li class="hub-sidebar-item text-muted" data-route="marketplace/register">
-					${__('Become a seller')}
-				</li>`;
-
-		this.$sidebar.append(`
-			<ul class="list-unstyled hub-sidebar-group">
-				<li class="hub-sidebar-item" data-route="marketplace/home">
-					${__('Browse')}
-				</li>
-				<li class="hub-sidebar-item" data-route="marketplace/favourites">
-					${__('Favorites')}
-				</li>
-				${user_specific_items_html}
-			</ul>
-		`);
-
-		this.make_sidebar_categories();
-	}
-
-	make_sidebar_categories() {
-		frappe.call('erpnext.hub_node.get_categories')
-			.then(r => {
-				const categories = r.message.map(d => d.value).sort();
-				const sidebar_items = [
-					`<li class="hub-sidebar-item bold is-title">
-						${__('Category')}
-					</li>`,
-					`<li class="hub-sidebar-item active" data-route="marketplace/home">
-						${__('All')}
-					</li>`,
-					...(this.registered
-						? [`<li class="hub-sidebar-item active" data-route="marketplace/my-products">
-							${__('Your Products')}
-						</li>`]
-						: []),
-					...categories.map(category => `
-						<li class="hub-sidebar-item text-muted" data-route="marketplace/category/${category}">
-							${__(category)}
-						</li>
-					`)
-				];
-
-				this.$sidebar.append(`
-					<ul class="list-unstyled">
-						${sidebar_items.join('')}
-					</ul>
-				`);
-
-				this.update_sidebar();
-			});
-	}
-
-	make_body() {
-		this.$body = this.$parent.find('.layout-main-section');
-	}
-
-	update_sidebar() {
-		const route = frappe.get_route_str();
-		const $sidebar_item = this.$sidebar.find(`[data-route="${route}"]`);
-
-		const $siblings = this.$sidebar.find('[data-route]');
-		$siblings.removeClass('active').addClass('text-muted');
-
-		$sidebar_item.addClass('active').removeClass('text-muted');
-	}
-
-	refresh() {
-		const route = frappe.get_route();
-		this.subpages = this.subpages || {};
-
-		for (let page in this.subpages) {
-			this.subpages[page].hide();
-		}
-
-		if (route[1] === 'home' && !this.subpages.home) {
-			this.subpages.home = new erpnext.hub.Home(this.$body);
-		}
-
-		if (route[1] === 'favourites' && !this.subpages.favourites) {
-			this.subpages.favourites = new erpnext.hub.Favourites(this.$body);
-		}
-
-		if (route[1] === 'category' && route[2] && !this.subpages.category) {
-			this.subpages.category = new erpnext.hub.Category(this.$body);
-		}
-
-		if (route[1] === 'item' && route[2] && !this.subpages.item) {
-			this.subpages.item = new erpnext.hub.Item(this.$body);
-		}
-
-		if (route[1] === 'register' && !this.subpages.register) {
-			this.subpages.register = new erpnext.hub.Register(this.$body);
-		}
-
-		if (route[1] === 'publish' && !this.subpages.publish) {
-			this.subpages.publish = new erpnext.hub.Publish(this.$body);
-		}
-
-
-		if (!Object.keys(this.subpages).includes(route[1])) {
-			frappe.show_not_found();
-			return;
-		}
-
-		this.update_sidebar();
-		frappe.utils.scroll_to(0);
-		this.subpages[route[1]].show();
-	}
-}
-
-class SubPage {
-	constructor(parent) {
-		this.$parent = $(parent);
-		this.make_wrapper();
-	}
-
-	make_wrapper() {
-		const page_name = frappe.get_route()[1];
-		this.$wrapper = $(`<div class="marketplace-page" data-page-name="${page_name}">`).appendTo(this.$parent);
-		this.hide();
-	}
-
-	show() {
-		this.refresh();
-		this.$wrapper.show();
-	}
-
-	hide() {
-		this.$wrapper.hide();
-	}
-}
-
-erpnext.hub.Home = class Home extends SubPage {
-	make_wrapper() {
-		super.make_wrapper();
-		this.make_search_bar();
-	}
-
-	refresh() {
-		this.get_items_and_render();
-	}
-
-	get_items_and_render() {
-		this.$wrapper.find('.hub-card-container').empty();
-		this.get_items()
-			.then(r => {
-				erpnext.hub.hub_item_cache = r.message;
-				this.render(r.message);
-			});
-	}
-
-	get_items() {
-		return frappe.call('erpnext.hub_node.get_list', {
-			doctype: 'Hub Item',
-			filters: {
-				image: ['like', 'http%']
-			}
-		});
-	}
-
-	make_search_bar() {
-		const $search = $(`
-			<div class="hub-search-container">
-				<input type="text" class="form-control" placeholder="Search for anything">
-			</div>`
-		);
-		this.$wrapper.append($search);
-		const $search_input = $search.find('input');
-
-		$search_input.on('keydown', frappe.utils.debounce((e) => {
-			if (e.which === frappe.ui.keyCode.ENTER) {
-				this.search_value = $search_input.val();
-				this.get_items_and_render();
-			}
-		}, 300));
-	}
-
-	render(items) {
-		const html = get_item_card_container_html(items, __('Recently Published'));
-		this.$wrapper.append(html)
-	}
-}
-
-erpnext.hub.Favourites = class Favourites extends SubPage {
-	refresh() {
-		this.get_favourites()
-			.then(r => {
-				this.render(r.message);
-			});
-	}
-
-	get_favourites() {
-		return frappe.call('erpnext.hub_node.get_item_favourites');
-	}
-
-	render(items) {
-		this.$wrapper.find('.hub-card-container').empty();
-		const html = get_item_card_container_html(items, __('Favourites'));
-		this.$wrapper.append(html)
-	}
-}
-
-erpnext.hub.Category = class Category extends SubPage {
-	refresh() {
-		this.category = frappe.get_route()[2];
-		this.get_items_for_category(this.category)
-			.then(r => {
-				this.render(r.message);
-			});
-	}
-
-	get_items_for_category(category) {
-		this.$wrapper.find('.hub-card-container').empty();
-		return frappe.call('erpnext.hub_node.get_list', {
-			doctype: 'Hub Item',
-			filters: {
-				hub_category: category
-			}
-		});
-	}
-
-	render(items) {
-		const html = get_item_card_container_html(items, __(this.category));
-		this.$wrapper.append(html)
-	}
-}
-
-erpnext.hub.Item = class Item extends SubPage {
-	refresh() {
-		this.hub_item_code = frappe.get_route()[2];
-
-		this.get_item(this.hub_item_code)
-			.then(item => {
-				this.render(item);
-			});
-	}
-
-	get_item(hub_item_code) {
-		return new Promise(resolve => {
-			const item = (erpnext.hub.hub_item_cache || []).find(item => item.name === hub_item_code)
-
-			if (item) {
-				resolve(item);
-			} else {
-				frappe.call('erpnext.hub_node.get_list', {
-					doctype: 'Hub Item',
-					filters: {
-						name: hub_item_code
-					}
-				})
-				.then(r => {
-					resolve(r.message[0]);
-				});
-			}
-		});
-	}
-
-	render(item) {
-		const title = item.item_name || item.name;
-		const company = item.company_name;
-
-		const who = __('Posted By {0}', [company]);
-		const when = comment_when(item.creation);
-
-		const city = item.seller_city ? item.seller_city + ', ' : '';
-		const country = item.country ? item.country : '';
-		const where = `${city}${country}`;
-
-		const dot_spacer = '<span aria-hidden="true"> · </span>';
-
-		const description = item.description || '';
-
-		const rating_html = get_rating_html(item);
-		const rating_count = item.reviews.length > 0 ? `(${item.reviews.length} reviews)` : '';
-
-		const html = `
-			<div class="hub-item-container">
-				<div class="row visible-xs">
-					<div class="col-xs-12 margin-bottom">
-						<button class="btn btn-xs btn-default" data-route="marketplace/home">Back to home</button>
-					</div>
-				</div>
-				<div class="row">
-					<div class="col-md-3">
-						<div class="hub-item-image">
-							<img src="${item.image}">
-						</div>
-					</div>
-					<div class="col-md-6">
-						<h2>${title}</h2>
-						<div class="text-muted">
-							<p>${where}${dot_spacer}${when}</p>
-							<p>${rating_html}${rating_count}</p>
-						</div>
-						<hr>
-						<div class="hub-item-description">
-						${description ?
-							`<b>${__('Description')}</b>
-							<p>${description}</p>
-							` : __('No description')
-						}
-						</div>
-					</div>
-				</div>
-				<div class="row hub-item-seller">
-					<div class="col-md-12 margin-top margin-bottom">
-						<b class="text-muted">Seller Information</b>
-					</div>
-					<div class="col-md-1">
-						<img src="https://picsum.photos/200">
-					</div>
-					<div class="col-md-6">
-						<a href="#marketplace/seller/${company}" class="bold">${company}</a>
-						<p class="text-muted">
-							Contact Seller
-						</p>
-					</div>
-				</div>
-				<!-- review area -->
-				<div class="row hub-item-review-container">
-					<div class="col-md-12 form-footer">
-						<div class="form-comments">
-							<div class="timeline">
-								<div class="timeline-head"></div>
-								<div class="timeline-items"></div>
-							</div>
-						</div>
-						<div class="pull-right scroll-to-top">
-							<a onclick="frappe.utils.scroll_to(0)"><i class="fa fa-chevron-up text-muted"></i></a>
-						</div>
-					</div>
-				</div>
-			</div>
-		`;
-
-		this.$wrapper.html(html);
-
-		this.make_review_area();
-		this.render_reviews(item);
-	}
-
-	make_review_area() {
-		this.comment_area = new frappe.ui.ReviewArea({
-			parent: this.$wrapper.find('.timeline-head').empty(),
-			mentions: [],
-			on_submit: (val) => {
-				val.user = frappe.session.user;
-				val.username = frappe.session.user_fullname;
-
-				frappe.call({
-					method: 'erpnext.hub_node.send_review',
-					args: {
-						hub_item_code: this.hub_item_code,
-						review: val
-					},
-					callback: (r) => {
-						console.log(r);
-						this.render_reviews(r.message);
-						this.comment_area.reset();
-					},
-					freeze: true
-				});
-			}
-		});
-	}
-
-	render_reviews(item) {
-		this.$wrapper.find('.timeline-items').empty();
-		item.reviews.forEach(review => this.render_review(review, item));
-	}
-
-	render_review(review, item) {
-		let username = review.username || review.user || __("Anonymous");
-
-		let image_html = review.user_image
-			? `<div class="avatar-frame" style="background-image: url(${review.user_image})"></div>`
-			: `<div class="standard-image" style="background-color: #fafbfc">${frappe.get_abbr(username)}</div>`
-
-		let edit_html = review.own
-			? `<div class="pull-right hidden-xs close-btn-container">
-				<span class="small text-muted">
-					${'data.delete'}
-				</span>
-			</div>
-			<div class="pull-right edit-btn-container">
-				<span class="small text-muted">
-					${'data.edit'}
-				</span>
-			</div>`
-			: '';
-
-		let rating_html = get_rating_html(item);
-
-		const $timeline_items = this.$wrapper.find('.timeline-items');
-
-		$(this.get_timeline_item(review, image_html, edit_html, rating_html))
-			.appendTo($timeline_items);
-	}
-
-	get_timeline_item(data, image_html, edit_html, rating_html) {
-		return `<div class="media timeline-item user-content" data-doctype="${''}" data-name="${''}">
-			<span class="pull-left avatar avatar-medium hidden-xs" style="margin-top: 1px">
-				${image_html}
-			</span>
-			<div class="pull-left media-body">
-				<div class="media-content-wrapper">
-					<div class="action-btns">${edit_html}</div>
-
-					<div class="comment-header clearfix">
-						<span class="pull-left avatar avatar-small visible-xs">
-							${image_html}
-						</span>
-
-						<div class="asset-details">
-							<span class="author-wrap">
-								<i class="octicon octicon-quote hidden-xs fa-fw"></i>
-								<span>${data.username}</span>
-							</span>
-							<a class="text-muted">
-								<span class="text-muted hidden-xs">&ndash;</span>
-								<span class="hidden-xs">${comment_when(data.modified)}</span>
-							</a>
-						</div>
-					</div>
-					<div class="reply timeline-content-show">
-						<div class="timeline-item-content">
-							<p class="text-muted">
-								${rating_html}
-							</p>
-							<h6 class="bold">${data.subject}</h6>
-							<p class="text-muted">
-								${data.content}
-							</p>
-						</div>
-					</div>
-				</div>
-			</div>
-		</div>`;
-	}
-}
-erpnext.hub.Register = class Register extends SubPage {
-	make_wrapper() {
-		super.make_wrapper();
-		this.$register_container = $(`<div class="row register-container">`)
-			.appendTo(this.$wrapper);
-		this.$form_container = $('<div class="col-md-8 col-md-offset-1 form-container">')
-			.appendTo(this.$wrapper);
-	}
-
-	refresh() {
-		this.$register_container.empty();
-		this.$form_container.empty();
-		this.render();
-	}
-
-	render() {
-		this.make_field_group();
-	}
-
-	make_field_group() {
-		const fields = [
-			{
-				fieldtype: 'Link',
-				fieldname: 'company',
-				label: __('Company'),
-				options: 'Company',
-				onchange: () => {
-					const value = this.field_group.get_value('company');
-
-					if (value) {
-						frappe.db.get_doc('Company', value)
-							.then(company => {
-								this.field_group.set_values({
-									country: company.country,
-									company_email: company.email,
-									currency: company.default_currency
-								});
-							});
-					}
-				}
-			},
-			{
-				fieldname: 'company_email',
-				label: __('Email'),
-				fieldtype: 'Data'
-			},
-			{
-				fieldname: 'country',
-				label: __('Country'),
-				fieldtype: 'Read Only'
-			},
-			{
-				fieldname: 'currency',
-				label: __('Currency'),
-				fieldtype: 'Read Only'
-			},
-			{
-				fieldtype: 'Text',
-				label: __('About your Company'),
-				fieldname: 'company_description'
-			}
-		];
-
-		this.field_group = new frappe.ui.FieldGroup({
-			parent: this.$form_container,
-			fields
-		});
-
-		this.field_group.make();
-
-		this.$form_container.find('.form-column').append(`
-			<div class="text-right">
-				<button type="submit" class="btn btn-primary btn-register btn-sm">${__('Submit')}</button>
-			</div>
-		`);
-
-		this.$form_container.find('.form-message').removeClass('hidden small').addClass('h4').text(__('Become a Seller'))
-
-		this.$form_container.on('click', '.btn-register', () => {
-			const form_values = this.field_group.get_values();
-			frappe.call('erpnext.hub_node.doctype.hub_settings.hub_settings.register_seller', form_values)
-				.then(() => {
-					// Reload page and things ... but for now
-					frappe.msgprint('Registered successfully.');
-				});
-		});
-	}
-}
-
-erpnext.hub.Publish = class Publish extends SubPage {
-	make_wrapper() {
-		super.make_wrapper();
-		const title_html = `<b>${__('Select Products to Publish')}</b>`;
-		const info = `<p class="text-muted">${__("Status decided by the 'Publish in Hub' field in Item.")}</p>`;
-		const subtitle_html = `
-		<p class="text-muted">
-			${__(`Only products with an image, description and category can be published.
-			Please update them if an item in your inventory does not appear.`)}
-		</p>`;
-		const publish_button_html = `<button class="btn btn-primary btn-sm publish-items">
-			<i class="visible-xs octicon octicon-check"></i>
-			<span class="hidden-xs">Publish</span>
-		</button>`;
-
-		const select_all_button = `<button class="btn btn-secondary btn-default btn-xs margin-right select-all">Select All</button>`;
-		const deselect_all_button = `<button class="btn btn-secondary btn-default btn-xs deselect-all">Deselect All</button>`;
-
-		const search_html = `<div class="hub-search-container">
-			<input type="text" class="form-control" placeholder="Search Items">
-		</div>`;
-
-		const subpage_header = $(`
-			<div class='subpage-title flex'>
-				<div>
-					${title_html}
-					${subtitle_html}
-				</div>
-				${publish_button_html}
-			</div>
-
-			${search_html}
-
-			${select_all_button}
-			${deselect_all_button}
-		`);
-
-		this.$wrapper.append(subpage_header);
-
-		this.setup_events();
-	}
-
-	setup_events() {
-		this.$wrapper.find('.select-all').on('click', () => {
-			this.$wrapper.find('.hub-card').addClass('active');
-		});
-
-		this.$wrapper.find('.deselect-all').on('click', () => {
-			this.$wrapper.find('.hub-card').removeClass('active');
-		});
-
-		this.$wrapper.find('.publish-items').on('click', () => {
-			this.publish_selected_items()
-				.then(r => {
-					frappe.msgprint('check');
-				});
-		});
-
-		const $search_input = this.$wrapper.find('.hub-search-container input');
-		this.search_value = '';
-
-		$search_input.on('keydown', frappe.utils.debounce((e) => {
-			if (e.which === frappe.ui.keyCode.ENTER) {
-				this.search_value = $search_input.val();
-				this.get_items_and_render();
-			}
-		}, 300));
-	}
-
-	get_items_and_render() {
-		this.$wrapper.find('.hub-card-container').empty();
-		this.get_valid_items()
-			.then(r => {
-				this.render(r.message);
-			});
-	}
-
-	refresh() {
-		this.get_items_and_render();
-	}
-
-	render(items) {
-		const items_container = $(get_item_card_container_html(items));
-		items_container.addClass('static').on('click', '.hub-card', (e) => {
-			const $target = $(e.currentTarget);
-			$target.toggleClass('active');
-		});
-
-		this.$wrapper.append(items_container);
-	}
-
-	get_valid_items() {
-		return frappe.call(
-			'erpnext.hub_node.get_valid_items',
-			{
-				search_value: this.search_value
-			}
-		);
-	}
-
-	publish_selected_items() {
-		const items_to_publish = [];
-		const items_to_unpublish = [];
-		this.$wrapper.find('.hub-card').map(function () {
-			const active = $(this).hasClass('active');
-
-			if(active) {
-				items_to_publish.push($(this).attr("data-id"));
-			} else {
-				items_to_unpublish.push($(this).attr("data-id"));
-			}
-		});
-
-		return frappe.call(
-			'erpnext.hub_node.publish_selected_items',
-			{
-				items_to_publish: items_to_publish,
-				items_to_unpublish: items_to_unpublish
-			}
-		);
-	}
-}
-
-function get_item_card_container_html(items, title='') {
-	const items_html = (items || []).map(item => get_item_card_html(item)).join('');
-
-	const html = `<div class="row hub-card-container">
-		<div class="col-sm-12 margin-bottom">
-			<b>${title}</b>
-		</div>
-		${items_html}
-	</div>`;
-
-	return html;
-}
-
-function get_item_card_html(item) {
-	const item_name = item.item_name || item.name;
-	const title = strip_html(item_name);
-	const img_url = item.image;
-
-	const company_name = item.company_name;
-
-	const active = item.publish_in_hub;
-
-	const id = item.hub_item_code || item.item_code;
-
-	// Subtitle
-	let subtitle = [comment_when(item.creation)];
-	const rating = get_rating(item);
-	if (rating > 0) {
-		subtitle.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
-	}
-	subtitle.push(company_name);
-
-	let dot_spacer = '<span aria-hidden="true"> · </span>';
-	subtitle = subtitle.join(dot_spacer);
-
-	// Decide item link
-	const isLocal = item.source_type === "local";
-	const route = !isLocal
-		? `marketplace/item/${item.hub_item_code}`
-		: `Form/Item/${item.item_name}`;
-
-	const card_route = isLocal ? '' : `data-route='${route}'`;
-
-	const show_local_item_button = isLocal
-		? `<div class="overlay button-overlay" data-route='${route}' onclick="event.preventDefault();">
-				<button class="btn btn-default zoom-view">
-					<i class="octicon octicon-eye"></i>
-				</button>
-			</div>`
-		: '';
-
-	const item_html = `
-		<div class="col-md-3 col-sm-4 col-xs-6">
-			<div class="hub-card ${active ? 'active' : ''}" ${card_route} data-id="${id}">
-				<div class="hub-card-header">
-					<div class="title">
-						<div class="hub-card-title ellipsis bold">${title}</div>
-						<div class="hub-card-subtitle ellipsis text-muted">${subtitle}</div>
-					</div>
-					<i class="octicon octicon-check text-success"></i>
-				</div>
-				<div class="hub-card-body">
-					<img class="hub-card-image ${item.image ? '' : 'no-image'}" src="${img_url}" />
-					<div class="overlay hub-card-overlay"></div>
-					${show_local_item_button}
-				</div>
-			</div>
-		</div>
-	`;
-
-	return item_html;
-}
-
-function get_rating(item) {
-	const review_length = (item.reviews || []).length;
-	return review_length
-		? item.reviews
-			.map(r => r.rating)
-			.reduce((a, b) => a + b, 0) / review_length
-		: 0;
-}
-
-function get_rating_html(item) {
-	const rating = get_rating(item);
-	let rating_html = ``;
-	for (var i = 0; i < 5; i++) {
-		let star_class = 'fa-star';
-		if (i >= rating) star_class = 'fa-star-o';
-		rating_html += `<i class='fa fa-fw ${star_class} star-icon' data-index=${i}></i>`;
-	}
-	return rating_html;
-}
 
 erpnext.hub.HubListing = class HubListing extends frappe.views.BaseList {
 	setup_defaults() {