text: import BulkOperations from "./bulk_operations";
  import ListSettings from "./list_settings";
  
  frappe.provide("frappe.views");
  
  frappe.views.ListView = class ListView extends frappe.views.BaseList {
  	static load_last_view() {
  		const route = frappe.get_route();
  		const doctype = route[1];
  
  		if (route.length === 2) {
  			const user_settings = frappe.get_user_settings(doctype);
  			const last_view = user_settings.last_view;
  			frappe.set_route(
  				"list",
  				frappe.router.doctype_layout || doctype,
  				frappe.views.is_valid(last_view) ? last_view.toLowerCase() : "list"
  			);
  			return true;
  		}
  		return false;
  	}
  
  	constructor(opts) {
  		super(opts);
  		this.show();
  	}
  
  	has_permissions() {
  		const can_read = frappe.perm.has_perm(this.doctype, 0, "read");
  		return can_read;
  	}
  
  	show() {
  		this.parent.disable_scroll_to_top = true;
  
  		if (!this.has_permissions()) {
  			frappe.set_route('');
  			frappe.msgprint(__("Not permitted to view {0}", [this.doctype]));
  			return;
  		}
  
  		super.show();
  	}
  
  	get view_name() {
  		return "List";
  	}
  
  	get view_user_settings() {
  		return this.user_settings[this.view_name] || {};
  	}
  
  	setup_defaults() {
  		super.setup_defaults();
  
  		this.view = "List";
  		// initialize with saved order by
  		this.sort_by = this.view_user_settings.sort_by || "modified";
  		this.sort_order = this.view_user_settings.sort_order || "desc";
  
  		// set filters from user_settings or list_settings
  		if (
  			this.view_user_settings.filters &&
  			this.view_user_settings.filters.length
  		) {
  			// Priority 1: user_settings
  			const saved_filters = this.view_user_settings.filters;
  			this.filters = this.validate_filters(saved_filters);
  		} else {
  			// Priority 2: filters in listview_settings
  			this.filters = (this.settings.filters || []).map((f) => {
  				if (f.length === 3) {
  					f = [this.doctype, f[0], f[1], f[2]];
  				}
  				return f;
  			});
  		}
  
  		// build menu items
  		this.menu_items = this.menu_items.concat(this.get_menu_items());
  
  		if (
  			this.view_user_settings.filters &&
  			this.view_user_settings.filters.length
  		) {
  			// Priority 1: saved filters
  			const saved_filters = this.view_user_settings.filters;
  			this.filters = this.validate_filters(saved_filters);
  		} else {
  			// Priority 2: filters in listview_settings
  			this.filters = (this.settings.filters || []).map((f) => {
  				if (f.length === 3) {
  					f = [this.doctype, f[0], f[1], f[2]];
  				}
  				return f;
  			});
  		}
  
  		if (this.view_name == 'List') this.toggle_paging = true;
  
  		this.patch_refresh_and_load_lib();
  		return this.get_list_view_settings();
  	}
  
  	get_list_view_settings() {
  		return frappe
  			.call("frappe.desk.listview.get_list_settings", {
  				doctype: this.doctype,
  			})
  			.then((doc) => (this.list_view_settings = doc.message || {}));
  	}
  
  	on_sort_change(sort_by, sort_order) {
  		this.sort_by = sort_by;
  		this.sort_order = sort_order;
  		super.on_sort_change();
  	}
  
  	validate_filters(filters) {
  		let valid_fields = this.meta.fields.map(df => df.fieldname);
  		valid_fields = valid_fields.concat(frappe.model.std_fields_list);
  		return filters
  			.filter((f) => valid_fields.includes(f[1]))
  			.uniqBy((f) => f[1]);
  	}
  
  	setup_page() {
  		this.parent.list_view = this;
  		super.setup_page();
  	}
  
  	setup_page_head() {
  		super.setup_page_head();
  		this.set_primary_action();
  		this.set_actions_menu_items();
  	}
  
  	set_actions_menu_items() {
  		this.actions_menu_items = this.get_actions_menu_items();
  		this.workflow_action_menu_items = this.get_workflow_action_menu_items();
  		this.workflow_action_items = {};
  
  		const actions = this.actions_menu_items.concat(
  			this.workflow_action_menu_items
  		);
  		actions.map((item) => {
  			const $item = this.page.add_actions_menu_item(
  				item.label,
  				item.action,
  				item.standard
  			);
  			if (item.class) {
  				$item.addClass(item.class);
  			}
  			if (item.is_workflow_action && $item) {
  				// can be used to dynamically show or hide action
  				this.workflow_action_items[item.name] = $item;
  			}
  		});
  	}
  
  	show_restricted_list_indicator_if_applicable() {
  		const match_rules_list = frappe.perm.get_match_rules(this.doctype);
  		if (match_rules_list.length) {
  			this.restricted_list = $(
  				`<button class="btn btn-default btn-xs restricted-button flex align-center">
  					${frappe.utils.icon('restriction', 'xs')}
  				</button>`
  			).click(() => this.show_restrictions(match_rules_list)).appendTo(this.page.page_form);
  		}
  	}
  
  	show_restrictions(match_rules_list = []) {
  		frappe.msgprint(
  			frappe.render_template("list_view_permission_restrictions", {
  				condition_list: match_rules_list,
  			}),
  			__("Restrictions")
  		);
  	}
  
  	set_fields() {
  		let fields = [].concat(
  			frappe.model.std_fields_list,
  			this.get_fields_in_list_view(),
  			[this.meta.title_field, this.meta.image_field],
  			this.settings.add_fields || [],
  			this.meta.track_seen ? "_seen" : null,
  			this.sort_by,
  			"enabled",
  			"disabled",
  			"color"
  		);
  
  		fields.forEach((f) => this._add_field(f));
  
  		this.fields.forEach((f) => {
  			const df = frappe.meta.get_docfield(f[1], f[0]);
  			if (
  				df &&
  				df.fieldtype === "Currency" &&
  				df.options &&
  				!df.options.includes(":")
  			) {
  				this._add_field(df.options);
  			}
  		});
  	}
  
  	patch_refresh_and_load_lib() {
  		// throttle refresh for 1s
  		this.refresh = this.refresh.bind(this);
  		this.refresh = frappe.utils.throttle(this.refresh, 1000);
  		this.load_lib = new Promise((resolve) => {
  			if (this.required_libs) {
  				frappe.require(this.required_libs, resolve);
  			} else {
  				resolve();
  			}
  		});
  		// call refresh every 5 minutes
  		const interval = 5 * 60 * 1000;
  		setInterval(() => {
  			// don't call if route is different
  			if (frappe.get_route_str() === this.page_name) {
  				this.refresh();
  			}
  		}, interval);
  	}
  
  	set_primary_action() {
  		if (this.can_create) {
  			this.page.set_primary_action(
  				`${__("Add")} ${frappe.router.doctype_layout || __(this.doctype)}`,
  				() => {
  					if (this.settings.primary_action) {
  						this.settings.primary_action();
  					} else {
  						this.make_new_doc();
  					}
  				},
  				"add"
  			);
  		} else {
  			this.page.clear_primary_action();
  		}
  	}
  
  	make_new_doc() {
  		const doctype = this.doctype;
  		const options = {};
  		this.filter_area.get().forEach((f) => {
  			if (f[2] === "=" && frappe.model.is_non_std_field(f[1])) {
  				options[f[1]] = f[3];
  			}
  		});
  		frappe.new_doc(doctype, options);
  	}
  
  	setup_view() {
  		this.setup_columns();
  		this.render_header();
  		this.render_skeleton();
  		this.setup_events();
  		this.settings.onload && this.settings.onload(this);
  		this.show_restricted_list_indicator_if_applicable();
  	}
  
  	refresh_columns(meta, list_view_settings) {
  		this.meta = meta;
  		this.list_view_settings = list_view_settings;
  
  		this.setup_columns();
  		this.refresh(true);
  	}
  
  	refresh(refresh_header=false) {
  		super.refresh().then(() => {
  			this.render_header(refresh_header);
  		});
  	}
  
  	setup_freeze_area() {
  		this.$freeze = $(
  			`<div class="freeze flex justify-center align-center text-muted">${__(
  				"Loading"
  			)}...</div>`
  		).hide();
  		this.$result.append(this.$freeze);
  	}
  
  	setup_columns() {
  		// setup columns for list view
  		this.columns = [];
  
  		const get_df = frappe.meta.get_docfield.bind(null, this.doctype);
  
  		// 1st column: title_field or name
  		if (this.meta.title_field) {
  			this.columns.push({
  				type: "Subject",
  				df: get_df(this.meta.title_field),
  			});
  		} else {
  			this.columns.push({
  				type: "Subject",
  				df: {
  					label: __("Name"),
  					fieldname: "name",
  				},
  			});
  		}
  
  
  		this.columns.push({
  			type: "Tag"
  		});
  
  		// 2nd column: Status indicator
  		if (frappe.has_indicator(this.doctype)) {
  			// indicator
  			this.columns.push({
  				type: "Status",
  			});
  		}
  
  		const fields_in_list_view = this.get_fields_in_list_view();
  		// Add rest from in_list_view docfields
  		this.columns = this.columns.concat(
  			fields_in_list_view
  				.filter((df) => {
  					if (
  						frappe.has_indicator(this.doctype) &&
  						df.fieldname === "status"
  					) {
  						return false;
  					}
  					if (!df.in_list_view) {
  						return false;
  					}
  					return df.fieldname !== this.meta.title_field;
  				})
  				.map((df) => ({
  					type: "Field",
  					df,
  				}))
  		);
  
  		if (this.list_view_settings.fields) {
  			this.columns = this.reorder_listview_fields();
  		}
  
  		// limit max to 8 columns if no total_fields is set in List View Settings
  		// Screen with low density no of columns 4
  		// Screen with medium density no of columns 6
  		// Screen with high density no of columns 8
  		let total_fields = 6;
  
  		if (window.innerWidth <= 1366) {
  			total_fields = 4;
  		} else if (window.innerWidth >= 1920) {
  			total_fields = 8;
  		}
  
  		this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields);
  
  		if (
  			!this.settings.hide_name_column &&
  			this.meta.title_field !== 'name'
  		) {
  			this.columns.push({
  				type: "Field",
  				df: {
  					label: __("Name"),
  					fieldname: "name",
  				},
  			});
  		}
  	}
  
  	reorder_listview_fields() {
  		let fields_order = [];
  		let fields = JSON.parse(this.list_view_settings.fields);
  
  		//title and tags field is fixed
  		fields_order.push(this.columns[0]);
  		fields_order.push(this.columns[1]);
  		this.columns.splice(0, 2);
  
  		for (let fld in fields) {
  			for (let col in this.columns) {
  				let field = fields[fld];
  				let column = this.columns[col];
  
  				if (column.type == "Status" && field.fieldname == "status_field") {
  					fields_order.push(column);
  					break;
  				} else if (column.type == "Field" && field.fieldname === column.df.fieldname) {
  					fields_order.push(column);
  					break;
  				}
  			}
  		}
  
  		return fields_order;
  	}
  
  	get_documentation_link() {
  		if (this.meta.documentation) {
  			return `<a href="${
  				this.meta.documentation
  			}" target="blank" class="meta-description small text-muted">Need Help?</a>`;
  		}
  		return "";
  	}
  
  	get_no_result_message() {
  		let help_link = this.get_documentation_link();
  		let filters = this.filter_area.get();
  		let no_result_message = filters.length
  			? __("No {0} found", [__(this.doctype)])
  			: __("You haven't created a {0} yet", [__(this.doctype)]);
  		let new_button_label = filters.length
  			? __("Create a new {0}", [__(this.doctype)])
  			: __("Create your first {0}", [__(this.doctype)]);
  		let empty_state_image =
  			this.settings.empty_state_image ||
  			"/assets/frappe/images/ui-states/list-empty-state.svg";
  
  		const new_button = this.can_create
  			? `<p><button class="btn btn-primary btn-sm btn-new-doc hidden-xs">
  				${new_button_label}
  			</button> <button class="btn btn-primary btn-new-doc visible-xs">${__('Create New')}</button></p>`
  			: "";
  
  		return `<div class="msg-box no-border">
  			<div>
  				<img src="${empty_state_image}" alt="Generic Empty State" class="null-state">
  			</div>
  			<p>${no_result_message}</p>
  			${new_button}
  			${help_link}
  		</div>`;
  	}
  
  	freeze() {
  		if (this.list_view_settings && !this.list_view_settings.disable_count) {
  			this.$result
  				.find(".list-count")
  				.html(`<span>${__("Refreshing")}...</span>`);
  		}
  	}
  
  	get_args() {
  		const args = super.get_args();
  
  		return Object.assign(args, {
  			with_comment_count: true,
  		});
  	}
  
  	before_refresh() {
  		if (frappe.route_options) {
  			this.filters = this.parse_filters_from_route_options();
  			frappe.route_options = null;
  
  			if (this.filters.length > 0) {
  				return this.filter_area
  					.clear(false)
  					.then(() => this.filter_area.set(this.filters));
  			}
  		}
  
  		return Promise.resolve();
  	}
  
  	parse_filters_from_settings() {
  		return (this.settings.filters || []).map((f) => {
  			if (f.length === 3) {
  				f = [this.doctype, f[0], f[1], f[2]];
  			}
  			return f;
  		});
  	}
  
  	toggle_result_area() {
  		super.toggle_result_area();
  		this.toggle_actions_menu_button(
  			this.$result.find(".list-row-check:checked").length > 0
  		);
  	}
  
  	toggle_actions_menu_button(toggle) {
  		if (toggle) {
  			this.page.show_actions_menu();
  			this.page.clear_primary_action();
  			this.toggle_workflow_actions();
  		} else {
  			this.page.hide_actions_menu();
  			this.set_primary_action();
  		}
  	}
  
  	render_header(refresh_header=false) {
  		if (refresh_header) {
  			this.$result.find('.list-row-head').remove();
  		}
  		if (this.$result.find(".list-row-head").length === 0) {
  			// append header once
  			this.$result.prepend(this.get_header_html());
  		}
  	}
  
  	render_skeleton() {
  		const $row = this.get_list_row_html_skeleton(
  			'<div><input type="checkbox" /></div>'
  		);
  		this.$result.append($row);
  	}
  
  	before_render() {
  		this.settings.before_render && this.settings.before_render();
  		frappe.model.user_settings.save(
  			this.doctype,
  			"last_view",
  			this.view_name
  		);
  		this.save_view_user_settings({
  			filters: this.filter_area.get(),
  			sort_by: this.sort_selector.sort_by,
  			sort_order: this.sort_selector.sort_order,
  		});
  		this.toggle_paging && this.$paging_area.toggle(false);
  	}
  
  	after_render() {
  		this.$no_result.html(`
  			<div class="no-result text-muted flex justify-center align-center">
  				${this.get_no_result_message()}
  			</div>
  		`);
  		this.setup_new_doc_event();
  		this.list_sidebar && this.list_sidebar.reload_stats();
  		this.toggle_paging && this.$paging_area.toggle(true);
  	}
  
  	render() {
  		this.render_list();
  		this.on_row_checked();
  		this.render_count();
  	}
  
  	render_list() {
  		// clear rows
  		this.$result.find(".list-row-container").remove();
  		if (this.data.length > 0) {
  			// append rows
  			this.$result.append(
  				this.data
  					.map((doc, i) => {
  						doc._idx = i;
  						return this.get_list_row_html(doc);
  					})
  					.join("")
  			);
  		}
  	}
  
  	render_count() {
  		if (!this.list_view_settings.disable_count) {
  			this.get_count_str().then((str) => {
  				this.$result.find(".list-count").html(`<span>${str}</span>`);
  			});
  		}
  	}
  
  	get_header_html() {
  		if (!this.columns) {
  			return;
  		}
  
  		const subject_field = this.columns[0].df;
  		let subject_html = `
  			<input class="level-item list-check-all hidden-xs" type="checkbox"
  				title="${__("Select All")}">
  			<span class="level-item list-liked-by-me">
  				<span title="${__("Likes")}">${frappe.utils.icon('heart', 'sm', 'like-icon')}</span>
  			</span>
  			<span class="level-item">${__(subject_field.label)}</span>
  		`;
  		const $columns = this.columns
  			.map(col => {
  				let classes = [
  					"list-row-col ellipsis",
  					col.type == "Subject" ? "list-subject level" : "hidden-xs",
  					col.type == "Tag" ? "tag-col hide": "",
  					frappe.model.is_numeric_field(col.df) ? "text-right" : "",
  				].join(" ");
  
  				return `
  				<div class="${classes}">
  					${col.type === "Subject" ? subject_html : `
  						<span>${__((col.df && col.df.label) || col.type)}</span>`}
  				</div>
  			`;
  			})
  			.join("");
  
  		return this.get_header_html_skeleton(
  			$columns,
  			'<span class="list-count"></span>'
  		);
  	}
  
  	get_header_html_skeleton(left = "", right = "") {
  		return `
  			<header class="level list-row-head text-muted">
  				<div class="level-left list-header-subject">
  					${left}
  				</div>
  				<div class="level-left checkbox-actions">
  					<div class="level list-subject">
  						<input class="level-item list-check-all hidden-xs" type="checkbox"
  							title="${__("Select All")}">
  						<span class="level-item list-header-meta"></span>
  					</div>
  				</div>
  				<div class="level-right">
  					${right}
  				</div>
  			</header>
  		`;
  	}
  
  	get_left_html(doc) {
  		return this.columns
  			.map((col) => this.get_column_html(col, doc))
  			.join("");
  	}
  
  	get_right_html(doc) {
  		return this.get_meta_html(doc);
  	}
  
  	get_list_row_html(doc) {
  		return this.get_list_row_html_skeleton(
  			this.get_left_html(doc),
  			this.get_right_html(doc)
  		);
  	}
  
  	get_list_row_html_skeleton(left = "", right = "") {
  		return `
  			<div class="list-row-container" tabindex="1">
  				<div class="level list-row">
  					<div class="level-left ellipsis">
  						${left}
  					</div>
  					<div class="level-right text-muted ellipsis">
  						${right}
  					</div>
  				</div>
  			</div>
  		`;
  	}
  
  	get_column_html(col, doc) {
  		if (col.type === "Status") {
  			return `
  				<div class="list-row-col hidden-xs ellipsis">
  					${this.get_indicator_html(doc)}
  				</div>
  			`;
  		}
  
  		if (col.type === "Tag") {
  			const tags_display_class = !this.tags_shown ? 'hide' : '';
  			let tags_html = doc._user_tags ? this.get_tags_html(doc._user_tags) : '<div class="tags-empty">-</div>';
  			return `
  				<div class="list-row-col tag-col ${tags_display_class} hidden-xs ellipsis">
  					${tags_html}
  				</div>
  			`;
  		}
  
  		const df = col.df || {};
  		const label = df.label;
  		const fieldname = df.fieldname;
  		const value = doc[fieldname] || "";
  
  		const format = () => {
  			if (df.fieldtype === "Code") {
  				return value;
  			} else if (df.fieldtype === "Percent") {
  				return `<div class="progress" style="margin: 0px;">
  						<div class="progress-bar progress-bar-success" role="progressbar"
  							aria-valuenow="${value}"
  							aria-valuemin="0" aria-valuemax="100" style="width: ${Math.round(value)}%;">
  						</div>
  					</div>`;
  			} else {
  				return frappe.format(value, df, null, doc);
  			}
  		};
  
  		const field_html = () => {
  			let html;
  			let _value;
  			// listview_setting formatter
  			if (
  				this.settings.formatters &&
  				this.settings.formatters[fieldname]
  			) {
  				_value = this.settings.formatters[fieldname](value, df, doc);
  			} else {
  				let strip_html_required =
  					df.fieldtype == "Text Editor" ||
  					(df.fetch_from &&
  						["Text", "Small Text"].includes(df.fieldtype));
  				if (strip_html_required) {
  					_value = strip_html(value);
  				} else {
  					_value =
  						typeof value === "string"
  							? frappe.utils.escape_html(value)
  							: value;
  				}
  			}
  
  			if (df.fieldtype === "Image") {
  				html = df.options ? `<img src="${doc[df.options]}"
  					style="max-height: 30px; max-width: 100%;">`
  					: `<div class="missing-image small">
  						<span class="octicon octicon-circle-slash"></span>
  					</div>`;
  			} else if (df.fieldtype === "Select") {
  				html = `<span class="filterable indicator-pill ${frappe.utils.guess_colour(
  					_value
  				)} ellipsis"
  					data-filter="${fieldname},=,${value}">
  					<span class="ellipsis"> ${__(_value)} </span>
  				</span>`;
  			} else if (df.fieldtype === "Link") {
  				html = `<a class="filterable ellipsis"
  					data-filter="${fieldname},=,${value}">
  					${_value}
  				</a>`;
  			} else if (
  				["Text Editor", "Text", "Small Text", "HTML Editor"].includes(
  					df.fieldtype
  				)
  			) {
  				html = `<span class="ellipsis">
  					${_value}
  				</span>`;
  			} else {
  				html = `<a class="filterable ellipsis"
  					data-filter="${fieldname},=,${value}">
  					${format()}
  				</a>`;
  			}
  
  			return `<span class="ellipsis"
  				title="${__(label)}: ${frappe.utils.escape_html(_value)}">
  				${html}
  			</span>`;
  		};
  
  		const class_map = {
  			Subject: "list-subject level",
  			Field: "hidden-xs",
  		};
  		const css_class = [
  			"list-row-col ellipsis",
  			class_map[col.type],
  			frappe.model.is_numeric_field(df) ? "text-right" : "",
  		].join(" ");
  
  		const html_map = {
  			Subject: this.get_subject_html(doc),
  			Field: field_html(),
  		};
  		const column_html = html_map[col.type];
  
  		return `
  			<div class="${css_class}">
  				${column_html}
  			</div>
  		`;
  	}
  
  	get_tags_html(user_tags) {
  		let get_tag_html = tag => {
  			if (tag) {
  				return `<div class="tag-pill ellipsis" title="${tag}">${tag}</div>`;
  			}
  		};
  		return user_tags.split(',').slice(1, 3).map(get_tag_html).join('');
  	}
  
  	get_meta_html(doc) {
  		let html = "";
  
  		let settings_button = null;
  		if (this.settings.button && this.settings.button.show(doc)) {
  			settings_button = `
  				<span class="list-actions">
  					<button class="btn btn-action btn-default btn-xs"
  						data-name="${doc.name}" data-idx="${doc._idx}"
  						title="${this.settings.button.get_description(doc)}">
  						${this.settings.button.get_label(doc)}
  					</button>
  				</span>
  			`;
  		}
  
  		const modified = comment_when(doc.modified, true);
  
  		let assigned_to = `<div class="list-assignments">
  			<span class="avatar avatar-small">
  			<span class="avatar-empty"></span>
  		</div>`;
  
  		let assigned_users = JSON.parse(doc._assign || "[]");
  		if (assigned_users.length) {
  			assigned_to = `<div class="list-assignments">
  					${frappe.avatar_group(assigned_users, 3, { filterable: true })[0].outerHTML}
  				</div>`;
  		}
  
  		const comment_count = `<span class="${
  			!doc._comment_count ? "text-extra-muted" : ""
  		} comment-count">
  				${frappe.utils.icon('small-message')}
  				${doc._comment_count > 99 ? "99+" : doc._comment_count}
  			</span>`;
  
  		html += `
  			<div class="level-item list-row-activity hidden-xs">
  				<div class="hidden-md hidden-xs">
  					${settings_button || assigned_to}
  				</div>
  				${modified}
  				${comment_count}
  			</div>
  			<div class="level-item visible-xs text-right">
  				${this.get_indicator_dot(doc)}
  			</div>
  		`;
  
  		return html;
  	}
  
  	get_count_str() {
  		let current_count = this.data.length;
  		let count_without_children = this.data.uniqBy((d) => d.name).length;
  
  		return frappe.db.count(this.doctype, {
  			filters: this.get_filters_for_args()
  		}).then(total_count => {
  			this.total_count = total_count || current_count;
  			let str = __('{0} of {1}', [current_count, this.total_count]);
  			if (count_without_children !== current_count) {
  				str = __('{0} of {1} ({2} rows with children)', [count_without_children, this.total_count, current_count]);
  			}
  			return str;
  		});
  	}
  
  	get_form_link(doc) {
  		if (this.settings.get_form_link) {
  			return this.settings.get_form_link(doc);
  		}
  
  		const docname = doc.name.match(/[%'"\s]/)
  			? encodeURIComponent(doc.name)
  			: doc.name;
  
  		return `/app/${frappe.router.slug(frappe.router.doctype_layout || this.doctype)}/${docname}`;
  	}
  
  	get_seen_class(doc) {
  		return JSON.parse(doc._seen || '[]').includes(frappe.session.user)
  			? ''
  			: 'bold';
  	}
  
  	get_like_html(doc) {
  		const liked_by = JSON.parse(doc._liked_by || "[]");
  		let heart_class = liked_by.includes(frappe.session.user)
  			? "liked-by liked"
  			: "not-liked";
  
  		return `<span
  			class="like-action ${heart_class}"
  			data-name="${doc.name}" data-doctype="${this.doctype}"
  			data-liked-by="${encodeURI(doc._liked_by) || "[]"}"
  			title="${liked_by.map(u => frappe.user_info(u).fullname).join(', ')}">
  			${frappe.utils.icon('heart', 'sm', 'like-icon')}
  		</span>
  		<span class="likes-count">
  			${liked_by.length > 99 ? __("99") + "+" : __(liked_by.length || "")}
  		</span>`;
  	}
  
  	get_subject_html(doc) {
  		let subject_field = this.columns[0].df;
  		let value = doc[subject_field.fieldname] || doc.name;
  		let subject = strip_html(value.toString());
  		let escaped_subject = frappe.utils.escape_html(subject);
  
  		const seen = this.get_seen_class(doc);
  
  		let subject_html = `
  			<input class="level-item list-row-checkbox hidden-xs" type="checkbox"
  				data-name="${escape(doc.name)}">
  			<span class="level-item" style="margin-bottom: 1px;">
  				${this.get_like_html(doc)}
  			</span>
  			<span class="level-item ${seen} ellipsis" title="${escaped_subject}">
  				<a class="ellipsis"
  					href="${this.get_form_link(doc)}"
  					title="${escaped_subject}"
  					data-doctype="${this.doctype}"
  					data-name="${doc.name}">
  					${subject}
  				</a>
  			</span>
  		`;
  
  		return subject_html;
  	}
  
  	get_indicator_html(doc) {
  		const indicator = frappe.get_indicator(doc, this.doctype);
  		if (indicator) {
  			return `<span class="indicator-pill ${indicator[1]} filterable ellipsis"
  				data-filter='${indicator[2]}'>
  				<span class="ellipsis"> ${__(indicator[0])}</span>
  			<span>`;
  		}
  		return "";
  	}
  
  	get_indicator_dot(doc) {
  		const indicator = frappe.get_indicator(doc, this.doctype);
  		if (!indicator) return "";
  		return `<span class='indicator ${indicator[1]}' title='${__(
  			indicator[0]
  		)}'></span>`;
  	}
  
  	setup_events() {
  		this.setup_filterable();
  		this.setup_list_click();
  		this.setup_tag_event();
  		this.setup_new_doc_event();
  		this.setup_check_events();
  		this.setup_like();
  		this.setup_realtime_updates();
  		this.setup_action_handler();
  		this.setup_keyboard_navigation();
  	}
  
  	setup_keyboard_navigation() {
  		let focus_first_row = () => {
  			this.$result.find(".list-row-container:first").focus();
  		};
  		let focus_next = () => {
  			$(document.activeElement)
  				.next()
  				.focus();
  		};
  		let focus_prev = () => {
  			$(document.activeElement)
  				.prev()
  				.focus();
  		};
  		let list_row_focused = () => {
  			return $(document.activeElement).is(".list-row-container");
  		};
  		let check_row = ($row) => {
  			let $input = $row.find("input[type=checkbox]");
  			$input.click();
  		};
  		let get_list_row_if_focused = () =>
  			list_row_focused() ? $(document.activeElement) : null;
  
  		let is_current_page = () => this.page.wrapper.is(":visible");
  		let is_input_focused = () => $(document.activeElement).is("input");
  
  		let handle_navigation = (direction) => {
  			if (!is_current_page() || is_input_focused()) return false;
  
  			let $list_row = get_list_row_if_focused();
  			if ($list_row) {
  				direction === "down" ? focus_next() : focus_prev();
  			} else {
  				focus_first_row();
  			}
  		};
  
  		frappe.ui.keys.add_shortcut({
  			shortcut: "down",
  			action: () => handle_navigation("down"),
  			description: __("Navigate list down"),
  			page: this.page,
  		});
  
  		frappe.ui.keys.add_shortcut({
  			shortcut: "up",
  			action: () => handle_navigation("up"),
  			description: __("Navigate list up"),
  			page: this.page,
  		});
  
  		frappe.ui.keys.add_shortcut({
  			shortcut: "shift+down",
  			action: () => {
  				if (!is_current_page() || is_input_focused()) return false;
  				let $list_row = get_list_row_if_focused();
  				check_row($list_row);
  				focus_next();
  			},
  			description: __("Select multiple list items"),
  			page: this.page,
  		});
  
  		frappe.ui.keys.add_shortcut({
  			shortcut: "shift+up",
  			action: () => {
  				if (!is_current_page() || is_input_focused()) return false;
  				let $list_row = get_list_row_if_focused();
  				check_row($list_row);
  				focus_prev();
  			},
  			description: __("Select multiple list items"),
  			page: this.page,
  		});
  
  		frappe.ui.keys.add_shortcut({
  			shortcut: "enter",
  			action: () => {
  				let $list_row = get_list_row_if_focused();
  				if ($list_row) {
  					$list_row.find("a[data-name]")[0].click();
  					return true;
  				}
  				return false;
  			},
  			description: __("Open list item"),
  			page: this.page,
  		});
  
  		frappe.ui.keys.add_shortcut({
  			shortcut: "space",
  			action: () => {
  				let $list_row = get_list_row_if_focused();
  				if ($list_row) {
  					check_row($list_row);
  					return true;
  				}
  				return false;
  			},
  			description: __("Select list item"),
  			page: this.page,
  		});
  	}
  
  	setup_filterable() {
  		// filterable events
  		this.$result.on("click", ".filterable", (e) => {
  			if (e.metaKey || e.ctrlKey) return;
  			e.stopPropagation();
  			const $this = $(e.currentTarget);
  			const filters = $this.attr("data-filter").split("|");
  			const filters_to_apply = filters.map((f) => {
  				f = f.split(",");
  				if (f[2] === "Today") {
  					f[2] = frappe.datetime.get_today();
  				} else if (f[2] == "User") {
  					f[2] = frappe.session.user;
  				}
  				this.filter_area.remove(f[0]);
  				return [this.doctype, f[0], f[1], f.slice(2).join(",")];
  			});
  			this.filter_area.add(filters_to_apply);
  		});
  	}
  
  	setup_list_click() {
  		this.$result.on("click", ".list-row, .image-view-header, .file-header", (e) => {
  			const $target = $(e.target);
  			// tick checkbox if Ctrl/Meta key is pressed
  			if (e.ctrlKey || (e.metaKey && !$target.is("a"))) {
  				const $list_row = $(e.currentTarget);
  				const $check = $list_row.find(".list-row-checkbox");
  				$check.prop("checked", !$check.prop("checked"));
  				e.preventDefault();
  				this.on_row_checked();
  				return;
  			}
  			// don't open form when checkbox, like, filterable are clicked
  			if (
  				$target.hasClass("filterable") ||
  				$target.hasClass("icon-heart") ||
  				$target.is(":checkbox")
  			) {
  				e.stopPropagation();
  				return;
  			}
  
  			// link, let the event be handled via set_route
  			if ($target.is("a")) return;
  
  			// clicked on the row, open form
  			const $row = $(e.currentTarget);
  			const link = $row.find(".list-subject a").get(0);
  			if (link) {
  				frappe.set_route(link.pathname);
  				return false;
  			}
  		});
  	}
  
  	setup_action_handler() {
  		this.$result.on("click", ".btn-action", (e) => {
  			const $button = $(e.currentTarget);
  			const doc = this.data[$button.attr("data-idx")];
  			this.settings.button.action(doc);
  			e.stopPropagation();
  			return false;
  		});
  	}
  
  	setup_check_events() {
  		this.$result.on("change", "input[type=checkbox]", (e) => {
  			const $target = $(e.currentTarget);
  
  			if ($target.is(".list-header-subject .list-check-all")) {
  				const $check = this.$result.find(
  					".checkbox-actions .list-check-all"
  				);
  				$check.prop("checked", $target.prop("checked"));
  				$check.trigger("change");
  			} else if ($target.is(".checkbox-actions .list-check-all")) {
  				const $check = this.$result.find(
  					".list-header-subject .list-check-all"
  				);
  				$check.prop("checked", $target.prop("checked"));
  
  				this.$result
  					.find(".list-row-checkbox")
  					.prop("checked", $target.prop("checked"));
  			} else if ($target.attr('data-parent')) {
  				this.$result
  					.find(`.${$target.attr('data-parent')}`)
  					.find('.list-row-checkbox')
  					.prop("checked", $target.prop("checked"));
  			}
  
  			this.on_row_checked();
  		});
  
  		this.$result.on("click", ".list-row-checkbox", (e) => {
  			const $target = $(e.currentTarget);
  
  			// shift select checkboxes
  			if (
  				e.shiftKey &&
  				this.$checkbox_cursor &&
  				!$target.is(this.$checkbox_cursor)
  			) {
  				const name_1 = this.$checkbox_cursor.data().name;
  				const name_2 = $target.data().name;
  				const index_1 = this.data.findIndex((d) => d.name === name_1);
  				const index_2 = this.data.findIndex((d) => d.name === name_2);
  				let [min_index, max_index] = [index_1, index_2];
  
  				if (min_index > max_index) {
  					[min_index, max_index] = [max_index, min_index];
  				}
  
  				let docnames = this.data
  					.slice(min_index + 1, max_index)
  					.map((d) => d.name);
  				const selector = docnames
  					.map((name) => `.list-row-checkbox[data-name="${name}"]`)
  					.join(",");
  				this.$result.find(selector).prop("checked", true);
  			}
  
  			this.$checkbox_cursor = $target;
  		});
  	}
  
  	setup_like() {
  		this.$result.on("click", ".like-action", frappe.ui.click_toggle_like);
  		this.$result.on("click", ".list-liked-by-me", (e) => {
  			const $this = $(e.currentTarget);
  			$this.toggleClass("active");
  
  			if ($this.hasClass("active")) {
  				this.filter_area.add(
  					this.doctype,
  					"_liked_by",
  					"like",
  					"%" + frappe.session.user + "%"
  				);
  			} else {
  				this.filter_area.remove("_liked_by");
  			}
  		});
  
  	}
  
  	setup_new_doc_event() {
  		this.$no_result.find(".btn-new-doc").click(() => {
  			if (this.settings.primary_action) {
  				this.settings.primary_action();
  			} else {
  				this.make_new_doc();
  			}
  		});
  	}
  
  	setup_tag_event() {
  		this.tags_shown = false;
  		this.list_sidebar && this.list_sidebar.parent.on("click", ".list-tag-preview", () => {
  			this.tags_shown = !this.tags_shown;
  			this.toggle_tags();
  		});
  	}
  
  	setup_realtime_updates() {
  		if (
  			this.list_view_settings &&
  			this.list_view_settings.disable_auto_refresh
  		) {
  			return;
  		}
  		frappe.realtime.on("list_update", (data) => {
  			if (this.filter_area.is_being_edited()) {
  				return;
  			}
  
  			const { doctype, name } = data;
  			if (doctype !== this.doctype) return;
  
  			// filters to get only the doc with this name
  			const call_args = this.get_call_args();
  			call_args.args.filters.push([this.doctype, "name", "=", name]);
  			call_args.args.start = 0;
  
  			frappe.call(call_args).then(({ message }) => {
  				if (!message) return;
  				const data = frappe.utils.dict(message.keys, message.values);
  				if (!(data && data.length)) {
  					// this doc was changed and should not be visible
  					// in the listview according to filters applied
  					// let's remove it manually
  					this.data = this.data.filter((d) => d.name !== name);
  					this.render_list();
  					return;
  				}
  
  				const datum = data[0];
  				const index = this.data.findIndex((d) => d.name === datum.name);
  
  				if (index === -1) {
  					// append new data
  					this.data.push(datum);
  				} else {
  					// update this data in place
  					this.data[index] = datum;
  				}
  
  				this.data.sort((a, b) => {
  					const a_value = a[this.sort_by] || "";
  					const b_value = b[this.sort_by] || "";
  
  					let return_value = 0;
  					if (a_value > b_value) {
  						return_value = 1;
  					}
  
  					if (b_value > a_value) {
  						return_value = -1;
  					}
  
  					if (this.sort_order === "desc") {
  						return_value = -return_value;
  					}
  					return return_value;
  				});
  				this.toggle_result_area();
  				this.render_list();
  				if (this.$checks && this.$checks.length) {
  					this.set_rows_as_checked();
  				}
  			});
  		});
  	}
  
  	set_rows_as_checked() {
  		$.each(this.$checks, (i, el) => {
  			let docname = $(el).attr("data-name");
  			this.$result
  				.find(`.list-row-checkbox[data-name='${docname}']`)
  				.prop("checked", true);
  		});
  		this.on_row_checked();
  	}
  
  	on_row_checked() {
  		this.$list_head_subject =
  			this.$list_head_subject ||
  			this.$result.find("header .list-header-subject");
  		this.$checkbox_actions =
  			this.$checkbox_actions ||
  			this.$result.find("header .checkbox-actions");
  
  		this.$checks = this.$result.find(".list-row-checkbox:checked");
  
  		this.$list_head_subject.toggle(this.$checks.length === 0);
  		this.$checkbox_actions.toggle(this.$checks.length > 0);
  
  		if (this.$checks.length === 0) {
  			this.$list_head_subject
  				.find(".list-check-all")
  				.prop("checked", false);
  		} else {
  			this.$checkbox_actions
  				.find(".list-header-meta")
  				.html(__("{0} items selected", [this.$checks.length]));
  			this.$checkbox_actions.show();
  			this.$list_head_subject.hide();
  		}
  		this.toggle_actions_menu_button(this.$checks.length > 0);
  	}
  
  	toggle_tags() {
  		this.$result.find('.tag-col').toggleClass("hide");
  		const preview_label = this.tags_shown ? __("Hide Tags") : __("Show Tags");
  		this.list_sidebar.parent.find(".list-tag-preview").text(preview_label);
  	}
  
  	get_checked_items(only_docnames) {
  		const docnames = Array.from(this.$checks || []).map((check) =>
  			cstr(unescape($(check).data().name))
  		);
  
  		if (only_docnames) return docnames;
  
  		return this.data.filter((d) => docnames.includes(d.name));
  	}
  
  	save_view_user_settings(obj) {
  		return frappe.model.user_settings.save(
  			this.doctype,
  			this.view_name,
  			obj
  		);
  	}
  
  	on_update() {}
  
  	get_share_url() {
  		const query_params = this.get_filters_for_args()
  			.map((filter) => {
  				filter[3] = encodeURIComponent(filter[3]);
  				if (filter[2] === "=") {
  					return `${filter[1]}=${filter[3]}`;
  				}
  				return [
  					filter[1],
  					"=",
  					JSON.stringify([filter[2], filter[3]]),
  				].join("");
  			})
  			.join("&");
  
  		let full_url = window.location.href;
  		if (query_params) {
  			full_url += "?" + query_params;
  		}
  		return full_url;
  	}
  
  	share_url() {
  		const d = new frappe.ui.Dialog({
  			title: __("Share URL"),
  			fields: [
  				{
  					fieldtype: "Code",
  					fieldname: "url",
  					label: "URL",
  					default: this.get_share_url(),
  					read_only: 1,
  				},
  			],
  		});
  		d.show();
  	}
  
  	get_menu_items() {
  		const doctype = this.doctype;
  		const items = [];
  
  		if (frappe.model.can_import(doctype)) {
  			items.push({
  				label: __("Import"),
  				action: () =>
  					frappe.set_route("list", "data-import", {
  						reference_doctype: doctype,
  					}),
  				standard: true,
  			});
  		}
  
  		if (frappe.model.can_set_user_permissions(doctype)) {
  			items.push({
  				label: __("User Permissions"),
  				action: () =>
  					frappe.set_route("list", "user-permission", {
  						allow: doctype,
  					}),
  				standard: true,
  			});
  		}
  
  		if (frappe.user_roles.includes("System Manager")) {
  			items.push({
  				label: __("Role Permissions Manager"),
  				action: () =>
  					frappe.set_route("permission-manager", {
  						doctype,
  					}),
  				standard: true,
  			});
  
  			items.push({
  				label: __("Customize"),
  				action: () => {
  					if (!this.meta) return;
  					if (this.meta.custom) {
  						frappe.set_route("form", "doctype", doctype);
  					} else if (!this.meta.custom) {
  						frappe.set_route("form", "customize-form", {
  							doc_type: doctype,
  						});
  					}
  				},
  				standard: true,
  				shortcut: "Ctrl+J",
  			});
  		}
  
  		items.push({
  			label: __("Toggle Sidebar"),
  			action: () => this.toggle_side_bar(),
  			condition: () => !this.hide_sidebar,
  			standard: true,
  			shortcut: "Ctrl+K",
  		});
  
  		items.push({
  			label: __("Share URL"),
  			action: () => this.share_url(),
  			standard: true,
  			shortcut: "Ctrl+L",
  		});
  
  		if (
  			frappe.user.has_role("System Manager") &&
  			frappe.boot.developer_mode === 1
  		) {
  			// edit doctype
  			items.push({
  				label: __("Edit DocType"),
  				action: () => frappe.set_route("form", "doctype", doctype),
  				standard: true,
  			});
  		}
  
  		if (frappe.user.has_role("System Manager")) {
  			items.push({
  				label: __("List Settings"),
  				action: () => this.show_list_settings(),
  				standard: true,
  			});
  		}
  		return items;
  	}
  
  	show_list_settings() {
  		frappe.model.with_doctype(this.doctype, () => {
  			new ListSettings({
  				listview: this,
  				doctype: this.doctype,
  				settings: this.list_view_settings,
  				meta: frappe.get_meta(this.doctype)
  			});
  		});
  	}
  
  	get_workflow_action_menu_items() {
  		const workflow_actions = [];
  		if (frappe.model.has_workflow(this.doctype)) {
  			const actions = frappe.workflow.get_all_transition_actions(
  				this.doctype
  			);
  			actions.forEach((action) => {
  				workflow_actions.push({
  					label: __(action),
  					name: action,
  					action: () => {
  						frappe.xcall(
  							"frappe.model.workflow.bulk_workflow_approval",
  							{
  								docnames: this.get_checked_items(true),
  								doctype: this.doctype,
  								action: action,
  							}
  						);
  					},
  					is_workflow_action: true,
  				});
  			});
  		}
  		return workflow_actions;
  	}
  
  	toggle_workflow_actions() {
  		if (!frappe.model.has_workflow(this.doctype)) return;
  		const checked_items = this.get_checked_items();
  		frappe
  			.xcall("frappe.model.workflow.get_common_transition_actions", {
  				docs: checked_items,
  				doctype: this.doctype,
  			})
  			.then((actions) => {
  				Object.keys(this.workflow_action_items).forEach((key) => {
  					this.workflow_action_items[key].toggle(
  						actions.includes(key)
  					);
  				});
  			});
  	}
  
  	get_actions_menu_items() {
  		const doctype = this.doctype;
  		const actions_menu_items = [];
  		const bulk_operations = new BulkOperations({ doctype: this.doctype });
  
  		const is_field_editable = (field_doc) => {
  			return (
  				field_doc.fieldname &&
  				frappe.model.is_value_type(field_doc) &&
  				field_doc.fieldtype !== "Read Only" &&
  				!field_doc.hidden &&
  				!field_doc.read_only
  			);
  		};
  
  		const has_editable_fields = (doctype) => {
  			return frappe.meta
  				.get_docfields(doctype)
  				.some((field_doc) => is_field_editable(field_doc));
  		};
  
  		const has_submit_permission = (doctype) => {
  			return frappe.perm.has_perm(doctype, 0, "submit");
  		};
  
  		// utility
  		const bulk_assignment = () => {
  			return {
  				label: __("Assign To"),
  				action: () =>
  					bulk_operations.assign(
  						this.get_checked_items(true),
  						this.refresh
  					),
  				standard: true,
  			};
  		};
  
  		const bulk_assignment_rule = () => {
  			return {
  				label: __("Apply Assignment Rule"),
  				action: () =>
  					bulk_operations.apply_assignment_rule(
  						this.get_checked_items(true),
  						this.refresh
  					),
  				standard: true,
  			};
  		};
  
  		const bulk_add_tags = () => {
  			return {
  				label: __("Add Tags"),
  				action: () =>
  					bulk_operations.add_tags(
  						this.get_checked_items(true),
  						this.refresh
  					),
  				standard: true,
  			};
  		};
  
  		const bulk_printing = () => {
  			return {
  				label: __("Print"),
  				action: () => bulk_operations.print(this.get_checked_items()),
  				standard: true,
  			};
  		};
  
  		const bulk_delete = () => {
  			return {
  				label: __("Delete"),
  				action: () => {
  					const docnames = this.get_checked_items(true).map(
  						(docname) => docname.toString()
  					);
  					frappe.confirm(
  						__("Delete {0} items permanently?", [docnames.length]),
  						() => bulk_operations.delete(docnames, this.refresh)
  					);
  				},
  				standard: true,
  			};
  		};
  
  		const bulk_cancel = () => {
  			return {
  				label: __("Cancel"),
  				action: () => {
  					const docnames = this.get_checked_items(true);
  					if (docnames.length > 0) {
  						frappe.confirm(
  							__("Cancel {0} documents?", [docnames.length]),
  							() =>
  								bulk_operations.submit_or_cancel(
  									docnames,
  									"cancel",
  									this.refresh
  								)
  						);
  					}
  				},
  				standard: true,
  			};
  		};
  
  		const bulk_submit = () => {
  			return {
  				label: __("Submit"),
  				action: () => {
  					const docnames = this.get_checked_items(true);
  					if (docnames.length > 0) {
  						frappe.confirm(
  							__("Submit {0} documents?", [docnames.length]),
  							() =>
  								bulk_operations.submit_or_cancel(
  									docnames,
  									"submit",
  									this.refresh
  								)
  						);
  					}
  				},
  				standard: true,
  			};
  		};
  
  		const bulk_edit = () => {
  			return {
  				label: __("Edit"),
  				action: () => {
  					let field_mappings = {};
  
  					frappe.meta.get_docfields(doctype).forEach((field_doc) => {
  						if (is_field_editable(field_doc)) {
  							field_mappings[field_doc.label] = Object.assign(
  								{},
  								field_doc
  							);
  						}
  					});
  
  					const docnames = this.get_checked_items(true);
  
  					bulk_operations.edit(
  						docnames,
  						field_mappings,
  						this.refresh
  					);
  				},
  				standard: true,
  			};
  		};
  
  		// bulk edit
  		if (has_editable_fields(doctype)) {
  			actions_menu_items.push(bulk_edit());
  		}
  
  		// bulk assignment
  		actions_menu_items.push(bulk_assignment());
  
  		actions_menu_items.push(bulk_assignment_rule());
  
  		actions_menu_items.push(bulk_add_tags());
  
  		// bulk printing
  		if (frappe.model.can_print(doctype)) {
  			actions_menu_items.push(bulk_printing());
  		}
  
  		// bulk submit
  		if (
  			frappe.model.is_submittable(doctype) &&
  			has_submit_permission(doctype) &&
  			!frappe.model.has_workflow(doctype)
  		) {
  			actions_menu_items.push(bulk_submit());
  		}
  
  		// bulk cancel
  		if (
  			frappe.model.can_cancel(doctype) &&
  			!frappe.model.has_workflow(doctype)
  		) {
  			actions_menu_items.push(bulk_cancel());
  		}
  
  		// bulk delete
  		if (frappe.model.can_delete(doctype)) {
  			actions_menu_items.push(bulk_delete());
  		}
  
  		return actions_menu_items;
  	}
  
  	parse_filters_from_route_options() {
  		const filters = [];
  
  		for (let field in frappe.route_options) {
  			let doctype = null;
  			let value = frappe.route_options[field];
  
  			let value_array;
  			if (
  				$.isArray(value) &&
  				value[0].startsWith("[") &&
  				value[0].endsWith("]")
  			) {
  				value_array = [];
  				for (var i = 0; i < value.length; i++) {
  					value_array.push(JSON.parse(value[i]));
  				}
  			} else if (
  				typeof value === "string" &&
  				value.startsWith("[") &&
  				value.endsWith("]")
  			) {
  				value = JSON.parse(value);
  			}
  
  			// if `Child DocType.fieldname`
  			if (field.includes(".")) {
  				doctype = field.split(".")[0];
  				field = field.split(".")[1];
  			}
  
  			// find the table in which the key exists
  			// for example the filter could be {"item_code": "X"}
  			// where item_code is in the child table.
  
  			// we can search all tables for mapping the doctype
  			if (!doctype) {
  				doctype = frappe.meta.get_doctype_for_field(
  					this.doctype,
  					field
  				);
  			}
  
  			if (doctype) {
  				if (value_array) {
  					for (var j = 0; j < value_array.length; j++) {
  						if ($.isArray(value_array[j])) {
  							filters.push([
  								doctype,
  								field,
  								value_array[j][0],
  								value_array[j][1],
  							]);
  						} else {
  							filters.push([doctype, field, "=", value_array[j]]);
  						}
  					}
  				} else if ($.isArray(value)) {
  					filters.push([doctype, field, value[0], value[1]]);
  				} else {
  					filters.push([doctype, field, "=", value]);
  				}
  			}
  		}
  
  		return filters;
  	}
  
  	static trigger_list_update(data) {
  		const doctype = data.doctype;
  		if (!doctype) return;
  		frappe.provide("frappe.views.trees");
  
  		// refresh tree view
  		if (frappe.views.trees[doctype]) {
  			frappe.views.trees[doctype].tree.refresh();
  			return;
  		}
  
  		// refresh list view
  		const page_name = frappe.get_route_str();
  		const list_view = frappe.views.list_view[page_name];
  		list_view && list_view.on_update(data);
  	}
  };
  
  $(document).on("save", (event, doc) => {
  	frappe.views.ListView.trigger_list_update(doc);
  });
  
  frappe.get_list_view = (doctype) => {
  	let route = `List/${doctype}/List`;
  	return frappe.views.list_view[route];
  };
  