text: frappe.ui.Notifications = class Notifications {
  	constructor() {
  		frappe.model
  			.with_doc('Notification Settings', frappe.session.user)
  			.then(doc => {
  				this.notifications_settings = doc;
  				this.make();
  			});
  	}
  
  	make() {
  		this.$dropdown = $('.navbar').find('.dropdown-notifications');
  		this.$dropdown_list = this.$dropdown.find('.notifications-list');
  		this.$notification_indicator = this.$dropdown.find(
  			'.notifications-indicator'
  		);
  		this.user = frappe.session.user;
  		this.max_length = 20;
  
  		this.render_dropdown_headers();
  		this.$notifications = this.$dropdown_list.find(
  			'.category-list[data-category="Notifications"]'
  		);
  		this.$open_docs = this.$dropdown_list.find(
  			'.category-list[data-category="Open Documents"]'
  		);
  		this.$today_events = this.$dropdown_list.find(
  			'.category-list[data-category="Todays Events"]'
  		);
  
  		frappe.utils.bind_actions_with_object(this.$dropdown_list, this);
  		this.setup_notifications();
  		this.bind_events();
  	}
  
  	setup_notifications() {
  		this.get_notifications_list(this.max_length).then(list => {
  			this.dropdown_items = list;
  			this.render_notifications_dropdown();
  
  			if (this.$notifications.find('.unseen').length) {
  				this.$notification_indicator.show();
  			}
  		});
  	}
  
  	render_todays_events(e, $target) {
  		let hide = $target.next().hasClass('in');
  		if (!hide) {
  			let today = frappe.datetime.get_today();
  			frappe.xcall('frappe.desk.doctype.event.event.get_events', {
  				start: today,
  				end: today
  			}).then(event_list => {
  				this.render_events_html(event_list);
  			});
  		}
  	}
  
  	render_events_html(event_list) {
  		let html = '';
  		if (event_list.length) {
  			let get_event_html = event => {
  				let time = frappe.datetime.get_time(event.starts_on);
  				return `<a class="recent-item event" href="#Form/Event/${event.name}">
  					<span class="event-time bold">${time}</span>
  					<span class="event-subject">${event.subject}</span>
  				</a>`;
  			};
  			html = event_list.map(get_event_html).join('');
  		} else {
  			html = `<li class="recent-item text-center">
  					<span class="text-muted">${__('No Events Today')}</span>
  				</li>`;
  		}
  
  		this.$today_events.html(html);
  	}
  
  	get_open_document_config(e) {
  		this.open_docs_config = {
  			ToDo: { label: __('To Do') },
  			Event: { label: __('Calendar'), route: 'List/Event/Calendar' }
  		};
  
  		let hide = $(e.currentTarget)
  			.next()
  			.hasClass('in');
  		if (!hide) {
  			frappe.ui.notifications.get_notification_config().then(r => {
  				this.open_document_list = r;
  				this.render_open_document_count();
  			});
  		}
  	}
  
  	render_open_document_count() {
  		this.$open_docs.html('');
  		let defaults = ['ToDo'];
  		this.get_counts(this.open_document_list['open_count_doctype'], 1, defaults);
  		let targets = { doctypes: {} },
  			map = this.open_document_list['targets'];
  
  		Object.keys(map).map(doctype => {
  			Object.keys(map[doctype]).map(doc => {
  				targets[doc] = map[doctype][doc];
  				targets.doctypes[doc] = doctype;
  			});
  		});
  
  		this.get_counts(targets, 1, null, ['doctypes'], true);
  		this.get_counts(
  			this.open_document_list['open_count_doctype'],
  			0,
  			null,
  			defaults
  		);
  	}
  
  	get_counts(map, divide, keys, excluded = [], target = false) {
  		let empty_map = 1;
  		keys = keys
  			? keys
  			: Object.keys(map).sort().filter(e => !excluded.includes(e));
  		keys.map(key => {
  			let doc_dt = map.doctypes ? map.doctypes[key] : undefined;
  			if (map[key] > 0 || target) {
  				this.add_open_document_html(key, map[key], doc_dt, target);
  				empty_map = 0;
  			}
  		});
  
  		if (divide && !empty_map) {
  			this.$open_docs.append($('<li class="divider"></li>'));
  		}
  	}
  
  	add_open_document_html(name, value, doc_dt, target = false) {
  		let label = this.open_docs_config[name]
  			? this.open_docs_config[name].label
  			: name;
  		let title = target ? `title="${__('Your Target')}"` : '';
  		let $list_item = !target
  			? $(`<li><a class="badge-hover" data-action="route_to_document_type" data-doctype="${name}" ${title}>
  				${label}
  				<span class="badge pull-right">${value}</span>
  			</a></li>`)
  			: $(`<li><a class="progress-small" data-action="route_to_document_type" ${title}
  				data-doctype="${doc_dt}" data-docname="${name}">
  					<span class="dropdown-item-label">${label}<span>
  					<div class="progress-chart">
  						<div class="progress">
  							<div class="progress-bar" style="width: ${value}%"></div>
  						</div>
  					</div>
  			</a></li>`);
  
  		this.$open_docs.append($list_item);
  		if (!target) this.total += value;
  	}
  
  	route_to_document_type(e) {
  		this.$dropdown.removeClass('open');
  		this.$dropdown.trigger('hide.bs.dropdown');
  		let doctype = $(e.currentTarget).attr('data-doctype');
  		let docname = $(e.currentTarget).attr('data-docname');
  		if (!docname) {
  			let config = this.open_docs_config[doctype] || {};
  			if (config.route) {
  				frappe.set_route(config.route);
  			} else if (config.click) {
  				config.click();
  			} else {
  				frappe.ui.notifications.show_open_count_list(doctype);
  			}
  		} else {
  			frappe.set_route('Form', doctype, docname);
  		}
  	}
  
  	update_dropdown() {
  		this.get_notifications_list(1).then(r => {
  			let new_item = r[0];
  			this.dropdown_items.unshift(new_item);
  			if (this.dropdown_items.length > this.max_length) {
  				this.$dropdown_list
  					.find('.recent-notification')
  					.last()
  					.remove();
  				this.dropdown_items.pop();
  			}
  
  			this.insert_into_dropdown();
  		});
  	}
  
  	insert_into_dropdown() {
  		let new_item = this.dropdown_items[0];
  		let new_item_html = this.get_dropdown_item_html(new_item);
  		$(new_item_html).prependTo(this.$dropdown_list.find(this.$notifications));
  		this.change_activity_status();
  	}
  
  	change_activity_status() {
  		if (this.$dropdown_list.find('.activity-status')) {
  			this.$dropdown_list.find('.activity-status').replaceWith(
  				`<a class="recent-item text-center text-muted" 
  					href="#List/Notification Log">
  					<div class="full-log-btn">${__('View Full Log')}</div>
  				</a>`
  			);
  		}
  	}
  
  	set_field_as_seen(docname, $el) {
  		frappe.call(
  			'frappe.desk.doctype.notification_log.notification_log.mark_as_seen',
  			{ docname: docname }
  		).then(()=> {
  			$el.removeClass('unseen');
  		});
  	}
  
  	explicitly_mark_as_seen(e, $target) {
  		e.preventDefault();
  		e.stopImmediatePropagation();
  		let docname = $target.parents('.unseen').attr('data-name');
  		this.set_field_as_seen(docname, $target.parents('.unseen'));
  	}
  
  	mark_as_seen(e, $target) {
  		let docname = $target.attr('data-name');
  		let df = this.dropdown_items.filter(f => docname.includes(f.name))[0];
  		this.set_field_as_seen(df.name, $target);
  	}
  
  	get_notifications_list(limit) {
  		return frappe.db.get_list('Notification Log', {
  			fields: ['*'],
  			limit: limit,
  			order_by: 'creation desc'
  		});
  	}
  
  	render_notifications_dropdown() {
  		let body_html = '';
  		let view_full_log_html = '';
  		let dropdown_html;
  
  		if (this.notifications_settings && !this.notifications_settings.enabled) {
  			dropdown_html = `<li class="recent-item text-center">
  				<span class="text-muted">
  					${__('Notifications Disabled')}
  				</span></li>`;
  		} else {
  			if (this.dropdown_items.length) {
  				this.dropdown_items.forEach(field => {
  					let item_html = this.get_dropdown_item_html(field);
  					if (item_html) body_html += item_html;
  				});
  				view_full_log_html = `<a class="recent-item text-center text-muted"
  					href="#List/Notification Log">
  						<div class="full-log-btn">${__('View Full Log')}</div>
  					</a>`;
  			} else {
  				body_html += `<li class="recent-item text-center activity-status">
  					<span class="text-muted">
  						${__('No activity')}
  					</span></li>`;
  			}
  			dropdown_html = body_html + view_full_log_html;
  		}
  
  		this.$notifications.html(dropdown_html);
  	}
  
  	get_dropdown_item_html(field) {
  		let doc_link = frappe.utils.get_form_link(
  			field.document_type,
  			field.document_name
  		);
  		let seen_class = field.seen ? '' : 'unseen';
  		let mark_seen_action = field.seen ? '': 'data-action="mark_as_seen"';
  		let message = field.subject;
  		let title = message.match(/<b class="subject-title">(.*?)<\/b>/);
  		message = title ? message.replace(title[1], frappe.ellipsis(title[1], 100)): message;
  		let message_html = `<div class="message">${message}</div>`;
  		let user = field.from_user;
  		let user_avatar = frappe.avatar(user, 'avatar-small user-avatar');
  		let timestamp = frappe.datetime.comment_when(field.creation, true);
  		let item_html = 
  			`<a class="recent-item ${seen_class}" 
  				href="${doc_link}"
  				data-name="${field.name}"
  				${mark_seen_action}
  			>
  				${user_avatar}
  				${message_html}
  				<div class="notification-timestamp text-muted">
  					${timestamp}
  				</div>
  				<span class="mark-read text-muted" data-action="explicitly_mark_as_seen">
  					${__('Mark as Read')}
  				</span>
  			</a>`;
  
  		return item_html;
  	}
  
  	render_dropdown_headers() {
  		this.categories = [
  			{
  				label: __("Notifications"),
  				value: "Notifications"
  			},
  			{
  				label: __("Today's Events"),
  				value: "Todays Events",
  				action: "render_todays_events"
  			},
  			{
  				label: __("Open Documents"),
  				value: "Open Documents",
  				action: "get_open_document_config"
  			}
  		];
  
  		let get_headers_html = category => {
  			let category_id = frappe.dom.get_unique_id();
  			let settings_html =
  				category.value === 'Notifications'
  					? `<span class="notification-settings pull-right" data-action="make_and_route_to_settings">
  						${__('Settings')}
  					</span>`
  					: '';
  			let html = `<li class="notifications-category">
  					<li class="text-muted header"
  						data-action="${category.action}"
  						href="#${category_id}" 
  						data-toggle="collapse">
  						${category.label}
  						<span class="octicon octicon-chevron-down collapse-indicator"></span>
  						${settings_html}
  					</li>
  					<div id="${category_id}" class="collapse category-list" data-category="${category.value}">
  						<div class="text-center text-muted notifications-loading">
  							${__('Loading...')}
  						</div>
  					</div>
  				</li>`;
  
  			return html;
  		};
  
  		let html = this.categories
  			.map(get_headers_html)
  			.join('<li class="divider"></li>');
  		this.$dropdown_list.append(html);
  		this.$dropdown_list
  			.find('.category-list[data-category="Notifications"]')
  			.collapse('show');
  		this.toggle_collapse_indicator(
  			this.$dropdown_list.find('.category-list[data-category="Notifications"]')
  		);
  	}
  
  	make_and_route_to_settings(e) {
  		e.stopImmediatePropagation();
  		this.$dropdown.removeClass('open');
  		this.$dropdown.trigger('hide.bs.dropdown');
  		let method =
  			'frappe.desk.doctype.notification_settings.notification_settings.create_notification_settings';
  
  		return Promise.resolve()
  			.then(() => {
  				if (!this.notifications_settings) return frappe.call(method);
  			})
  			.then(() => {
  				frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
  			});
  	}
  
  	bind_events() {
  		this.setup_dropdown_events();
  		this.setup_notification_listeners();
  
  		this.$dropdown_list.on('click', '.recent-item', () => {
  			this.$dropdown.removeClass('open');
  		});
  
  		$('.category-list').on('hide.bs.collapse', e => {
  			this.toggle_collapse_indicator($(e.currentTarget));
  		});
  
  		$('.category-list').on('show.bs.collapse', e => {
  			this.toggle_collapse_indicator($(e.currentTarget));
  		});
  	}
  
  	setup_notification_listeners() {
  		frappe.realtime.on('notification', () => {
  			this.$dropdown.find('.notifications-indicator').show();
  			this.update_dropdown();
  		});
  
  		frappe.realtime.on('indicator_hide', () => {
  			this.$dropdown.find('.notifications-indicator').hide();
  		});
  	}
  
  	setup_dropdown_events() {
  		this.$dropdown_list
  			.find(
  				'[data-category="Notifications"], [data-category="Todays Events"], [data-category="Open Documents"]'
  			)
  			.collapse({
  				toggle: false
  			});
  		this.$dropdown.on('hide.bs.dropdown', e => {
  			let hide = $(e.currentTarget).data('closable');
  			if (hide) {
  				this.$dropdown_list
  					.find('[data-category="Notifications"]')
  					.collapse('show');
  				this.$dropdown_list
  					.find(
  						'[data-category="Todays Events"], [data-category="Open Documents"]'
  					)
  					.collapse('hide');
  			}
  			$(e.currentTarget).data('closable', true);
  			return hide;
  		});
  
  		this.$dropdown.on('show.bs.dropdown', () => {
  			if (this.$notification_indicator.is(':visible')) {
  				frappe.call(
  					'frappe.desk.doctype.notification_log.notification_log.trigger_indicator_hide'
  				);
  			}
  		});
  
  		this.$dropdown.on('click', e => {
  			if ($(e.target).closest('.dropdown-toggle').length) {
  				$(e.currentTarget).data('closable', true);
  			} else {
  				$(e.currentTarget).data('closable', false);
  			}
  		});
  	}
  
  	toggle_collapse_indicator($el) {
  		$el
  			.prev()
  			.find('.collapse-indicator')
  			.toggleClass('octicon-chevron-down');
  		$el
  			.prev()
  			.find('.collapse-indicator')
  			.toggleClass('octicon-chevron-up');
  	}
  };
  
  
  frappe.ui.notifications = {
  	get_notification_config() {
  		return frappe.xcall('frappe.desk.notifications.get_notification_info').then(r => {
  			frappe.ui.notifications.config = r;
  			return r;
  		});
  	},
  
  	show_open_count_list(doctype) {
  		if (!frappe.ui.notifications.config) {
  			this.get_notification_config().then(()=> {
  				this.route_to_list_with_filters(doctype);
  			});
  		} else {
  			this.route_to_list_with_filters(doctype);
  		}
  	},
  
  	route_to_list_with_filters(doctype) {
  		let filters = frappe.ui.notifications.config['conditions'][doctype];
  		if (filters && $.isPlainObject(filters)) {
  			if (!frappe.route_options) {
  				frappe.route_options = {};
  			}
  			$.extend(frappe.route_options, filters);
  		}
  		frappe.set_route('List', doctype);
  	}
  };