i777777o6c6962726172797468696e67o636f6dz.oszar.com Open in urlscan Pro
2606:4700:3030::ac43:dc4c  Public Scan

Submitted URL: http://i777777o6c6962726172797468696e67o636f6dz.oszar.com//js2/lt2_main.js?v=5234
Effective URL: https://i777777o6c6962726172797468696e67o636f6dz.oszar.com//js2/lt2_main.js?v=5234
Submission: On August 28 via api from US — Scanned from US

Form analysis 0 forms found in the DOM

Text Content

/* lt2_main.js */
try {
	var lt = lt || {};
	lt.a11y = {}; // for accessibility functions
	var $J = $J || jQuery;
} catch(err){}
var SUPPORT_NIGHTMODE = false;

lt.last_interaction = new Date();
lt.refresh_message_counts_interval_time_default = 120000;
lt.refresh_message_counts_interval_time = lt.refresh_message_counts_interval_time_default;

//isLT2 = true;
function isLT2() {
	if (typeof LibraryThing._isLT2 != "undefined") {
		return LibraryThing._isLT2;
	}
	var b = $J('body');
	if (b.data('lt2')) {
		return b.data('lt2');
	}
	return 0;
}

function lt2_product() {
	var b = $J('body');
	if (b.data('lt2')) {
		return b.data('product');
	}
	return false;
}

function is_product(prod)
{
	return (prod === lt2_product());
}

function loadScript(src, done) {
	var js = document.createElement('script');
	js.src = src;
	js.onload = done;
	document.head.appendChild(js);
}

lt.is_touch_device = function() {
	return 'ontouchstart' in document.documentElement || 'ontouchstart' in window || navigator.maxTouchPoints;
}

/* scroll handler for topbar and search box */
lt.didScroll = false;
lt.lastScrollTop = 0;
lt.delta = 5;
lt.searchbarHeight = $J('#mobiletop_search').outerHeight();

// These ajax requests never trigger a prettify.
// This is because they likely don't have content, are lightbox content, or have well-defined content.
// Only add things to this list if you know they aren't using content that needs to be initialized
// by the prettify functions (ex: work popups, card_lists, truncation, etc.)
lt.prettify_none = [
	'/ajax_amazon_cover_harvester.php',
	'/ajax_amazon_cover_harvester.php?preflight_check=1',
	'/ajax_work_popup.php',
	'/ajax_popup_cover_img.php',


	'/admin/ajax_admin_set_service_throttle.php',


	//'/ajax_nstats_json.php',
	'/ajax_charts_share_sheet.php',
	'/ajax_page_share_sheet.php',
	'/commonknowledge/ajax_autocompleteFwikiField.php',
	'/ajax_setSessionData.php',
	'/ajax_comment_post.php',
	'/ajax_newcomments_refreshcounts.php',
	'/ajax_newcomments_refreshcounts_new.php',
	'/ajax_profile_medialine.php',
	//'/ajax_conversation_star_toggle.php'
	'/local_venue_combine_validator.php',
	'/ajax_checkGeocode.php',
	'/ajax_setCookie.php',

	'/set_viewport.php',

	/* Home */
	'/ajax_newhome_type.php',
	'/ajax_home_circulation.php',
	'/ajax_home_topwishlisted.php',
	'/ajax_home_movers.php',
	'/ajax_home_whatisbeingread.php',
	'/ajax_home_yourlists.php',
	'/ajax_home_listsyouvestarted.php',
	'/ajax_home_listsfavorited.php',
	'/ajax_home_hotlists.php',
	'/ajax_home_listrecommendations.php',
	'/ajax_home_listofthemonth.php',
	'/ajax_home_recentlists.php',
	'/ajax_home_listspopular.php',

	'/ajax_home_zeitgeist.php',
	'/ajax_home_yourtoptags.php',
	'/ajax_home_quicklinks.php',
	'/ajax_home_yourconnections.php',
	'/ajax_userswithyourbooks.php',
	'/ajax_home_groups.php',
	'/ajax_home_collections.php',
	'/ajax_home_chartrecentbooks.php',
	'/ajax_home_favorites.php',
	'/ajax_home_spamworks.php',
	'/ajax_home_tagcombinations.php',
	'/ajax_home_venuematching.php',
	'/ajax_home_helperzeitgeist.php',
	'/ajax_dismisscover.php',
	'/ajax_list_explain_lt2.php',
	'/ajax_award_poweredit.php',

	'/ajax_save_sticky_newauthor_section.php',

	//local
	'/ajax_maps_plot.php',

	'/ajax_tagautocomplete.php',
	'/ajax_autocomplete.php',

	// Talpa
	'/ajax_new_progressbar_status_check.php',
	'/ajax_new_progressbar_input_status_check.php',
	'/ajax_nadmin_talpa_saved_query_new_progressbar_input_status_check.php',
	'/ajax_talpa_timing_store.php',
	'/ajax_talpa_list_item_controls.php',
	'/ajax_talpa_item_flag.php',

	'/ajax_free_talpa_limit_check.php',
	'/api_talpa_search.php',

	// SU
	'/syndeticsunbound/ajaxinc_libpow_find_biblio_info.php',

	// validators
	'/award_edit_award_validator.php',
];
// These ajax requests trigger only CORE prettify
// (because we know it won't contain things like datatables, etc.)
// Core means: work popup, truncation, rolldowns, collection menu
lt.prettify_core_only = [
	'/ajax_userswithyourbooks.php',
	'/ajax_home_connectionnews.php',
	'/ajax_home_yourrecentreviews.php',
	'/ajax_home_recentlyadded.php',
	'/ajax_home_news.php',
	'/ajax_home_recentyourbookscovers.php',
	'/ajax_home_edit_section.php',
	'/ajaxinc_newshelf.php', // Might want to disable this after we do the new shelves.
	'/ajax_nstats_json.php', // LH: Removing this so that ajaxed data in stats will work
	'/ajax_award_dispensation_picker.php'
];
// These ajax requests trigger only SOME prettify (like reflow checks)
// Likely because they contain content, so might cause a reflow, but we know the content doesn't need to be prettified.
lt.prettify_some = [
	//'/ajax_nstats_json.php'
	'/ajaxinc_award_edit_work_load_works.php',
];

/*
lt.setVh = () => {
	const vh = window.innerHeight * 0.01;
	document.documentElement.style.setProperty('--vh', `${vh}px`);
	document.documentElement.style.setProperty('--vh100', `${window.innerHeight}px`);
};
window.addEventListener('load', lt.setVh);
 */
//window.addEventListener('resize', lt.setVh);

lt.basename = function(path) {
	return path.split('/').reverse()[0];
};

lt.prettify_queue = [];

$J(function() {
	//mmgroup('DOM Ready:lt2_main');

	// Check for the scroll position once at page load
	//lt.copy_action_area();
	//lt.scroll_handle_mini_lede();

	//lt.fix_sidenavs_if_possible();

	// Figure out how tall the nav and subnav are.
	// We can then set variables in CSS and reference
	// items to these variables for flexible CSS that knows how tall or where to be.
	var htmlel = $J('html');
	var masthead = $J('#masthead');
	var subnav_height = 0;
	htmlel.css('--lt-navtabs-height', masthead.height() + 'px');
	var subnav = $J('.lt2_subnav').first();
	if (subnav.length) {
		subnav_height = subnav.height();
	}
	htmlel.css('--lt-subnav-height', subnav_height + 'px');



	//lt.attachMainTabHovers();
	lt.attachMainMenuHandlers();

	// Check to see if the subnav is rolling off the page. If so, we need to deal with that.
	lt.checkSubnavOcclusion();


	// now set up a handler to do it any time there's a scroll event.
	//$J(window).on('scroll.lede', lt.scroll_handle_mini_lede);

	// Do the general prettifying: hovers, truncate, lazy loading (yall)
	setTimeout(function() {
		lt.prettify_content();
	},25);

	lt.body = $J('body');

	/* used for whole-page overlay behind menus and lightboxes */
	lt.page_overlay = $J('#lt2_page_overlay');
	lt.initSpoilersOnPage();

	lt.newFeatureFind();

	lt.body.on('keydown.lt_tabcheck', handleFirstTab);

	lt.body.on('click', function(e) {
		//mmlog('lt.body.on(\'click\') in lt2_main.js', 'log', 'grey');

		var is_in_menu_input = false;
		var target = $J(e.target);

		//mmlog(target);
		if (target.is('.lt2_multiselect-header, .lt2_multiselect-header-option, .lt2_multiselect-header-placeholder')) {
			mmlog('target is multiselect input element. letting it bubble up.');
			return;
		}

		if (typeof lt.close_all_multiselects === 'function') {
			lt.close_all_multiselects();
		}

		if (target.is('input')) {
			mmlog('target is input element. letting it bubble up.');
			return;
		}

		// Autocomplete jqueryui tag menu
		if (target.is('.ui-menu-item')) {
			mmlog('event target is jquery autocomplete menu item. letting it bubble up.');
			return;
		}


		// Allow clicking of form submits and validation.
		if (target.is('button[type="submit"]')) {
			if (target.closest('form').is('.validate')) {
				mmlog('target is validated submit button. Letting event bubble up.');
				return;
			}
		}


		// close the work popup if we click anywhere.
		// We are doing this in lt2_popup.js now but for some reason it isn't firing from there.
		// So we can only do this here until we get that figured out.
		if (typeof lt.hideHoverImmediate === 'function') {
			if (typeof lt.hideHoverImmediate === 'function') {
				// only close if it is a click outside the hover.
				mmlog(target);
				mmlog(target.closest('.lt2_hover'));
				if (target.closest('.lt2_hover').length === 0) {
					lt.hideHoverImmediate();
				}
			}
		}



		if (target.is('.lt2_accordion_clickable') || target.is('.lt2_sidebar_accordion_parent_title')) {
			is_in_menu_input = true;
		}
		else {
			// If we click anywhere BUT in a menu, close the menus.
			var is_in_menu_input = false;
			var is_in_menu = target.is('.lt2_menu, #mobile_topmenu_content, .dyn_nav_menu');
			if (!is_in_menu && !target.hasClass('lt2_sidebar_accordion_parent')) {
				is_in_menu = target.closest('.lt2_menu, #mobile_topmenu_content, .dyn_nav_menu');
				if (target.is('input')) {
					is_in_menu_input = true;
				}else {
					if (is_in_menu.is('.dyn_nav_menu')) {
						is_in_menu = 0;
						is_in_menu.length = 0;
					}
				}
			}
		}

		if (is_in_menu_input) {

		}
		else {
			e.stopImmediatePropagation();
			lt.hide_menus();
			$J('.lt_popup_menu[data-active], .lt_collection_menu, input[data-autocomplete]').trigger('close_menu');
			if (typeof lt.destroyAllACMenus == 'function') {
				lt.destroyAllACMenus(); // autocomplete menus for CK
			}
		}

		/*
		if (!is_in_menu.length) {
			lt.hide_menus();
		}

		 */
		//mmlog(e.target);
	});

	$J(document).ajaxComplete(function lt2_main_handleAjaxComplete(event, xhr, settings) {
		//mmlog(event);
		//mmlog(xhr);
		//mmlog(settings);

		var loc = settings.url.split( '?' )[0];
		//var pretty_none = loc.match(lt.prettify_none.join('|'));
		var pretty_none = lt.prettify_none.indexOf(loc);
		if (xhr.status === 242) {
			// We can use 242 status codes from our server to indicate that we don't need to do a prettify.
			pretty_none = 1;
		}
		if (pretty_none == -1) {
			//mmlog('AJAX COMPLETE FIRED!', 'warn');


			var prettyWorkFunction = function(settings) {
				var ajaxcomplete_prettify_timer;
				//clearTimeout(ajaxcomplete_prettify_timer);
				// Check to see if settings.url is in lt.prettify_none. If so, skip.
				var prettify_filter = null;
				var prettify_selector = settings.prettify_selector || null

				lt.force_containerQuery_update(prettify_selector);

				// If url is in the prettify_some array then only run those prettify functions
				if (lt.prettify_some.indexOf(loc) != -1) {
					prettify_filter = 'some';
				}
				// If url is in the prettify_core_only array then only run those prettify functions
				if (lt.prettify_core_only.indexOf(loc) != -1) {
					prettify_filter = 'core_only';
				}

				if (typeof lt.prettify_none[settings.url] !== undefined)
					ajaxcomplete_prettify_timer = setTimeout(function () {
						if (typeof settings !== 'undefined' && typeof settings.url !== 'undefined') {
							lt.prettify_content('ajax: ' + settings.url, prettify_selector, prettify_filter);
						}
					}, 250);
			}


			prettyWorkFunction(settings);


			//lt.prettify_content(true);
		}


	});

	/*
	$J('.lt2_sidebar').on('DOMSubtreeModified', function() {
		mmlog('changed!!!!!!!');
	});
	*/


	/* scroll handler for topbar and search box */
	$J(window).on('scroll.tabbar', lt.scrollHandler);

	// Rollover handler for ui_link_buttons
	if (lt.is_touch_device()) {
		// If it is a touch device we don't have rollovers.
		// So we will automatically show them as rolled over at page load.
		$J('.btn[data-rollover]').each(lt.rolloverEnter);
	}
	else {
		// On a desktop device we will activate on rollover.
		lt.body.on('mouseover', '.btn[data-rollover]', lt.rolloverEnter);
		lt.body.on('mouseout', '.btn[data-rollover]', lt.rolloverExit);
	}



	lt.body.on('click', '.lt_pillbox > [role="button"]', lt.pillbox_handleclick);
	lt.body.on('click', '.card_list_viewtoggle > [role="button"]', lt.cardlist_viewstyle_select);
	lt.body.on('click', '.disclosure_toggle', lt.disclosure_toggle);
	lt.body.on('click', '.accordion_item_toggle', lt.accordion_toggle);

	lt.body.on('click.lt_uli mouseover.lt_uli keydown.lt_uli', lt.update_last_interaction);

	// Hilite parts of the page that need to be hilited (via the #spotlight:element_id URL arg)
	setTimeout(function() {
			lt.spotlight();
		}, 150
	);

	if (is_product('lt2') && LibraryThing.is_signed_in) {
		//lt.refresh_message_counts();
		// Turning off continuous message count checking. It might have gone rogue on certain browsers in some conditions.
		//lt.refresh_message_counts_interval = setInterval(lt.refresh_message_counts, lt.refresh_message_counts_interval_time);
	}


	//mmgroupend();
});




// IMPORTANT FUNCTION
// This fires on every page load and on every AJAX content load.
lt.prettify_content = function(ajax_called_me, selector, refresh_what) {

	ajax_called_me = ajax_called_me || false;
	selector = selector || '';
	mmgroup("lt.prettify_content["+ajax_called_me+']:'+selector, 1);
	//litsy_yall();

	if (ajax_called_me) {
		mmgroup("lt.prettify_content.always["+ajax_called_me+'] --> \''+selector+'\'', 1);
		// If we ajaxed content and are prettifying it, the page size may change due to the new content.
		// The ResizeObserver won't catch these "resizes" so we need to fire here instead.
		lt.fix_sidenavs_if_possible();
		mmgroupend();
	}

	refresh_what = refresh_what || 'all';

	// Core Only
	// Do these after EVERY ajax. AS LONG AS IT'S NOT IN THE "NONE" list.
	if (refresh_what === 'all' || refresh_what === 'some' || refresh_what === 'core_only')
	{
		setTimeout(function() {
			mmgroup("lt.prettify_content.all/some/core[" + ajax_called_me + '] --> \'' + selector + '\'', 1);
			if (typeof lt.find_hover_items === 'function') {
				lt.find_hover_items(selector); // work/author popups
			}
			lt.truncate(selector); // old style truncate.
			lt.setup_rolldowns(selector); // new (show more) rolldowns for lists
			lt.prettify_pagination(selector);
			mmgroupend();
		}, 0);


		// ACT harvester. Only runs if the JS has been included (via cookie check in factory)
		setTimeout( function() {
			if (typeof act != 'undefined') {
				if (typeof act.harvest_covers === 'function') {
					act.harvest_covers(selector);
				}
			}
		}, 500);

	}

	// Some
	// Do these SOME of the time. They aren't core...but they aren't nec. for every page.
	if (refresh_what === 'all' || refresh_what === 'some')
	{
		setTimeout(function() {
			mmgroup("lt.prettify_content.all/some[" + ajax_called_me + '] --> \'' + selector + '\'', 1);

			$J(selector + ' .lt_popup_menu').lt_popup_menu();
			if (typeof lt.autocomplete_init === 'function') {
				lt.autocomplete_init(selector);
			}
			lt.validate_forms(selector); // initiation validation on forms with data-validate attribs

			lt.a11y.fontawesome(selector); // do some generic a11y stuff for fontawesome images

			mmgroupend();
		}, 10);
	}


	// All
	// Standard prettification.
	if (refresh_what === 'all') {
		// Going to split these up into tasks so that they can hopefully run a little faster.
		// Faster UI items
		setTimeout(function() {
			mmgroup("lt.prettify_content.all[" + ajax_called_me + '] --> \'' + selector + '\'', 1);

			$J(selector + ' .lt_collection_menu').lt_collection_menu();
			lt.stars_hovers(selector); // mostly used on catalog page. If there are stars that are editable, hilite on hover.

			lt.build_date_pickers(selector);

			if (typeof lt.setup_tabviews == 'function') {
				lt.setup_tabviews(selector); // makes tabviews clickable and connects tabs to tab content views.
			}
			else {
				mmlog('TABVIEWS not supported because functions are not loaded', 'error');
			}
			lt.init_clipboxes(selector); // box-based truncation
			lt.check_cardlists(selector); // for the box-like cardlists that use grid-auto-rows to expand.

			lt.lineclampSeemore(selector); // new style items truncated by CSS line-clamp with show more buttons to them

			lt.init_tooltips(selector); // use popper.js to show tooltips and informational popup views.

			lt.createPiecharts(selector); // rarely used, mini pies (like the series admin progress pies)


			if (typeof lt.find_hover_items === 'function') {
				lt.find_hover_items(selector); // work/author popups
			}

			// a11y
			lt.a11y.covers(selector); // set titles on covers if they do not have them.


			if (selector) {
				$J(selector).trigger('lt_prettify_core_complete');
			}

			mmgroupend();
		}, 10);
		//}

		// Slower UI items
		setTimeout(function() {

			mmgroup("lt.prettify_content."+refresh_what+".delayed20ms["+ajax_called_me+'] --> \''+selector+'\'', 1);
			lt.prettify_dropzones(selector); // set up dropzones if there are any on the page.
			lt.prettify_datatables(selector); // set up datatables if there are any on the page
			lt.prettify_advanced_text_editors(selector);
			//lt.setup_shelves(selector); // Not used yet. Testing for new shelf system for LT2.

			// Those syntax editor textareas like we use on oneadmin
			if (lt.setup_syntax_editors) {
				lt.setup_syntax_editors(selector);
			}

			// Setup multiselect items if we have that file loaded.
			if (typeof lt.prettify_multiselect === 'function') {
				lt.prettify_multiselect(selector); // work/author popups
			}

			lt.prettify_print_rr(selector);
			//lt.yall(selector);

			mmgroupend();
		}, 20);
	}
	mmgroupend();
};


lt.get_hash = function() {
	var thehash = window.location.hash;
	thehash = thehash.replace('#','');
	return thehash;
}

function lightbox_close() {
	LibraryThing.lightbox.close();
}

lt.prettify_print_rr = function(selector) {
	var print_rrs = $J(selector + ' .lt_print_rr:not(.lt_print_rr_init)').each(function() {
		var that = $J(this);
		var copy = $J('<a href="javascript:void(0);" onclick="lt.copy_text(\'#'+that.attr('id')+'\')" class="lt_print_rr_copy"><i class="fa-solid fa-copy"></i></a>');
		that.append(copy);
		that.addClass('lt_print_rr_init');
	});
}

// element_id is optional. Normally the element_id is read from the #spotlight:element_id URL arg
lt.spotlight = function(element_id) {
	// We set a timeout here to let various UI elements reshape themselves on page loads
	// before the spotlight can measure their size/position.
	setTimeout(function() {
		var thehash = lt.get_hash();
		if (thehash.startsWith('spotlight:')) {
			thehash = thehash.replace(/^spotlight:/, '');
			if (0) { //(thehash.startsWith('j:')) {
				// we need to do some JS here, instead of doing a highlight.
				// TODO: CH We might THEN do a spotlight, but we'll get to that.
				var thejs = thehash.replace(/^j:/, '');
				if (thejs.length) {
					$J.ajaxSetup({
						cache: true
					});
					$J.getScript("/js2/base64.js", function() {
						//alert("Script loaded and executed.");
						var decoded = BASE64.decode(thejs);
						eval(decoded);
						// here you can use anything you defined in the loaded script
					});
					$J.ajaxSetup({
						cache: false
					});
				}
			}
			if (thehash) {
				mmlog('Spotlight: #' + thehash);
				var el;
				if (thehash.startsWith('.')) {
					el = $J(thehash).first();
				}
				else {
					el = $J('#' + thehash);
				}

				if (!el.length) {
					mmlog('Spotlight: did not find element with id. Checking element names.')
					el = $J('[name="' + thehash + '"]');
				}

				if (el.length) {
					mmlog(el);
					if (el.is('textarea.trumbowyg-textarea')) {
						el = el.closest('.trumbowyg-box');
					}
					else if (el.is('input') && el.parent().is('label')) {
						el = el.parent();
					}
					lt.body.spotlight = $J('.spotlight');
					if (!lt.body.spotlight.length) {
						lt.body.spotlight = $J('<div class="spotlight flash">');
						$J('body').prepend(lt.body.spotlight);
					}
					lt.body.spotlight.removeClass('flash').addClass('flash');

					// wrap element in a spotlight element
					var spotlight_offset = 10;
					// TODO: this does not seem to be working.
					if (el.is("input[type='checkbox']")) {
						spotlight_offset = 10;
					}
					var eloff = el.offset(); //AbsoluteCoordinates(el);
					var _winscroll = 0;//$J(window).scrollTop();
					el_scrolloffset = eloff.top - _winscroll;

					mmlog('Button offset: ' + eloff.top);
					//mmlog('Button abs: '+obj._abs.top);
					mmlog('Window scrollTop: ' + _winscroll);
					mmlog('Offset - scroll: ' + (eloff.top - _winscroll));


					lt.body.spotlight.css({
						top: el_scrolloffset - spotlight_offset,
						display: 'block',
						width: el.outerWidth() + (spotlight_offset * 2),
						height: el.outerHeight() + (spotlight_offset * 2),
						left: eloff.left - spotlight_offset,
					});
					el.on('click.spotlight', function() {
						$J('.spotlight').fadeOut();
					});
					mmlog(eloff);


					setTimeout(function() {
						lt.scrollto(el, 500);
					}, 100);

				}
			}
		}
	}, 60);
}

lt.new_feature_explainer_dismiss = function(el) {
	var el = $J(el);
	if (el.length) {
		var feature_el = el.closest('[data-new_feature_key]');
		feature_key = feature_el.data('new_feature_key');

		var url = '/ajax_setSessionData.php';
		var params = {
			mode: 'users_preferences',
			key: feature_key,
			value: 1
		}
		var callback = function () {

		}
		//callback();
		feature_el.slideUp(250, function() {
			$J(this).remove();
		});
		basic_ajax(url, params, callback);
	}
}

lt.toast = function(msg, params) {
	//return; // disabled for now.
	params = params || {};
	params.text = msg;
	const type = params.type || 'msg';


	// set some defaults
	var offset = 40;
	if ($J('body').hasClass('has_subnav')) {
		offset = 80;
	}
	params.offset = params.offset || {y: offset+'px'};
	params.duration = params.duration || 5000;
	params.className = params.className + ' lt_toast' || 'lt_toast';
	params.stopOnFocus = true;
	params.close = params.close || true;
	params.escapeMarkup = false;

	// Different types
	if (type) {
		params.className += ' lt_toast_'+type;
	}

	Toastify(params).showToast()
};
/*
lt.yall = function () {
	mmlog('yalling');
	try {
		yall({
			observeChanges: false,
			idlyLoad: true,
			threshold: 600,
			observeRootSelector: '#lt2_content'
		});
	} catch(err) {}
};
 */

lt.ltlazyload = function() {
	return;// BAILING OUT FOR NOW.
	var wt = $J(window).scrollTop();    //* top of the window
	var wb = wt + window.innerHeight; // $J(window).height();  // bottom of the window, jquery buggy

	$J(".ltlazy").each(function(){
		var ot = $J(this).offset().top;  //* top of object (i.e. advertising div)
		var ob = ot + $J(this).height(); //* bottom of object

		if(!$J(this).attr("loaded") && wt<=ob && wb >= ot){
			$J(this).html("here goes the iframe definition");
			$J(this).attr("loaded",true);
		}
	});
};



lt.pillbox_handleclick = function() {
	mmlog('pillbox_handleclick');
	var that = $J(this);
	mmlog(that);
	var pillbox = that.closest('.lt_pillbox');
	if (!pillbox.hasClass('lt2_pagination') && !pillbox.hasClass('multi_select_pillbox')) {
		// Paginator has different handling because of prev/next buttons.
		// Multi is different because we don't want to unselect others when selecting one
		that.siblings().removeClass('selected');
		that.addClass('selected');
	}
	pillbox.attr('data-value', that.attr('data-value'));
	var onclick = that.attr('data-onclick');
	if (onclick) {
		var result = new Function(onclick)();
		//eval('(function() {' + onclick+ '}())');
		return result;
	}
};

lt.handle_pagination_popup = function(e, elid) {
	var source = $J('#'+elid);
	const popup_path = '.lt2_pagination_popup[data-sourceid="'+elid+'"]';
	var popup = $J(popup_path);
	if (!popup.length) {
		// need to make the popup.
		mmlog('no popup found');
		mmlog(popup_path);
	}
	popup.fadeToggle(200);
};

lt.prettify_pagination = function(selector) {
	var paginator = $J(selector + '.lt2_pagination').each(function() {
		var buttons = $J(this).find('.lt2_pi');

		buttons.each(function() {
			var b = $J(this);

			b.off('click').on('click', function() {
				mmlog('button clicked');
				var clicked_button = $J(this);

				var currently_selected = buttons.filter('.selected').first();
				buttons.removeClass('selected');

				if (clicked_button.is('.jumptofirst')) {
					mmlog('doing jumptofirst button');
					clicked_button.addClass('selected');
				}
				else if (clicked_button.is('.jumptolast')) {
					mmlog('doing jumptolast button');
					clicked_button.addClass('selected');

				}
				else if (clicked_button.is('.jumptoprev')) {
					mmlog('doing prev button');
					if (currently_selected.hasClass('pi_num')) {
						var prev = currently_selected.prev();
						if (prev.hasClass('pi_num')) {
							prev.addClass('selected');
						}
					}
				}
				else if (clicked_button.is('.jumptonext'))
				{
					mmlog('doing next button');
					if (currently_selected.hasClass('pi_num')) {
						var next = currently_selected.next();
						if (next.hasClass('pi_num')) {
							next.addClass('selected');
						}
					}
				}
				else {
					clicked_button.addClass('selected');
				}

			})
		})
	});
}

lt.handle_pagination_click = function(clicked_el) {
	mmlog('lt.handle_pagination_click');
	mmlog(clicked_el);
}

/* used on sidenavs */
lt.disclosure_toggle = function() {
	mmlog('disclosure_toggle()');
	var that = $J(this);
	var disclosure = that.parent('.disclosure');
	var content = that.siblings('.disclosure_content').first();
	var savekey = disclosure.data('savekey');
	var _selected = disclosure.is('.selected');

	if (_selected) {
		content.slideUp();
		disclosure.removeClass('selected');
	}
	else {
		content.slideDown();
		disclosure.addClass('selected');
	}
	if (savekey) {
		var newval = (_selected) ? 0 : 1;
		mmlog('saving: '+ savekey + ' = ' + newval);
		//LibraryThing.setSessionData(savekey, newval);
	}

};

function handleFirstTab(e) {
	if (e.keyCode === 9) { // the "I am a keyboard user" key
		mmlog('handling keydown to check for a keyboard user.');
		document.body.classList.add('keyboard_user');
		lt.body.off('keydown.lt_tabcheck');
	}
}


(function($J) {
	$J.fn.copy_text = function() {
		var that = $(this);
		if (typeof navigator.clipboard != undefined) {
			// Clipboard API
			var val = that.val() || that.text();
			if (val === '') { mmlog('ERROR copying text. Empty string.'); return; }
			navigator.clipboard.writeText(val)
				.then(() => {
					mmlog('Text copied to clipboard: ' + val);
				})
				.catch(err => {
					mmlog('Error in copying text: ' + err, 'error');
				});
		}
		else {
			// Old school
			that.get(0).select();
			that.get(0).setSelectionRange(0, 99999); /* For mobile devices */
			/* Copy the text inside the text field */
			document.execCommand("copy");
		}
	};

	$.fn.replaceHtml = function( html ) {
		var stack = [];
		return this.each( function(i, el) {
			var oldEl = el;
			/*@cc_on // Pure innerHTML is slightly faster in IE
			oldEl.innerHTML = html;
			return oldEl;
			@*/
			var newEl = oldEl.cloneNode(false);
			newEl.innerHTML = html;
			oldEl.parentNode.replaceChild(newEl, oldEl);
			/* Since we just removed the old element from the DOM, return a reference
			to the new element, which can be used to restore variable references. */
			stack.push( newEl );
		}).pushStack( stack );
	};

})(jQuery);

lt.copy_text = function(elementid) {
	var el = null;
	if (typeof elementid !== 'undefined') {
		el = lt.elf(elementid);
	}
	if (!el || typeof el.length === 'undefined' || !el.length) {
		el = $J('#'+elementid).first();
	}

	el.copy_text();
	el.flash();

	lt.toast('Text copied to clipboard.', {duration:2000});
	return false;
};

// puts a bit of text (val) into the clipboard for the user
lt.send_to_clipboard = function(val) {
	if (typeof navigator.clipboard != undefined) {
		// Clipboard API
		if (val === '') { mmlog('ERROR copying text. Empty string.'); return; }
		navigator.clipboard.writeText(val)
			.then(() => {
				mmlog('Text copied to clipboard: ' + val);
				lt.toast('Text copied to clipboard.', {duration:2000});
			})
			.catch(err => {
				mmlog('Error in copying text: ' + err, 'error');
			});
	}
	return false;
};

(function($){
	$.fn.lt_popup_menu = function() {
		const fadeDuration = 300;
		return this.each( function() {
			var obj = $J(this);
			if (obj.attr('lt_inited')) { return; }
			obj.attr('lt_inited', 1);

			obj.menu_type = 'popup';
			var _child_button = obj.find('button').first();

			if (obj.hasClass('lt_accordion_popup')) {
				obj.menu_type = 'accordion';
			}

			/*
			var main_button = obj.children('button[role=menu]').first();
			obj._h = main_button.outerHeight();
			obj._w = main_button.outerWidth();
			*/

			/*
			var relative_root = $J('#lt2_content_interior');
			var menuPos = obj.position();
			relative_root.css({
				top: menuPos.top,
				left: menuPos.left
			});
			 */

			obj.lt_popup_menu_default_handler = function(val, usernum, clickedItem) {
				var jitem = $J(clickedItem);
				obj.removeAttr('data-active');
			};
			var click_func = obj.data('handler');

			obj.click(function(ev) {
				// If it is disabled, bail out.
				var _child_button = obj.find('button').first();
				if (!_child_button.length) {
					_child_button = obj.children().first();
				}
				if (_child_button.hasClass('disabled')) {
					return;
				}

				ev.stopImmediatePropagation();

				// Close this menu if you click on the menu again, then bail out.
				if (obj.attr('data-active')) {
					var target = ev.target;
					var is_inside_popup_view = ($J(target).parents('.popup_list_view').length == 1) ? true : false;
					if (is_inside_popup_view) {
						return true;
					}
					obj.triggerHandler('close_menu');
					if (obj.menu_type === 'accordion') { obj.save_active_state(); }
					return false;
				}

				// Close ALL (other) menus when you click on one.
				$J('.lt_popup_menu[data-active]').trigger('close_menu');

				// If we have gotten here then open the menu
				if (obj.length) {
					var baseobj = obj.get(0);
					var origtarget = ev.originalEvent.target;
					// || origtarget.matches('.lt_popupmenu_button')

					if (0 && origtarget != baseobj) {
						mmlog(ev.target);
						mmlog(obj.get(0));
						return;
					}
					mmlog(ev.target);
					obj.triggerHandler('open_menu', {event: ev});
					if (obj.menu_type === 'accordion') { obj.save_active_state(); }
				}



				$J(document).keyup(function(event) { //keypress event, fadeout on 'escape'
					if(event.keyCode == 27) {
						obj.triggerHandler('close_menu');
					}
				});


				/*
				obj.find('.popup_list').hover(function(){ },
					function(){
						$J(this).fadeOut(400);
					});

				 */
			});

			obj.save_active_state = function() {
				var active_id = obj.attr('data-save_active_id');
				if (active_id) {
					var active = parseIntLT(obj.attr('data-active'));
					if (!active) {
						obj.removeClass('saved_active');
					}
					LibraryThing.setSessionData(obj.attr('data-save_active_id'), active);
				}
			}

			obj.reset = function() {
				var button_host = true;
				if (obj.hasClass('nonbutton_host')) {
					button_host = false;
				}
				obj.find('.selected').removeClass('selected');
				if (button_host && _child_button) {
					var _main_btn_content = _child_button.children('.btntxt').first();
					_main_btn_content.html(_child_button.attr('value'));
				}
				_child_button.removeClass('btn-nav-selected').addClass('btn-default');

				obj.triggerHandler('close_menu');
			}
			obj.on('reset', function() {
				obj.reset();
			})

			obj.find('.popup_list li').click(function(ev) { //onclick event, change field value with selected 'list' item and fadeout 'list'
				mmlog('.popup_list li -> click handler');
				ev.stopImmediatePropagation();
				var clickedItem = $J(this);
				if (clickedItem.hasClass('lt_popup_header')) {
					return true;
				}
				var val = clickedItem.attr('data-value');
				var usernum = obj.attr('data-usernum');

				var button_host = true;
				if (obj.hasClass('nonbutton_host')) {
					button_host = false;
				}


				var _this_txt = clickedItem.children('.ddmi_text').first().text();
				if (button_host && obj.attr('data-no-change-on-select')) {}
				else {
					if (button_host) {
						// set the main menu title to be that of this menu item's content div
						var _main_btn_content = _child_button.children('.btntxt').first();
						_main_btn_content.html(_this_txt);
					}

					if (clickedItem.data('is_default_item')) {
						_child_button.removeClass('btn-nav-selected').addClass('btn-default');
					}
					else {
						_child_button.removeClass('btn-default').addClass('btn-nav-selected');
					}
				}



				// unselect siblings. select self.
				clickedItem.siblings().removeClass('selected');

				if (clickedItem.attr('href')) {
					// don't do anything for items with an href (this indicates that there's an href child)
					// if the user hits the clickedItem but misses the child item that has an href attached
					// then we want to fire a gotourl or a click on the child.
					let aurl = clickedItem.find('a').first().attr('href');
					goToURL(aurl);
				}
				else {
					if (obj.attr('data-no-change-on-select')) {}
					else {
						clickedItem.addClass('selected');
					}
				}


				if (clickedItem.data('handler')) {
					click_func = clickedItem.data('handler');
				}

				if (typeof click_func !== 'undefined') {
					var _tmpFunc;
					try {
						_tmpFunc = eval(click_func);
					} catch(err) {
						mmlog('2334: No function matching: "' + click_func + '()" available for popup menu handler', 'warning');
					}
					if (typeof _tmpFunc === 'function') {
						_tmpFunc(val, usernum, clickedItem); // call the function defined by the data-handler set on the parent popup div
					}
					else {
						mmlog('2893: No function matching: "' + click_func + '()" available for popup menu handler', 'warning');
					}
				}
				else {
					if (typeof obj[click_func] === 'function') {
						obj[click_func](val, usernum, clickedItem);
						mmlog('using built-in popup menu handler function to handle individual element onclick or hrefs');
					}
					else {
						mmlog('No function matching: "' + click_func + '()" available for popup menu handler', 'warning');
					}
				}
				obj.triggerHandler('close_menu');
				//obj.find('.popup_list').fadeOut(fadeDuration);
			});

			obj.resize = function(is_ajax_view) {
				mmlog('popup_list resize()');
				var _popup_menu = obj.find('.popup_list, .popup_view');
				// Check to see if we are in a lightbox. If so things will be positioned relative to it.
				var _lightbox = _popup_menu.parents('.LT_LB2020_wrapper');
				var _maincontent = $J('#lt2_content');
				var _content_width = _popup_menu.data('content_width') || false;
				var _content_height = _popup_menu.data('content_height') || false;
				var _maincontent_offset = _maincontent.offset();

				var winwidth, winheight;
				if (_lightbox.length) {
					// this thing is in a lightbox. Now  we have to use IT as the window confines.
					winheight = _lightbox.height();
					winwidth = _lightbox.width();
					obj._pos = obj.position();
				}
				else {
					var _win = $J(window);
					winheight = window.innerHeight; // _win.height();
					winwidth = _maincontent.width();
				}

				if (is_ajax_view) {
					_popup_menu.css({
						'overflow': 'auto',
						'width': 'max-content',
						'height': 'max-content',
					});
					if (_content_width) {
						_popup_menu.css({
							'width': _content_width
						});
					}
					if (_content_height) {
						_popup_menu.css({
							'height': _content_height
						});
					}
				}
				else {

				}

				/* Now check position again, now that we have content that might have changed the size */
				var _raw_height = _popup_menu.outerHeight();
				var _raw_width = _popup_menu.outerWidth();
				var list_view_pos = _popup_menu.offset();
				var list_view_left = list_view_pos.left;


				var _maincontent_pos = _maincontent.position();
				var _maincontent_offset = _maincontent.offset();

				var falloff_right = false;
				var falloff_left = false;
				var falloff_bottom = false;

				// We should check to see if the view is bigger than the screensize in either dimension.
				// If it is, make that dimension fit screensize and maximize as well as we can.


				// If it falls off the right of the screen, align it with the right side of the dropdown menu button.
				if (winwidth < (list_view_left + _raw_width - _maincontent_offset.left)) {
					mmlog('popup: falling off right. moving');
					let oow = obj.outerWidth();
					menu_left = Math.round(Math.abs(oow - _raw_width) * -1);
					_popup_menu.css({
						'left': menu_left,
						'right': 0
					});
					falloff_right = true;
				}

				list_view_pos = _popup_menu.offset();
				var _opos = (list_view_pos.left + _raw_width);
				var newleft = _popup_menu.offset().left;

				// If it is now falling off the left of the screen then we don't really have room for it
				if (newleft < 0 || newleft < _maincontent_offset.left) {
					if (_lightbox.length) {

					}
					else {
						mmlog('popup: bigger than screensize. maximizing');
						newleft = (((obj._pos.left - _maincontent_offset.left) * -1 ) + 10);
						_popup_menu.css({
							'left': newleft,
							'width': 'calc(100vw - '+  (_maincontent_offset.left + 30) +'px)'
						});
					}
				}

				newleft = _popup_menu.offset().left;

				// if it is bigger than the available screen size, just make it the screen size and center it.
				if (0) { //(winwidth < _raw_width) {
					mmlog('popup: bigger than screensize. maximizing');
					_popup_menu.css({
						'width': 'calc(100vw - 30px)',
						'left': ((obj._pos.left * -1) + 10) + 'px'
					});
				}


				// Now we need to check the height. If it's falling off the bottom of the document size then
				// we need to make it scrollable and full-screen, like a lightbox. For mobile sizes.
				// Hm. We can't really do that because we'd have to make this thing position:fixed because
				// it is already position absolute, but relative to the button that clicked it.
				// Then we'd have to contend with nav bars, etc. at the top of the screen. And find a
				// proper way to exit this thing that now takes up the entire screen?
				// Sooo. Let's just maybe make the body's min height be the bottom of this popup so that
				// the user can scroll if they want.
				/*
				var body = lt.body || $J('body');
				_raw_height = _popup_menu.outerHeight();
				_body_height = body.height();

				if ((list_view_pos.top + _raw_height) > window.innerHeight) {

					var bottom_of_panel = (list_view_pos.top + _raw_height + 20);
					$J('body').css({'min-height': bottom_of_panel + 'px'});
				}

				 */
			};

			obj.on('open_menu', function(e) {
				mmlog('open_menu called');
				mmlog(obj);
				obj.attr('data-active', 1);
				/*
				var relative_root = $J('#lt2_content_interior');
				var menuPos = obj.position();
				relative_root.css({
					top: menuPos.top,
					left: menuPos.left
				});

				 */
				var button_host = true;
				if (obj.hasClass('nonbutton_host')) {
					button_host = false;
				}

				var _popup_menu = obj.find('.popup_list, .popup_view');
				var objchildren = obj.children();

				var main_button = obj.children('button[role="menu"]').first();
				if (!main_button.length) {
					main_button = obj.children().first();
				}

				obj._h = main_button.outerHeight();
				obj._w = main_button.outerWidth();

				// Check to see if we are in a lightbox. If so things will be positioned relative to it.
				var _lightbox = _popup_menu.parents('.LT_LB2020_wrapper');
				var winwidth, winheight;
				var _win = $J(window);
				if (_lightbox.length) {
					// this thing is in a lightbox. Now  we have to use IT as the window confines.
					winheight = _lightbox.height();
					winwidth = _lightbox.width();
					obj._pos = obj.position();
				}
				else {
					winheight = window.innerHeight; //_win.height();
					winwidth = _win.width();
					obj._pos = main_button.offset();
				}

				/* have to display the menu to get its natural size first, to see if we need to change the scroll */
				/* this is because of a bug in safari. See below. */
				_popup_menu.css({'xleft': '-4000px', 'xmax-height': 'unset', 'overflow-y':'hidden', 'max-width':'unset'}).show();
				var _raw_height = _popup_menu.height();
				var _raw_width = _popup_menu._width = _popup_menu.width();
				//_popup_menu.hide();

				//obj._abs = AbsoluteCoordinates(main_button);
				var _winscroll = _win.scrollTop();
				obj._buttonoffset = obj._pos.top - _winscroll;

				mmlog('Button offset: '+obj._pos.top);
				//mmlog('Button abs: '+obj._abs.top);
				mmlog('Window scrollTop: '+ _winscroll);
				mmlog('Offset - scroll: ' + (obj._pos.top - _winscroll));


				obj._menu_top = (obj._h + 2);
				obj._maxh = Math.round(winheight - (obj._buttonoffset + obj._menu_top) - 10);
				obj._scrolly = (_raw_height > obj._maxh ) ? 'scroll' : 'auto'; /* have to do this because of safari bug with overflow-y: auto not really working */


				obj._offsetLeft = obj._pos.left;
				obj._offsetTop = obj._pos.top;

				var menu_left = 0;
				var maxwidth = winwidth - 10;
				minwidth = obj._w;


				if (_popup_menu.hasClass('popup_list_view')) {
					mmlog('popup_menu: is list view');
					var _content_width = _popup_menu.data('content_width') || false;
					var _content_height = _popup_menu.data('content_height') || false;

					_popup_menu.css({
						'left': menu_left,
						'top': obj._menu_top,
						//'max-width': maxwidth,
						'min-width': obj._w,
						//'max-height': obj._maxh,
						//'overflow-y': obj._scrolly
					});
					if (_content_width) {
						_popup_menu.css({
							'width': _content_width
						});
					}
					if (_content_height) {
						_popup_menu.css({
							'height': _content_height
						});
					}
					_popup_menu.fadeIn(fadeDuration);

					if (_popup_menu.data('ajax-url')) {
						_popup_menu.css({
							'min-width': 'unset',
							'overflow': 'hidden',
						});
						if (_popup_menu.html() === '') {
							_popup_menu.css({
								'min-width': '200px',
								'min-height': '100px'
							});
						}
						var p_id = _popup_menu.attr('id');
						var params = {
							'ui_style_inplace': 1
						};
						fancy_ajax_updater(_popup_menu.data('ajax-url'), params, p_id, function() {
							obj.resize(1);
						});
					}
					else {
						obj.resize(0);
					}


				}
				else {
					// NORMAL MENU HERE.
					// If it is bigger than the window/view width we will use the bigger of:
					// 0. Left-side of obj to the right side of the view
					// 1. Left side of view to right side of obj.
					// 2. Right side of view to left side of obj.
					if (1) { //(_raw_width >= winwidth) {
						var option0 = winwidth - obj._offsetLeft;
						var option1 = obj._offsetLeft - _popup_menu._width;
						var option2 = winwidth - obj._offsetLeft + obj._w;


						// if the menu is smaller than the left-side of obj to the right side of the view
						if (_popup_menu._width < option0) {
							// Then we don't have to do anything.
							_popup_menu.css({
								'overflow-y': obj._scrolly
							});
						}
						else {
							// if option1 is bigger, we will put the menu hanging down from the left side of obj.
							// Like normal.
							if (option1 >= option2) {
								maxwidth = _popup_menu._width;
								menu_left = (_popup_menu._width - obj._w) * -1;
							}
							// if option2 is bigger, we will put the menu hanging down from the right side of obj.
							else {
								maxwidth = obj._pos.left + obj._w - 10;
								_popup_menu._width = _popup_menu.width();
								menu_left = obj._w - maxwidth;
							}

							// IF the minimum width still can't be met. Then we'll use the entire width of the window/view.
							if (_raw_width > minwidth) {

							}
							_popup_menu.css({
								'left': menu_left,
								'width': maxwidth,
								'min-width': obj._w,
								'overflow-y': obj._scrolly
							});
						}
					}



					/*
					// Check to see if it's going to run off the right side of the screen.
					// if so, let's align it with the right side of the element or screen.
					else if (winwidth < (obj._pos.left + _raw_width)) {
						mmlog('_raw_width: ' + _raw_width);
						var _ow = obj.width();
						mmlog('button_width: ' + _ow);
						//debugger;
						//menu_left = (Math.round(Math.abs(_ow - _raw_width)) * -1);
						// try and slide it left to align with the right side of the element.
						menu_left = Math.round((_ow + obj._pos.left - _raw_width));

						// If it is going to place it off the left side of the screen, move it back.
						if (menu_left < 0) {
							menu_left = (obj._pos.left * -1) + 5;
						} else {
							maxwidth = 'calc(100vw + ' + (menu_left * -1) + 'px)';
						}

						//menu_left = menu_left * -1;

						_popup_menu.css({
							'left': menu_left,
							'top': obj._menu_top,
							'max-width': maxwidth,
							'min-width': obj._w,
							'max-height': obj._maxh,
							'overflow-y': obj._scrolly
						}).fadeIn(fadeDuration);
					}
					*/

					// Loop through the menu items and see if any of them need to be cut off or wrapped.

					_popup_menu.find('.ddmi_text').each(function () {
						var ddmi = $J(this);
						var parent = ddmi.parent();
						var pw = parent.outerWidth();
						var dw = ddmi.outerWidth();
						if (obj.hasClass('notification_popup')) {
							// don't break notifications menu items.
						}
						else {
							ddmi.css({'white-space': 'nowrap'});
						}
						if (dw > pw) {
							ddmi.css({'white-space': 'break-spaces'})
						}
					});


					// TODO: vertical placement
					// Vertical placement.
					if (1) {
						// We should see if it's running off the bottom. If so, can we fit it above? If yes, move it. If not, leave it.

					}
					_popup_menu.fadeIn(fadeDuration);
				}
			});

			obj.on('close_menu', function(e) {
				//debugger;
				e.stopImmediatePropagation();
				e.preventDefault();
				mmlog('close_menu triggered on popup_menu');
				mmlog(obj);
				//obj.attr('data-active', 0);
				obj.removeData('active').removeAttr('data-active');
				obj.find('.popup_list').hide();
				obj.find('.popup_view').hide();
				return false;
			});


		});
	};
})($J);

function AbsoluteCoordinates($element) {

	mmgroup('ABS');
	var sTop = $J(window).scrollTop();
	var sLeft = $J(window).scrollLeft();
	var w = $element.width();
	var h = $element.height();
	var offset = $element.position();
	var $p = $element;
	mmlog('offset before loop (element offset): '+offset.left+', '+offset.top);
	while (typeof $p == 'object') {
		try {
			if ($p.get(0) == document.getElementsByTagName("body")[0]) {
				mmlog('stopping because we hit body');
				break;
			}
			var otype = typeof $p;
			var pOffset = $p.parent().position();
			mmlog($p);
			mmlog('pOffset: '+pOffset.left + ', '+pOffset.top);
			if (typeof pOffset == 'undefined') break;
			offset.left = Math.round(offset.left + (pOffset.left));
			offset.top = Math.round(offset.top + (pOffset.top));
			mmlog('offset at end of step: '+offset.left+', '+offset.top);
			$p = $p.parent();
		} catch (err) {
			mmlog(err,'error');
			break;
		}
	}

	var pos = {
		left: offset.left + sLeft,
		right: offset.left + w + sLeft,
		top: offset.top + sTop,
		bottom: offset.top + h + sTop,
	}
	pos.tl = {x: pos.left, y: pos.top};
	pos.tr = {x: pos.right, y: pos.top};
	pos.bl = {x: pos.left, y: pos.bottom};
	pos.br = {x: pos.right, y: pos.bottom};
	//console.log( 'left: ' + pos.left + ' - right: ' + pos.right +' - top: ' + pos.top +' - bottom: ' + pos.bottom  );

	mmgroupend();
	return pos;
}


executeFunctionByName = function(functionName)
{
	var args = Array.prototype.slice.call(arguments).splice(1);
	//debug
	console.log('args:', args);

	var namespaces = functionName.split(".");
	//debug
	console.log('namespaces:', namespaces);

	var func = namespaces.pop();
	//debug
	console.log('func:', func);
	ns = namespaces.join('.');
	//debug
	console.log('namespace:', ns);

	if(ns == '')
	{
		ns = 'window';
	}

	ns = eval(ns);
	//debug
	console.log('evaled namespace:', ns);

	return ns[func].apply(ns, args);
}

lt.accordion_toggle = function() {
	mmlog('accordion_toggle()');
	var that = $J(this);
	var accordion_item = that.parent('.accordion_item');
	if (accordion_item.is('.selected, .preselected'))
	{
		return false;
	}
	var item_id = accordion_item.data('savekey');
	var accordion = accordion_item.parent('.accordion');
	var savekey = accordion.data('savekey');
	if (savekey && item_id) {
		mmlog('saving: '+ savekey + ' = ' + item_id);
		LibraryThing.setSessionData(savekey, item_id);
	}

	var accordion_item_content = accordion_item.children('.accordion_item_content').first();


	accordion.find('.accordion_item_content').slideUp();
	accordion_item_content.slideDown();
	accordion.children('.accordion_item').removeClass('selected preselected');
	accordion_item.addClass('selected');
	return false;
};

lt.rolloverEnter = function() {
	mmlog('lt.rolloverEnter');
	mmlog(this);
	var that = $J(this);
	let classes_to_remove = 'btn-success btn-danger btn-warning btn-primary btn-plain btn-default'
	var orig_width = that.outerWidth();
	var rollover_wording = that.data('rollover_wording') || false;
	var rollover_class = that.data('rollover_class') || false;
	var rollover_orig_class = that.data('rollover_orig_class') || false;
	if (rollover_wording && rollover_wording.length) {
		// LH: these buttons are now all using ui_link_button which creates an
		// <a> tag, so the button text is just the html inside the <a>
		that.attr('data-rollover_wording_orig', that.html());
		that.html(rollover_wording);
	}
	if (rollover_class && rollover_class.length) {
		if (rollover_orig_class && rollover_orig_class.length) {
			that.removeClass(classes_to_remove);
		}
		that.addClass(rollover_class);
	}
	var new_width = that.outerWidth();
	if (new_width < orig_width) {
		that.css('min-width', orig_width);
	}
};
lt.rolloverExit = function() {
	mmlog('lt.rolloverExit');
	mmlog(this);
	var that = $J(this);
	//let classes_to_remove = 'btn-success btn-danger btn-warning btn-primary btn-plain btn-default'
	var orig_width = that.outerWidth();
	var rollover_wording_orig = that.data('rollover_wording_orig') || false;
	var rollover_class = that.data('rollover_class') || false;
	var rollover_orig_class = that.data('rollover_orig_class') || false;
	if (rollover_wording_orig && rollover_wording_orig.length) {
		// LH: these buttons are now all using ui_link_button which creates an
		// <a> tag, so the button text is just the html inside the <a>
		that.html(rollover_wording_orig);
	}
	if (rollover_class && rollover_class.length) {
		that.removeClass(rollover_class);
		if (rollover_orig_class && rollover_orig_class.length) {
			that.addClass(rollover_orig_class);
		}
	}
	var new_width = that.outerWidth();
	if (new_width < orig_width) {
		that.css('min-width', orig_width);
	}
};

/* SUPPORT FOR COVER LOADING ANIMATION and other interesting cover stuff */
lt.onload_c = function(c) {
	var cover = $J(c); //$J(this);
	cover.addClass('lt2_loaded').removeClass('loading');
};
var $C = lt.onload_c;


lt.initSpoilersOnPage = function () {
	lt.body.on('click', 'spoiler', function (event) {
		mmlog('spoiled!');
		$J(event.target).addClass('spoiled');
	});

	lt.body.on('click', 'spoiler.spoiled', function (event) {
		mmlog('unspoiled!');
		$J(event.target).removeClass('spoiled');
	});
};

lt.checkSubnavOcclusion = function() {

};

lt.newFeatureFind = function() {
	return;
	mmgroup('lt.findNewFeatures');
	lt.features_on_page = [];
	$J('[data-newfeature]').each(function() {
		var that = $J(this);
		var featureid = that.attr('data-newfeature');
		mmlog('NewFeatureFind: '+featureid);
		lt.features_on_page.push(featureid);
		var popup = $J('#'+featureid);

		if (popup.length) {
			var offset = that.offset();
			popup.offset(offset);
			popup.fadeIn();
		}
	});

	if (lt.features_on_page.length) {
		var url = 'ajax_feature_popup_check.php';
		var params = {
			features: lt.features_on_page
		};
		var handleCallback = function(r) {
			var rd = JSON.parse(r.responseText);
			mmlog(rd);
		};
		basic_ajax(url, params, handleCallback);
	}

	mmgroupend();
};
lt.newFeatureDismiss = function(featurename) {
	mmlog('lt.newFeatureDismiss: '+featurename);
	var popup = $J('#'+featurename);
	popup.fadeOut();
};

lt.createPiecharts = function(selector) {
	mmgroup('lt.createPiecharts: \''+selector+'\'', true);
	var pies = $J(selector+' .pie:not(.pie_init)');
	pies.each(function(pieIndex, pie) {
		piej = $J(pie);
		var p = parseFloat(pie.getAttribute('data-value'));
		var NS = "http://www.w3.org/2000/svg";
		var svg = document.createElementNS(NS, "svg");
		var circle = document.createElementNS(NS, "circle");

		var titleS;
		circle.setAttribute('class', 'piecircle');
		circle.setAttribute("r", 16);
		circle.setAttribute("cx", 16);
		circle.setAttribute("cy", 16);
		circle.setAttribute("stroke-dasharray", p + " 100");
		svg.setAttribute("viewBox", "0 0 32 32");
		svg.setAttribute('class', 'piesvg');
		titleS = pie.getAttribute('data-title') || pie.textContent;

		pie.textContent = '';
		piej.addClass('pie_init');
		if (titleS) {
			var title = document.createElementNS(NS, "title");
			title.textContent = titleS;
			svg.appendChild(title);
		}
		svg.appendChild(circle);
		pie.appendChild(svg);
	});
	mmgroupend();
};


// when autoscaling text in a clamped item, how far is the jump in font-size for each step.
lt.CLAMP_AUTOSCALE_INTERVAL = 1;
lt.lineclampSeemore = function(selector) {
	mmgroup('lt.lineclampSeemore: \''+selector+'\'', true);
	var seemores = $J(selector+' .clamp,[data-clamp],.clamp1,.clamp2,.clamp3,.clamp4,.clamp5,.clamp6,.clamp7,.clamp8,.clamp9,.clamp10,.clamp11,.clamp12');
	mmlog(seemores);
	seemores.each(function () {
		var that = $J(this);
		if (that.hasClass('clamp_init')) { return; }

		var sh = this.scrollHeight;
		var oh = this.offsetHeight;

		if (that.is('[data-autoscale]')) {
			if (sh <= oh) { that.addClass('clamp clamp_init clamp_no_scaling_needed'); return; }
			else {
				that.addClass('clamp_scaled');
				var _min_scale = that.data('autoscale') || 80; /* default scaling cut-off percentage */
				var _current_scale = 100;
				while (sh > oh && _current_scale > _min_scale) {
					that.css({'font-size' : _current_scale+'%'});
					_current_scale = _current_scale - lt.CLAMP_AUTOSCALE_INTERVAL;
					sh = this.scrollHeight;
					oh = this.offsetHeight;
				}
				// Now that we've clipped the title we probably want to set it as the
				// title attribute (as long as it's not already being used) so that
				// users can hover and get the full name.
				if (!that.attr('title')) {
					that.attr('title', that.text());
				}
			}
		}

		if (that.is('[data-noseemore]')) {
			that.addClass('clamp clamp_init');
			if (!that.attr('title')) {
				that.attr('title', that.text());
			}
			return;
		}


		if (sh <= oh) { return; }
		var id = LT_GetRandomID(6);
		var input = $J('<input type="checkbox" id="' + id + '" class="clamp_seemore_input">');
		var label = $J('<div class="clamp_seemore_label_container"><label role="button" for="' + id + '" class="clamp_seemore_label">('+LibraryThing.ltstrings.show_more.toLowerCase()+')</label></div>');
		that.addClass('clamp clamp_init');
		that.wrap('<div class="clampwrap">');
		that.before(input);
		that.after(label);
		mmlog(that);
		this.classList[sh > oh ? 'add' : 'remove']('truncated');
	});
	mmgroupend();
};

lt.select_sidenav_item = function(itemname) {
	mmlog('lt.select_sidenav_item( '+itemname+ ' )');
	var sidebar_items = $J('#lt_mainsidebar .sidebar_menu_item').removeClass('selected');
	var item = $J('#lt2_sidebar_'+itemname);
	item.addClass('selected');

	// Set the name for the currently selected dropdown menu for mobile.
	$J('#sidebar_dropdownmenu_currentpage_name').html(item.html());
};

lt.fix_sidenavs_if_possible = function() {
	mmgroup('fix_sidenavs_if_possible',1);
	lt.navFixed_ifpossible('lt_mainsidebar');
	lt.navFixed_ifpossible('lt_altsidebar');


	// check for visibility of rightside. Handle replacement of actions area if needed.
	var sidebar_right = $J('#lt_altsidebar');
	if (sidebar_right.is(':visible')) {
		mmlog('lt_altsidebar is visible');
	}
	else {
		mmlog('lt_altsidebar is hidden');
	}
	mmgroupend();
};

// this makes the left nav fixed if there's enough space in browser to do so.
// If the menu is too tall then it will scroll with the rest of the page.
lt.navFixed_ifpossible = function(navid) {
	navid = navid || 'lt_mainsidebar';
	mmlog('lt.navFixed_ifpossible('+navid+')');
	//var pos = 'relative';

	var menu = $J('#'+navid+' .dyn_nav_menu');
	//var o = menu.position();
	var h = menu.height();
	var ho = h + 120; // adding the offset of the top menu + some padding
	var wh = window.innerHeight; //$J(window).height(); // jquery's window height was buggy.

	var biggerThanWindow = (ho > wh);
	const stickyclass = 'stickied';
	var positionedClass = menu.hasClass(stickyclass);

	if (biggerThanWindow) {
		//mmlog('using relative position for '+navid);
		pos = 'relative';
		if (positionedClass) { menu.removeClass(stickyclass); }
	}
	else {
		//mmlog('using fixed position for '+navid);
		pos = 'sticky';
		if (!positionedClass) { menu.addClass(stickyclass); }
	}

	menu.css('position', pos);
	menu.data('position', pos);
};


lt.mobileScrollHandler = function(event) {
	var winRect = $J('body').get(0).getBoundingClientRect();
	//mmlog('mobileScrollHandler');
	var winW = window.innerWidth;
	//mmlog(winW);
	if (winW <= 768) {
		lt.didScroll = true;
		var st = $J(this).scrollTop();

		mmlog(st);
		if (st < 51) {
			$J('.lt768 .topnav_container').css('top', -(st) + 'px');
			$J('#lt2_navlogo_mobile').css('top', -(st) + 'px');
		} else if (st > 50) {
			$J('.lt768 .topnav_container').css('top', '-50px');
			$J('#lt2_navlogo_mobile').css('top', '-50px');
		}
		lt.maxScrollTop = st;
	}
};

lt.sidenav_accordion_handle_click = function(e) {
	mmlog('lt2_main: lt.sidenav_accordion_handle_click()');
	var target = $J(e.target);
	if (!target.hasClass('lt2_sidebar_accordion_parent')) {
		target = target.parents('.lt2_sidebar_accordion_parent').first();
	}

	var isOpen;
	targetid = target.attr('id');
	if (e.altKey) {
		// option key was pressed when clicking. Toggle all items.
	}
	if (target.hasClass('open')) {
		target.removeClass('open');
		$J('.lt2_sidebar_accordion_items[data-parent="'+targetid+'"]').removeClass('open');
		isOpen = 0;
	}
	else {
		target.addClass('open');
		$J('.lt2_sidebar_accordion_items[data-parent="'+targetid+'"]').addClass('open');
		isOpen = 1;
	}

	mmlog(target);

	var pref_key = target.data('prefs_key');
	if (typeof pref_key !== typeof undefined) {
		LibraryThing.setSessionData(pref_key, isOpen);
	}
	return false;
};


lt.yall = function(selector) {
	mmgroup('yalling images to lazy load them: \''+selector+'\'');
	/*
	try {
		yall({
			observeChanges: true,
			//idlyLoad: true,
			threshold: 600,
			observeRootSelector: selector
		});
	} catch(err) {mmlog(err,'error'); mmgroupend();}

	// */
	mmgroupend();
}

lt.init_tooltips = function(selector) {
	if (window.Popper) {
		mmgroup("lt.init_tooltips: '"+selector+"'", 0);
		var _select = selector + ' [role="tooltip"]';
		$J(_select.trim()).each(function () {
			var popup = this;
			var anchor = document.querySelector('[aria-describedby="'+this.id+'"]');
			var popper = Popper.createPopper(anchor, popup);
			mmlog(this);
			mmlog(anchor);
			mmlog(popper);
		});
		mmgroupend();
	}
}

lt.prettify_dropzones = function(selector) {
	// find dropzones and attach to them.
	mmgroup("lt.prettify_dropzones: '"+selector+"'", true);
	var _select = selector + ' .dropper';
	var err = false;
	$J(_select.trim()).each(function () {
		if (window.Dropzone) {
			var dz = $J(this);
			var target = dz.data('url');

			$J(this).dropzone({
				url: target
			}).addClass('dropzone');
		}
		else {
			err = "Dropzone found but supporting JS/CSS is not loaded on this page.";
		}
	});
	mmgroupend();

	if (err) {
		mmlog(err, 'error');
	}
};

lt.prettify_advanced_text_editors = function(selector) {
	var _select = selector + ' .lt2_advanced_text_editor';

	$J(_select.trim()).each(function () {
		var textarea = $J(this);
		if (textarea.hasClass('ate_preinit'))
		{
			textarea.on('click', function() {
				textarea_init_function();
				textarea.siblings('.trumbowyg-editor').first().focus();
			});
		}
		else
		{
			textarea_init_function();
		}

		function textarea_init_function() {
			var defaultButtons = {
				html: ['viewHTML'],
				//undo: ['undo', 'redo'], // Only supported in Blink browsers
				formatting: ['formatting'],
				text: ['strong', 'em'],
				links: ['link'],
				images: ['insertImage'],
				justification: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
				lists: ['unorderedList', 'orderedList'],
				//dividers: ['horizontalRule'],
				//format: ['removeformat'],
				//lt: ['touchstones'],
				fullscreen: ['fullscreen']
			};

			// Options
			if (textarea.data('button-links') == 0) {
				delete defaultButtons['links'];
			}
			if (textarea.data('button-images') == 0) {
				delete defaultButtons['images'];
			}
			if (textarea.data('button-formatting') == 0) {
				delete defaultButtons['formatting'];
			}
			if(textarea.data('button-justification') == 0) {
				delete defaultButtons['justification'];
			}
			if (textarea.data('isadmin') == 1) {
				// Admins get access to more buttons and formatting features.
				defaultButtons = {
					html: ['viewHTML'],
					undo: ['undo', 'redo'], // Only supported in Blink browsers
					formatting: ['formatting'],
					text: ['strong', 'em'],
					links: ['link'],
					images: ['insertImage'],
					justification: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'],
					lists: ['unorderedList', 'orderedList'],
					dividers: ['horizontalRule'],
					format: ['removeformat'],
					lt: ['touchstones'],
					fullscreen: ['fullscreen']
				};
			}

			try {
				textarea.siblings('.textarea_head, .textarea_foot').remove();
				textarea.removeClass('ate_preinit').trumbowyg({
					semantic: true,
					autogrow: true,
					urlProtocol: true,
					autogrowOnEnter: true,
					minimalLinks: true,
					removeformatPasted: true,
					imageWidthModalEdit: true,
					defaultLinkTarget: '_blank',
					tagsToRemove: ['script', 'link', 'iframe', 'embed'],
					btns: defaultButtons,
					svgPath: 'https://i696d616765o6c6962726172797468696e67o636f6dz.oszar.com/libs/trumbowyg/ui/icons2.svg'
				}).addClass('ate_init')
					.on('tbwfocus', function(){
						textarea.siblings('.trumbowyg-button-pane').first().show();
						var tp = textarea.parent();
						var newfeature_box = $J('[data-new_feature_key="newfeature_advanced_text_editor"]').first();
						if (newfeature_box.length) {
							newfeature_box.insertBefore(tp).css('width', tp.width()).show();
						}
					})
					.on('tbwblur', function(){
					});
				textarea.siblings('.trumbowyg-button-pane').first().hide();

				//textarea.off('focus.ate');
			} catch (err) {
				mmlog(err, 'error');
			}
		};

	});
};

/*
 * This will change/add/remove the badge from a sidenav item
 * It handles some empty values (but probably not all possible variations)
 */

lt.sidenav_badge_content_change = function(sidenav_el, content) {
	var sidenav_item = $J(sidenav_el);
	if (!sidenav_item.length) {
		sidenav_item = $J('#'+sidenav_el);
	}

	if (sidenav_item.length) {
		var badge = sidenav_item.find('.badge').first();
		var has_content = (content && (content !== '') && (content !== 0) && (content !== '0'));

		var prev_content = badge.text();
		if (badge.length) {
			if (!has_content) {
				badge.hide();
			}
			else {
				badge.html(content).show();
				if (prev_content != content) {
					badge.pulse({pulse_count:3});
				}
			}
		}
		else if (has_content) {
			badge = $J('<span class="badge">'+content+'</span>');
			sidenav_item.append(badge);
		}
	}
}

lt.update_last_interaction = function(e) {
	return;
	let prev_interaction = lt.last_interaction;
	lt.last_interaction = new Date();
	var time_diff =  lt.last_interaction - prev_interaction;

	lt.refresh_message_counts_interval_time = lt.refresh_message_counts_interval_time_default;

	if (time_diff <= lt.refresh_message_counts_interval_time_default) {
		//mmlog('resetting time_diff to: '+ (lt.refresh_message_counts_interval_time_default / 1000)+'secs');
		lt.refresh_message_counts_interval_time = lt.refresh_message_counts_interval_time_default;
		clearInterval(lt.refresh_message_counts_interval);
		lt.refresh_message_counts_interval = setInterval(lt.refresh_message_counts, lt.refresh_message_counts_interval_time);
	}
	//mmlog('LT: update_last_interaction: '+lt.last_interaction+'/'+time_diff);
}

lt.refresh_message_counts = function() {
	var current_time = new Date();
	var time_diff = current_time - lt.last_interaction;
	if (0) { //(time_diff >= (lt.refresh_message_counts_interval_time * 2) ) {
		// longer than 5 minutes idle. Slow down the interval.
		clearInterval(lt.refresh_message_counts_interval);
		lt.refresh_message_counts_interval_time = Math.round(time_diff);
		mmlog('Inactive: stretching refresh interval to: '+ Math.round(lt.refresh_message_counts_interval_time / 1000) + 'secs');
		lt.refresh_message_counts_interval = setInterval(lt.refresh_message_counts, lt.refresh_message_counts_interval_time);
		return;
	}

	//var url='/ajax_newcomments_refreshcounts.php';
	var url='/ajax_newcomments_refreshcounts_new.php';
	var params = {};
	var callbackF = function(r) {
		try {
			if (r.status) {
				if (r.status >= 400) {
					clearInterval(lt.refresh_message_counts_interval);
					return;
				}
			}
			mmlog('lt2_main->refresh_counts->callbackF()');
			var rt = $J.parseJSON(r.responseText);

			// if there is a content item then just replace [data-id="userpad_notifications"] with the content.
			if (typeof rt.content !== 'undefined') {
				var notifications_mast_item = $J('.mastuseritem[data-id="userpad_notifications"]');
				if (notifications_mast_item.length) {
					notifications_mast_item.html(rt.content);
					notifications_mast_item.find('.lt_popup_menu').lt_popup_menu();
					//notifications_mast_item.find('.lt_notifier_control').pulse();
				}


			}
			// set the subnav count
			//var subnav_messages = $J('#subnav_item_messages');
			lt.sidenav_badge_content_change('subnav_item_messages', rt.all);

			// and the mobile badge
			lt.sidenav_badge_content_change('ltpad_username', rt.all);

			// Set the sidebar counts
			lt.sidenav_badge_content_change('lt2_sidebar_messages_inbox', rt.all);

			// Go through each conversation item and set the count, adding divs/spans if needed
			var conversations = $J('.sidebar_group.messages_conversations .sidebar_menu_item');
			if (conversations.length) {
				conversations.each(function () {
					var that = $J(this);
					var eid = that.attr('id');
					var cnum = eid.replace(/^lt2_sidebar_messages_/, '');
					if (rt[cnum]) {
						lt.sidenav_badge_content_change(that, rt[cnum]);
					} else {
						lt.sidenav_badge_content_change(that, 0);
					}
				});
			}
		} catch(err) {

		}
	};
	basic_ajax(url, params, callbackF, 'lt_refresh_message_counts');
}

lt.validate_forms = function(selector) {
	mmgroup("lt.validate_forms: '"+selector+"'", 0);
	// validate is extended in lt_validator.js
	if ($J.fn.validate) {
		$J('form[data-validate]:not(.validator_init)').each(function() {
			$J(this).validate();
		});
	}
	mmgroupend();
};


lt.prettify_datatables = function(selector) {
	/* set up data tables if they are on the page and Datatable is loaded */
	/* https://i777777o646174617461626c6573o6e6574z.oszar.com/manual/ */
	var errormsg = false;
	mmgroup("lt.prettify_datatables: '"+selector+"'", true);

	var _select = selector + ' table.dataize:not(.dataTable)';
	mmlog('selecting based on: ' + _select.trim());
	var tables_to_dataize = $J(_select.trim());
	if (tables_to_dataize.length) {
		if ($J.fn.dataTable) {
			tables_to_dataize.each(function () {
				var table = $J(this);
				var order = [];
				var orderby = table.data('orderby');
				var orderdir = table.data('orderdirection') || 'asc';
				var orderby2 = table.data('orderby2');
				var orderdir2 = table.data('orderdirection2') || 'asc';
				var limit = table.data('limit');
				var paginate = table.data('paginate');
				paginate = (typeof paginate != typeof undefined) ? !!paginate : true;
				// LH: changing to check for undefined, otherwise overwrites 0/false value
				if (typeof limit == typeof undefined) {
					limit = '25';
				}
				var search = table.data('search');
				if (typeof search == typeof undefined) {
					search = true;
				}
				var columnDefs = [];
				var tableheads = table.find('th').each(function (index, th) {
					var that_th = $J(th);
					let th_sortable = that_th.attr('data-sortable');
					let th_columntype = that_th.attr('data-columntype');
					if (typeof th_sortable == typeof undefined) {
						th_sortable = true;
					} else {
						th_sortable = !!parseInt(th_sortable);
					}
					columnDefs[index] = columnDefs[index] || {};
					if (typeof th_columntype != 'undefined') {
						columnDefs[index].type = th_columntype;
					}
					columnDefs[index].orderable = !!th_sortable;
					// LH: If data-order-sequence is sent in as a negative number, set the
					// order sequence to sort descending on the first click, then ascending
					// on the second
					var orderSeq = that_th.data('order-sequence');
					if(orderSeq && parseInt(orderSeq) < 0) {
						columnDefs[index].orderSequence = ['desc','asc'];
					}
				});
				mmlog(columnDefs);
				var search_label = table.data('searchlabel');


				// LH: determine if the Show [x] entries should be displayed
				var show_filtered_entries = table.data('filterentries');
				if (typeof show_filtered_entries == typeof undefined) {
					show_filtered_entries = true;
				}
				var lengthMenu = {};
				if (show_filtered_entries) {
					lengthMenu = [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]];
					if (limit && paginate) {
						paginate = true;
						if (lengthMenu[0][0] < limit) {
							lengthMenu[0].shift();
							lengthMenu[1].shift();
						}
						if (lengthMenu[0].indexOf(limit) == -1) {
							lengthMenu[0].unshift(limit);
							lengthMenu[1].unshift(limit);
						}
					}
				}
				if (typeof orderby != typeof undefined) {
					order = [[orderby, orderdir]]
				}
				if (typeof orderby2 != typeof undefined) {
					order[1] = [orderby2, orderdir2];
				}
				if (search_label != undefined) {
					LibraryThing.ltstrings.datatables.search = search_label;// + ':';
				}
				let show_page_of = true;
				if (!paginate) {
					show_page_of = false;
				}
				var dataTableSettings = {
					ordering: true,
					pageLength: limit,
					paging: paginate,
					'bInfo': show_page_of,
					responsive: true,
					searching: search,
					'order': order,
					'lengthMenu': lengthMenu,
					'lengthChange': show_filtered_entries,
					'language': LibraryThing.ltstrings.datatables,
					columns: columnDefs
				};
				mmlog(dataTableSettings);
				$J(this).DataTable(dataTableSettings);
			});
		}
		else {
			errormsg = 'DATATABLES not available! Are they in the factory?';
		}
	}

	mmgroupend();
	if (errormsg) {
		mmlog(errormsg, 'warn');
	}
};

lt.build_date_pickers = function(selector) {
	//return;
	selector = selector || '';
	//mmgroup("lt.prettify_content["+ajax_called_me+']:'+selector, 1);
	//litsy_yall();
	mmgroup("lt.build_date_pickers: \'"+selector+'\'', true);
	var dateElements = $J(selector+' .date_input:not(.date_input_inited)');
	if (dateElements.length) {
		dateElements.each(function() {
			var that = $J(this);
			var date_format = 'mm/dd/yyyy';


			var _date_format_attrib = that.data('date_format');
			if (_date_format_attrib) {
				date_format = _date_format_attrib;//.toLowerCase();
			}

			that.addClass('date_input_inited');
			that.datepicker({
				// TODO: this needs to be based on the user's date preferences.
				// To do that we probably need to set the date format in a data attribute
				// so that we can send it in from the backend on each date field.
				// Alternatively, we could set it in the translated JS code...but that
				// gets the setting away from the control, which I'd like to avoid.
				//format: date_format,
				format: {
					toValue(datestring, format, locale) {
						var date;
						// Get the user's timezone offset for figuring out date stamps
						var _tz_date = new Date();
						var _userTimezoneOffset = _tz_date.getTimezoneOffset() * 60000;

						var date_stamp = that.data('date_stamp');
						if (date_stamp) {
							date_stamp = date_stamp * 1000; // difference in s vs ms.
							var _timestamp_offset = date_stamp + (_userTimezoneOffset * 2);
							date = new Date(_timestamp_offset);
						} else {
							if (date_format === 'd-m-yyyy') {
								// js date parser doesn't parse this one correctly. Need to help it.
								// we can reverse the elements and send it through.
								var _dateA = datestring.split('-');
								datestring = parseInt(_dateA[1]) + '-' + parseInt(_dateA[0]) + '-' + _dateA[2];
							} else if (date_format === 'M d, yyyy') {
								var timestamp = Date.parse(datestring);
							}
							var timestamp = Date.parse(datestring);
							var _timestamp_offset = timestamp + (_userTimezoneOffset * 2);
							date = new Date(_timestamp_offset);
						}
						return date;
					},
					toDisplay(date, format, locale) {
						that.attr('data-date_stamp', Math.floor(date.valueOf() / 1000));
						that.data('date_stamp', Math.floor(date.valueOf() / 1000));
						var _formattedDate = lt.dateStringFormater(date, date_format, locale);
						return _formattedDate;
					},
				},

				buttonClass: 'btn',
				autohide: true,
				//todayHighlight: true,
				clearBtn: true,
				//todayBtn: true,
				daysOfWeekHighlighted: [0, 6],
				nextArrow: '<i class="fa-solid fa-chevron-right"></i>',
				prevArrow: '<i class="fa-solid fa-chevron-left"></i>'
			});

		})
	}
	mmgroupend();
};

/* needed for our date picker so that it can format non-standard JS date formats (read: PHP formats) */
lt.dateStringFormater = function(date, format, locale) {
	var separator = '-';
	var day = date.getDate();
	// add +1 to month because getMonth() returns month from 0 to 11
	var month = date.getMonth() + 1;
	var year = date.getFullYear();

	var day_padded, month_padded;

	var _month, _months;

	// show date and month in two digits
	// if month is less than 10, add a 0 before it
	if (day < 10) {
		day_padded = '0' + day;
	}
	if (month < 10) {
		month_padded = '0' + month;
	}

	if (format === 'yyyy-mm-dd') {
		// 2013-12-25
		return date.toISOString().substring(0, 10);
	}
	else if (format === 'm/d/yyyy') {
		// 12/25/2013
		return month + '/' + day + '/' + year;
	}
	else if (format === 'd-m-yyyy') {
		// d-m-yyyy
		return day + '-' + month_padded + '-' + year;
	}
	else if (format === 'd M yyyy') {
		// 25 Dec 2013
		month = date.getMonth();
		_month = month;
		try {
			if (locale) {
				if (locale.monthsShort) {
					_month = locale.monthsShort[month];
				}
				else {
					throw('no locale object.');
				}
			}
			else {
				throw('no locale object.');
			}
		} catch(err) {
			_months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
			_month = _months[month];
		}
		return day + ' ' + _month + ' ' + year;
	}
	else if (format === 'M d, yyyy') {
		// Dec 25, 2013
		month = date.getMonth();
		_month = month;
		try {
			if (locale) {
				if (locale.monthsShort) {
					_month = locale.monthsShort[month];
				}
				else {
					throw('no locale object.');
				}
			}
			else {
				throw('no locale object.');
			}
		} catch(err) {
			_months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
			_month = _months[month];
		}
		return _month + ' ' + day + ', ' + year;
	}
	else if (format === 'd MM yyyy') {
		// 25 December 2013
		month = date.getMonth();
		_month = month;
		try {
			if (locale) {
				if (locale.months) {
					_month = locale.months[month];
				}
				else {
					throw('no locale object.');
				}
			}
			else {
				throw('no locale object.');
			}
		} catch(err) {
			_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
			_month = _months[month];
		}
		return day + ' ' + _month + ' ' + year;
	}
	else if (format === 'MM d, yyyy') {
		// December 25, 2013
		month = date.getMonth();
		_month = month;
		try {
			if (locale) {
				if (locale.months) {
					_month = locale.months[month];
				}
				else {
					throw('no locale object.');
				}
			}
			else {
				throw('no locale object.');
			}
		} catch(err) {
			_months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
			_month = _months[month];
		}
		return _month + ' ' + day + ', ' + year;
	}


	return date.toISOString().substring(0, 10);

	// now we have day, month and year
	// use the separator to join them
	//return day + separator + month + separator + year;
}

lt.stars_hovers = function(selector) {
	mmgroup("lt.stars_hovers: \'"+selector+'\'', true);
	//var stars = $J('.rating.editable i');
	$J(selector+' .rating.editable i').off('mouseenter mouseleave').hover(function star_mouse_in(e) {
		var hstar = $J(this);
		hstar.addClass('hovering');
		var all = hstar.siblings('i').removeClass('hovering');
		var prev = hstar.addClass('hovering').prevAll().addClass('hovering');
	}, function() {
		//$J(this).parent().children().removeClass('hovering');
	});

	$J(selector+' .rating.editable').off('mouseenter mouseleave').on('mouseleave', function rating_mouse_out(e) {
		$J(this).children().removeClass('hovering');
	});
	mmgroupend();
};

// <a class="follow_star follow_star-1-233" href="#" onclick="lt.follow_star(e, item_type, item_id)"><i class="fa-solid fa-star"></i></a>

lt.follow_star = function(e, item_type, item_id) {
	var target = e.target || e.srcElement;
	target = $J(target);
	var stars = $J('.follow_star-'+item_type+'-'+item_id+' i');
	var state = target.hasClass('selected');
	if (state) {
		stars.removeClass('selected');
	}
	else {
		stars.addClass('selected');
	}
	// at this point the UI is correct. Eventual consistency

	var url = '/ajax_toggle_follow_star.php';
	var params = {
		'follow_type_id'		: item_type,
		'follow_item_id'		: item_id,
		'follow_state'			: (!state) ? 1 : 0
	};
	var callback = function(r) {
		var response = JSON.parse(r.responseText);
		if(response.toast_msg) {
			lt.toast(response.toast_msg);
		}
	};

	basic_ajax( url, params, null, 'follow_star-'+item_type+'-'+item_id, callback);
}


lt.init_clipboxes = function(selector) {
	mmgroup("lt.init_clipboxes: "+selector, true);
	var _select = selector + ' .lt_clipbox:not(.cpinit)';
	var boxes = $J(_select.trim());
	boxes.each(function() {
		var that = $J(this);
		var columnized = false;
		if (that.hasClass('columnized') && that.hasClass('compact')) {
			columnized = true;
		}
		var data_height = parseInt(that.data('clipbox')) || 240; // default val
		var _orig_height = this.offsetHeight;
		var _orig_width = this.offsetWidth;
		var _orig_sc = this.scrollHeight;
		var _orig_sw = this.scrollWidth;
		if (data_height) {
			that.css({
				'max-height': data_height
			});
		}
		var _sc = this.scrollHeight;
		var _seemoreT = that.attr('data-seemore_text') || LibraryThing.ltstrings.see_more;
		var _seelessT = that.attr('data-seeless_text') || LibraryThing.ltstrings.see_less;
		if ((this.offsetHeight < _sc) || (columnized && this.offsetWidth < _orig_sw) ) {
			var ex = $J('<div class="clipbox_expander"></div>');
			var linkclass = 'btn btn-sm btn-default';
			if (that.data('expander_class')) {
				linkclass = that.data('expander_class');
			}
			var exb = $J('<a class="clipbox_expanderB">'+ _seemoreT + '</a>');
			exb.addClass(linkclass);
			ex.append(exb);
			that.append(ex).addClass('cpinit');
			exb.on('click', function() {
				columnized = false;
				if (that.hasClass('columnized') && that.hasClass('compact')) {
					columnized = true;
				}
				const anim_duration = 500;
				//ex.remove();
				if (ex.hasClass('expanded')) {
					// close it
					exb.html(_seemoreT);
					ex.removeClass('expanded');
					if (columnized) {
						that.css({
							'max-height': data_height,
							'display'	: 'block'
						});
					}
					else {
						that.css({
							'max-height': data_height,
						});
					}

				}
				else {
					// open it
					exb.html(_seelessT);
					ex.addClass('expanded');
					if (columnized) {
						that.css({
							'max-height': 'unset',
							'display'	: 'inline-block'
						});
					}
					else {
						that.animate({
							'max-height' : _sc + ex.outerHeight() /* adding in the height of the now-relative (expanded) 'show less' */
						}, {
							duration: anim_duration,
							easing: "easeOutExpo",
							complete: function() {
								that.css({
									'max-height': 'unset'
								});
							}
						});
					}

				}
			});
		}
		else {
			// The box is smaller than the prescribed height.
			// Maybe we want to compact it or do some other layout changes.
		}
	});
	mmgroupend();
};

// copied from SU lists implementation...needs to be changed to fit LT2 general usage.
// This is the LISTS overflow check that needs to be fixed. (ch)
lt.container_overflow_check = function (parent, child) {
	mmlog('su_librarianpower.js->check_lists_seeall()');
	var sections = lt.elf(parent);
	var showItems = function(parentid) {
		var parent_el = lt.elf(parentid);
		parent_el
	};
	if (sections.length) {
		sections.each(function() {
			var section = $J(this);
			//var list = section.find('.libpow_list').first();
			var has_hidden_items = lt.overflow_items_check(section, child);
			var seeall = section.next('.list_seeall_footer');
			var section_id = section.attr('id');
			if (!section_id) {
				section.attr('id', uniqid(4));
			}
			if (!seeall.length) {
				var seemore_text = section.attr('data-seemore_text') || LibraryThing.ltstrings.show_more;
				seeall = $J('<div class="gen_smc"><a onclick="return showItems(\''+section.attr('id')+'\');" class="btn btn-default btn-sm card_list_showmore"><span class="smct">' + seemore_text + '</span> <i class="fas fa-caret-down"></i></a></div>');
				section.after(seeall);
			}

			if (has_hidden_items) {
				seeall.css('visibility', 'visible').slideDown();
			}
			else {
				seeall.css('visibility', 'hidden').slideUp();
			}
		});
	}
};

lt.check_cardlists = function(selector) {
	mmgroup('lt.check_cardlists: \''+selector+'\'',1);
	var _select = selector + ' .card_list:not(.ltinit):not(.lt_clipbox)';// :not([rows="all"])
	//const _selector_default = '.card_list:not(.ltinit):not(.lt_clipbox)';
	//selector = selector || _selector_default;
	//if (selector != _selector_default) { mmlog('Using custom cardlist selector.', 'warn'); }
	var card_listA = $J(_select.trim());
	//mmlog({card_listA});

	card_listA.each(function() {
		var card_list = $J(this);
		var id = card_list.attr('id');
		if (id == undefined) {
			card_list.attr('id',uniqid(5));
		}
		var hide_overflow_toggle = card_list.attr('data-hide_overflow_toggle') || 0;

		lt.card_list_overflow_check(card_list, hide_overflow_toggle);

	});
	mmgroupend();

	var card_list_toggles = $J('.card_list_viewtoggle').each(function() {
		var toggle = $J(this);
		var target = toggle.data('control-target');
	});
};

lt.overflow_items_check = function(list, child_selector) {
	list = $J(list);
	list.has_hidden_items = false;
	child_selector = child_selector || '';
	// No way to tell what is showing by height.
	// Maybe we can get the children and use is:visible? Nope.
	// Ok. So maybe we can calculate based on the innerHeight, because it's just the padding if it's not showing.
	// ... but that means we have to calculate based on padding information.
	var children = list.children(child_selector);
	var avg_h = 0;
	var total_h = 0;
	var h_count = 0;
	children.each(function() {
		var c = $J(this);
		c.removeClass('clinvis');
		total_padding = Math.round(parseInt(c.css('padding-top').replace(/[^-\d\.]/g, '')) + parseInt(c.css('padding-bottom').replace(/[^-\d\.]/g, '')));
		var h = Math.round(c.innerHeight());
		if (total_padding == h) {
			// this guy is not showing. I think.
			list.has_hidden_items = true;
			c.addClass('clinvis');
		}
		else {
			h_count++;
			total_h += h;
		}
	});

	return list.has_hidden_items;
}

lt.card_list_overflow_check = function(card_list, hide_overflow_toggle) {
	hide_overflow_toggle = hide_overflow_toggle || 0;
	card_list.some_are_hidden = false;
	// No way to tell what is showing by height.
	// Maybe we can get the children and use is:visible? Nope.
	// Ok. So maybe we can calculate based on the innerHeight, because it's just the padding if it's not showing.
	// ... but that means we have to calculate based on padding information.
	var children = card_list.children();
	var avg_h = 0;
	var total_h = 0;
	var h_count = 0;
	var filtered = false;
	var filter_attr = card_list.attr('rows-filter');
	if (typeof filter_attr !== typeof undefined && filter_attr !== false) {
		if (!card_list.hasClass('rows_all')) {
			card_list.addClass('rows_all');
		}
		filtered = true;
	}

	children.each(function() {
		var c = $J(this);
		c.removeClass('clinvis');
		total_padding = Math.round(parseInt(c.css('padding-top').replace(/[^-\d\.]/g, '')) + parseInt(c.css('padding-bottom').replace(/[^-\d\.]/g, '')));
		var h = Math.round(c.innerHeight());
		var should_be_filtered = false;
		if (filtered) {
			should_be_filtered = c.hasClass(filter_attr);
		}
		if (total_padding == h || should_be_filtered) {
			// this guy is not showing. I think.
			card_list.some_are_hidden = true;
			c.addClass('clinvis');
		}
		else {
			h_count++;
			total_h += h;
		}
	});

	// We are going to use the average height so that we can do a nicer transition when doing a seemore.
	if (total_h) {
		card_list.attr('data-cl_avgh', Math.round((total_h / h_count)));
	}

	var card_list_invis = card_list.children('.clinvis');
	var smc = card_list.next('.cl_smc');
	var is_exposed = Boolean(Number(card_list.attr('data-clexposed')));

	if (card_list.some_are_hidden == true) {
		card_list.attr('data-cloverflow', 1);
		if (!smc.length) {
			var seemore_text = card_list.attr('data-seemore_text') || LibraryThing.ltstrings.show_more;
			smc = $J('<div class="cl_smc flex_center"><a onclick="return lt.toggle_cardlist(\''+card_list.attr('id')+'\');" class="btn btn-default btn-sm card_list_showmore"><span class="smct">' + seemore_text + '</span> <i class="fas fa-caret-down"></i></a></div>');
			card_list.after(smc);
		}

		var card_list_invis_count = card_list.children('.clinvis').length;
		if (card_list_invis_count && !hide_overflow_toggle) {
			smc.show();
		}
		else {
			smc.hide();
		}
	}
	else if (card_list.some_are_hidden == false && !is_exposed) {
		card_list.attr('data-cloverflow', 0);
		//card_list.removeAttr('data-cloverflow');
		//card_list.next('.cl_smc').hide();

		if (card_list_invis_count) {
			smc.show();
		}
		else {
			smc.hide();
		}

		//var sm = card_list.next('.card_list_showmore');
		//sm.remove();
	}
	return false;
};
lt.cardlist_viewstyle_select = function(viewstyle) {
	mmlog('lt.cardlist_viewstyle_select('+viewstyle+')');
	var that = $J(this);
	var parent = that.parent('.card_list_viewtoggle');
	var targetlist_id = parent.data('control-target');

	if (typeof targetlist_id !== 'undefined' && targetlist_id) {
		var targetlist = $J('#' + targetlist_id);

		var targetlist_rows_full = targetlist.attr('rows-full');
		var targetlist_rows_compact = targetlist.attr('rows-compact');

		targetlist.removeClass('compact');

		var val = that.attr('value');
		var target_option = targetlist.data(val+'_rows');
		//that.siblings().removeClass('selected');
		//that.addClass('selected');
		if (typeof val !== 'undefined' && val) {
			targetlist.addClass(val);
			mmlog(target_option);

			if (val == 'compact') {
				if (typeof targetlist_rows_compact !== 'undefined' && targetlist_rows_compact) {
					targetlist.attr('rows', targetlist_rows_compact);
				}
			}
			else if (val == 'full') {
				if (typeof targetlist_rows_full !== 'undefined' && targetlist_rows_full) {
					targetlist.attr('rows', targetlist_rows_full);
				}
			}
		}
	}
};

lt.toggle_cardlist = function (cardid) {
	mmgroup('lt.toggle_cardlist');
	mmlog(cardid);
	var card_list = $J('.card_list#'+cardid);
	mmlog(card_list);
	var exposed = Boolean(Number(card_list.attr('data-clexposed') || 0));
	var v = card_list.css('grid-auto-rows');
	mmlog('v');
	mmlog(v);
	var avg_h = card_list.attr('data-cl_avgh');
	var nv = 0;
	var smc = card_list.next('.cl_smc');
	var smct = smc.find('.smct');
	var icon = smc.find('i.fas');
	icon.toggleClass('fa-caret-up fa-caret-down');
	var seeless_text = card_list.attr('data-seeless_text') || LibraryThing.ltstrings.show_less;
	var seemore_text = card_list.attr('data-seemore_text') || LibraryThing.ltstrings.show_more;

	// Change the button to a show less
	if (exposed) {
		// slide back up.
		card_list.css('grid-auto-rows', '0');

		smct.text(seemore_text);
		card_list.attr('data-clexposed', 0);
	}
	else {
		// Change the button text/icon
		smct.text(seeless_text);
		card_list.attr('data-clexposed', 1);


		if (avg_h) {
			nv = avg_h;
		} else {
			nv = (v == '0px' || parseInt(v) == 0) ? 'unset' : 0;
		}
		var card_list_invis = card_list.children('.clinvis');
		card_list.animate({
			'grid-auto-rows': nv
		}, {
			easing: 'easeOutExpo',
			duration: 300,
			start: function () {
				card_list_invis.css('opacity', 0);
				//card_list_invis.animate({'opacity': 1}, 1000);
				//card_list.children().removeClass('clinvis');
				var timeoffset = 100;
				card_list_invis.each(function () {
					var invis = $J(this);
					setTimeout(function () {
						invis.animate({'opacity': 1}, 500);
					}, timeoffset);
					timeoffset += 30; //this controls speed of the fade in of the single cards.
				});
			},
			complete: function () {
				card_list.css('grid-auto-rows', 'unset');
				//card_list_invis.removeClass('clinvis');

			}
		});
	}
	//card_list.css('grid-auto-rows', nv);
	//lt.card_list_overflow_check(card_list);
	mmgroupend();
	return false;
};

lt.a11y.fontawesome = function(selector) {
	mmgroup("lt.a11y.fontawesome: [document]", true);
	var icons = $J(':not(a) i.fa, :not(a) i.fas, :not(a) i.far');
	icons.each(function() {
		$J(this).attr({
			'aria-hidden'	: true,
			'data-a11y'		: true
		})
	});
	mmgroupend();
};

lt.a11y.covers = function(selector) {
	mmgroup("lt.a11y.covers: '"+selector+'\'', true);
	var _select = selector + ' [data-workid] > img:not(".ally_cover")';
	var images = $J(_select.trim());
	images.each(function() {
		var img = $J(this);
		var alt = img.attr('alt');
		if (alt == undefined) {
			//mmlog(img, 'warn');
			img.attr('alt', LibraryThing.ltstrings.alt_cover_image).
				attr('aria-label', LibraryThing.ltstrings.alt_cover_image).
				addClass('ally_cover');
		}
	});
	mmgroupend();
};


lt.truncate = function(el) {
	if (!el) {
		el = 'body'
	}
	mmgroup('lt.truncate: '+el, true);
	var jel = $J(el);
	jel.find(".truncate:not(.truncated)").each(function() {
		var that = $J(this);
		//if (that.hasClass('truncated')) { return; }
		var data_len = that.data("truncate");
		var tlen = data_len ? data_len : 240;
		that.data('truncated', 1);
		that.truncate({max_length: tlen});
	});
	mmgroupend();
};


lt.setup_rolldowns = function(selector) {
	mmgroup('lt.setup_rolldowns: '+selector, true);
	$J(selector+' .rolldown:not(.rolldown_init)').each(function() {
		var rd = $J(this);
		mmlog(rd);
		rd.addClass('rolldown_init');
		var rd_visible = rd.is(':visible');
		var rd_show = rd.data('all') || rd.data('more') || LibraryThing.ltstrings.show_more || LibraryThing.ltstrings.show_all || 'show all';
		var rd_hide = rd.data('less') || LibraryThing.ltstrings.show_less || 'show less';
		var rd_id = rd.attr('id');
		var omit_parens =  !!(parseIntLT(rd.data('omit_parens')));
		var rd_link_type = rd.data('');
		var _link_markup = '<a class="rolldown_a" href="#" onclick="return lt.rolldown(\''+rd_id+'\');">';

		if (!omit_parens) {
			_link_markup += '(';
		}
		_link_markup += '<span data-rolldown="'+rd_id+'" class="rolldown_a_show">'+rd_show+'</span><span data-rolldown="'+rd_id+'" class="rolldown_a_hide">'+rd_hide+'</span>';
		if (!omit_parens) {
			_link_markup += ')';
		}
		_link_markup += '</a>';

		var rd_link = $J(_link_markup);
		var parent = rd.parent();
		var parent_displaytype = parent.css('display');
		if ((parent_displaytype == 'grid') || (parent_displaytype == 'flex')) {
			var rd_link_container = $J('<div class="rd_link_container"></div>');
			rd_link_container.append(rd_link);
			parent.append(rd_link_container);
		}
		else {
			rd.after(rd_link);
		}

	});
	mmgroupend();
};

lt.rolldown = function(id) {
	var rolldown = $J('#'+id);
	var parent = rolldown.parent();
	var link_container; // used only in grid/flex contexts.
	if (parent.is('.rd_link_container')) {
		link_container = parent;
		parent = parent.parent();
	}
	var parent_displaytype = parent.css('display');
	if ((parent_displaytype == 'grid') || (parent_displaytype == 'flex')) {
		// if it is display grid or flex, then we'll need to do more magic here.
		// Otherwise we'll just be in a single child of a grid/flex containing box incorrectly.
		// We'll need to move/copy the items into the grid layout?
		var rolldown_a = parent.find('.rd_link_container');
		rolldown_a.detach(); // TODO: reattach this at the end.
		//rolldown_a.remove(); // TODO: use the detach/reattach that is commented out instead of this.
		// But it will mean that we need a new function for removing all the newly moved items.
		rolldown.children().each(function() {
			var child = $J(this).detach();
			parent.append(child.addClass('rolldown_inserted'));
		});
		//parent.append(rolldown_a);
		//toggleItemsWithSelector('.rolldown_a_show[data-rolldown="' + id + '"]');
		//toggleItemsWithSelector('.rolldown_a_hide[data-rolldown="' + id + '"]');
	}
	else {
		toggleItemsWithSelector_slide('#' + id);
		toggleItemsWithSelector('.rolldown_a_show[data-rolldown="' + id + '"]');
		toggleItemsWithSelector('.rolldown_a_hide[data-rolldown="' + id + '"]');
	}
	return false;
};

lt.turndown_toggle = function(elt, ev) {
	var el = lt.elf(elt);
	var altKey = false;
	if (typeof ev !== 'undefined') {
		altKey = ev.altKey;
		if (altKey) {
			let sectionName = el.attr('data-name');
			if (sectionName.length) {
				let turndown_group_subselect = '';
				let turndown_group = el.attr('data-group');
				if (turndown_group.length) {
					turndown_group_subselect = '[data-group="' + turndown_group + '"]';
				}
				let selector = '.lt_turndown' + turndown_group_subselect + '[data-visible="1"]:not([data-name="' + sectionName + '"])';
				$J(selector).each(function () {
					lt.turndown_toggle(this);
				});
			}
		}
	}
	var caret = el.find('#'+el.data('caret-id'));
	var hidden_id = el.data('hidden-id');
	var hidden_content = $J('#'+hidden_id);
	var current_state = parseIntLT(el.attr('data-visible'));
	var duration = el.data('duration');
	var callback = el.data('callback');
	var anim_complete = el.data('anim-complete');


	if (caret.length) {

		caret.css({
			position: 'relative',
			transition: 'transform '+duration+'ms ease-in-out',
		});
		if (current_state === 1) {
			//caret.css('transform', 'rotate(0deg)');
			el.attr('data-visible', '0');
		}
		else {
			//caret.css('transform', 'rotate(90deg)');
			el.attr('data-visible', '1');
		}
	}





	if (hidden_content.length) {
		if (current_state == '0') {
			el.attr('data-visible', (current_state ? 0 : 1));
		}
		hidden_content.slideToggle(duration).promise().done(function() {
			if (anim_complete) {
				eval(anim_complete);
			}
			if (current_state == '1') {
				el.attr('data-visible', (current_state ? 0 : 1));
			}
			// If we are in a lightbox then request a resize here.
			if (el.parents('.LT_LIGHTBOX').length) {
				LibraryThing.lightbox.request_resize(1, 250);
			}
			if (callback) {
				eval(callback);
			}
		});
	}
};


jQuery.fn.rotate = function(degrees) {
	$(this).css({'transform' : 'rotate('+ degrees +'deg)'});
	return $(this);
};


lt.promises = $({});

lt.setup_shelves = function() {
	mmgroup('lt.setup_shelves', true);
	/* shelf init */
	var shelves = $J('.LTShelf');

	shelves.each(function() {
		var visible_count = 0;
		mmlog('Shelf init');
		var shelf = $J(this);
		var total = shelf.data('shelf_total');
		var start = shelf.data('shelf_start');
		var _offset = 0;
		shelf.find('.shelf_item').each(function() {
			var item = $J(this);
			var item_offset = item.offset();
			if (item_offset.left < _offset)
			{
				// Once we get an offset.left lower than the last one
				// it means we are on a new, hidden row.
				// We can stop counting. The rest are hidden.
				// Later we'll want to check the num_rows
				// attrib and stop after that many stops
				return false;
			}
			_offset = item_offset.left;
			visible_count++;
		});
		mmlog("LTShelf visible items: "+ visible_count);

		if (visible_count < total)
		{
			shelf.find('.shelf_controls').show();
		}
	});
	mmgroupend();
};

lt.dismiss_sitemessage = function(messageid) {
	if(typeof choice === 'undefined') {
		choice = 1;
	}

	if(typeof reload === 'undefined'){
		reload = true;
	}
	var params = {'value' : messageid, 'type' : 'dismissalert'};
	//var success_noreload = function (j){};
	basic_ajax("/ajax_set_users_boolean.php", params);
	$J('#lt2_sitemessage').slideUp();
};





lt.page_overlay_show = function(onOff) {
	if (typeof lt.page_overlay === 'undefined') {
		return;
	}
	if (onOff) {
		lt.page_overlay.addClass('visible');
	} else {
		lt.page_overlay.removeClass('visible');
	}

};

lt.notification_markRead = function(e, ntuId) {
	e.stopImmediatePropagation();
	var clicked_item = $J(e.target);
	var url = '/action_notifications_toggle_read.php';
	var urlParams = {
		ntu_id: ntuId,
		mark_read: 1
	};
	var callback = function() {
		// update the menu to make sure it's current.
		lt.refresh_message_counts();
	}

	// update line item
	var menu_item = clicked_item.closest('.notification_menu_item');
	menu_item.removeClass('new');
	clicked_item.fadeOut();

	basic_ajax(url+lt.param(urlParams), null, callback);
}

lt.attachMainMenuHandlers = function() {
	//mmlog('Finding menu sources');

	// LT2 top menus
	//var menuSources = $J('[data-lt2menu_source]');
	//mmlog(menuSources);
	if (isLT2() == 2) {
		$J('[data-lt2menu_source]').on('click', function (e) {
			//mmlog('clicked lt2menu source');
			var that = $J(this);
			var menuid = that.data("lt2menu_source");

			// If mobile OR clicking profile menu at any size
			if (window.innerWidth < 768 || (menuid === 'profile')) {
				//mmlog('stopping event propagation!');
				e.stopImmediatePropagation();
				var menu = $J('[data-lt2menu="' + menuid + '"]');
				var showingAtStart = menu.is(':visible');
				//lt.hide_menus(); // this one is a problem. Need to hide them EXCEPT the one you clicked on.
				// If we're in mobile sizes, no menus. Continue.
				//mmlog(window.innerWidth);


				if (!showingAtStart) {
					that.addClass('active');
					var animation_finishFunc = function () {
					};
					var os = that.offset();
					if (!that.hasClass('maintab')) {
						if (that.hasClass('rightmenu')) {
							menu.css({
								right: os.left + that.offsetWidth,
								left: 'unset'
							});
						} else {
							menu.css({
								left: os.left,
								right: 'unset'
							});
						}
					}
					lt.page_overlay_show(1);
					menu.slideToggle(200).addClass('visible', animation_finishFunc());
				}
				return false;
			} else { //( (window.innerWidth >= 800) && (menuid !== 'profile') ) {
				lt.hide_menus(); // this one is a problem. Need to hide them EXCEPT the one you clicked on.
				return true;
			}
		});
	}


	// Sidenav menu
	$J('#mobile_pagemenu').on('click.mobile_pagemenu', function(event) {
		lt.sidenav_menu_show(event, 1);
	});

	// LT1 main menu
	$J('#mobile_topmenu').on('click.mobile_topmenu', function(event) {
		lt.toggleLT1MainMenu(event, 1);
	});

	return false;
};

lt.hide_menus = function(e) {
	//mmlog('lt.hide_menus');

	// Popup menus and LT2 top menus.
	$J('[data-lt2menu_source]').removeClass('active');
	$J('.lt_autocomplete_menu.popup_list').trigger('close_menu');

	// LT2 main menu
	var menu = $J('.lt2_menu');
	if (menu.length) {
		menu.slideUp(100).removeClass('visible');
	}


	lt.toggleLT1MainMenu(e, 0);
	lt.sidenav_menu_show(e, 0);



	/*
	if (lt.page_overlay && (isLT2() == 2)) {
		lt.page_overlay.removeClass('visible');
	}

	 */

	//$J('#ibrarything').removeClass('visible');
	//$J('.ltlogosvg').removeClass('rotated');
};



var lt_page_share = function() {
	var url = '/ajax_page_share_sheet.php';
	var page_url;
	var page_wording;
	var page_title;
	var page_imageurl;

	var og_item = $J('#lt_share_og');
	if (og_item.length) {
		page_url = og_item.attr('data-url');
		page_title = og_item.attr('data-title');
		page_wording = og_item.attr('data-wording');
		page_imageurl = og_item.attr('data-imageurl');
	}
	page_url = page_url || '';
	var params = {
		//title: LibraryThing.ltstrings.share || 'Share',
		ajaxparams: {
			//id: container_id,
			//chart_id: chart_id,
			share_type: 'page',
			page_url: page_url,
			page_title: page_title,
			page_wording : page_wording,
			page_imageurl: page_imageurl
		}/*,
		scriptwhendone: function() {
			// This callback is called from the plotly image create function's promise
			var callback = function(url) {
				var alt = page_title || 'LibraryThing Image Share';
				var downloadAttrSupported = ("download" in document.createElement("a"));
				var container = $J('#sharesheet_img_container');
				var link = $J('<a download="'+alt+'"/>');
				link.attr('href', url);
				var img = $J('<img src="'+page_imageurl+'" class="cover sharesheet_img" />');
				var instruction = $J('#sheet_download_instruction');
				if (downloadAttrSupported) {
					link.append(img);
					container.append(link);
					instruction.html(instruction.attr('data-click'));
				}
				else {
					container.append(img);
					instruction.html(instruction.attr('data-longclick'));
				}

				LibraryThing.lightbox.resize();
			};
			// This builds the plotly flat image file, then sends the base64 image data via a callback
			//nstats_export(chart_id, callback);

			// We'll see if we can get the page image from the OG data in the head.
			var page_image = $J("head meta[property='og:image']");
			if (page_image.length) {
				callback(page_image.attr('content'));
			}
			else {
				$J('#save_image').hide();
			}
		}
		*/
	};
	LibraryThing.lightbox.ajax(url, params);
};


// You can send in a value of -1 to turn off the menu but leave the overlay showing
// This is because of race conditions when switching between two menus.
lt.sidenav_menu_show = function(e, onOff) {
	onOff = (typeof onOff == 'undefined') ? 1 : onOff;
	//mmlog('sidenav_menu_show('+onOff+')');
	if (e !== undefined) {
		e.stopImmediatePropagation();
	}

	var sidebar = $J('.lt_mainsidebar');
	var menu = sidebar;

	if ((menu.data('onoff') == 0) || onOff === 0 || onOff === -1)
	{
		menu.data('onoff', 1);
		sidebar.removeClass('active');
		if (onOff != -1) {
			lt.page_overlay_show(0);
		}
		$J(document).off('keydown.sidenav_menu_show');
	}
	else if (onOff === 1) {
		menu.data('onoff', 0);
		sidebar.addClass('active');
		lt.toggleLT1MainMenu(e, -1);
		lt.page_overlay_show(e, 1);
		$J(document).on('keydown.sidenav_menu_show', function(event) {
			mmlog('keypress'+event.which);
			if (event.which === 27) {
				lt.sidenav_menu_show(event, 0);
			}
		});
	}
};

// You can send in a value of -1 to turn off the menu but leave the overlay showing
// This is because of race conditions when switching between two menus.
lt.toggleLT1MainMenu = function(event, onOff) {
	//mmlog('toggleLT1MainMenu('+onOff+')');
	$J(document).off('keydown.lt1mainmenu');

	if (event) {
		event.stopImmediatePropagation();
	}

	onOff = (typeof onOff == 'undefined') ? 1 : onOff;
	const speed = 100;
	var burger = $J('#mobile_topmenu');
	var menu = $J('#mobile_topmenu_content');

	//mmlog('toggling top menu');
	if ((menu.data('onoff') == 0) || onOff === 0 || onOff === -1) {
		menu.data('onoff', 1);
		if (onOff != -1) {
			lt.page_overlay_show(0);
		}
		menu.removeClass('active');
		burger.removeClass('hovered');
	}
	else if (onOff === 1) {
		menu.data('onoff', 0);
		lt.page_overlay_show(1);
		menu.addClass('active');
		burger.addClass('hovered');
		lt.sidenav_menu_show(event, -1);

		$J(document).on('keydown.lt1mainmenu', function(event) {
			mmlog('keypress'+event.which);
			if (event.which === 27) {
				lt.toggleLT1MainMenu(event, 0);
			}
		});
	}
	//menu.slideToggle();
};

function shareLB( idA )
{
	chromeChoice = 0;
	var url = '/ajax_shareblast.php?idA=' + idA;
	var params = {
		height: 600,
		width: 650,
		modal: true,
		content_class: 'LT_LB_blast'
	};
	LibraryThing.lightbox.iframe(url, params);
}

function shareLB_book( book_id )
{
	chromeChoice = 0
	var url = '/ajax_shareblast.php?share_book_id=' + book_id;
	var params = {
		height: 255,
		width: 650,
		modal: true,
		content_class: 'LT_LB_blast'
	};
	LibraryThing.lightbox.ajax(url, params);
}

lt.desktopversion = function() {
	var vp = $J('meta#viewport');
	var dmode = $J('.lt2_footer .footer_displaymode').attr('data-displaymode');
	var mobile = $J('.lt2_footer #footer_mobile');
	var desktop = $J('.lt2_footer #footer_desktop');
	if (desktop.is(':visible')) {
		// Set to desktop mode.
		setCookie('lt2_desktopmode', 1);
		vp.attr('content', 'width=961, initial-scale=0, maximum-scale=5.0, user-scalable=1');
		desktop.hide();
		mobile.show();
	}
	else {
		// set to normal mode, letting browser handle sizing
		setCookie('lt2_desktopmode', 0);
		vp.attr('content', 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=2.0, user-scalable=no viewport-fit=cover');
		mobile.hide();
		desktop.show();
	}


	return false;
};


/* note the parens following this! It gets run immediately when loaded. */
lt.current_color_mode = 'light';
if (SUPPORT_NIGHTMODE) {
	lt.checkColormode = function () {
		var darkmode_browser = !!(window.matchMedia('(prefers-color-scheme: dark)').matches);
		var darkmode_user = window.localStorage.getItem('color-mode');
		mmlog('darkmode browser: ' + darkmode_browser);
		mmlog('darkmode user: ' + darkmode_user);
		if (darkmode_user == 'dark' || (darkmode_browser && !darkmode_user)) {
			document.documentElement.setAttribute('color-mode', 'dark');
			lt.current_color_mode = 'dark';
		}
	}();
}

// hide/show night mode correctly
$J(function() {
	if (lt.current_color_mode == 'dark') {
		$J('#footer_nightmode_0').hide();
		$J('#footer_nightmode_1').show();
	}
	else {
		$J('#footer_nightmode_1').hide();
		$J('#footer_nightmode_0').show();
	}
});

lt.set_user_color_mode = function(cm) {
	if (cm) {
		document.documentElement.setAttribute('color-mode', cm);
		localStorage.setItem('color-mode', cm);
		lt.current_color_mode = cm;
	}
	if (lt.current_color_mode == 'dark') {
		$J('#footer_nightmode_0').hide();
		$J('#footer_nightmode_1').show();
	}
	else {
		$J('#footer_nightmode_1').hide();
		$J('#footer_nightmode_0').show();
	}
};
lt.nightmode_toggle = function() {
	lt.set_user_color_mode((lt.current_color_mode == 'light') ? 'dark' : 'light');
	return false;
};

lt.stylepick_switch = function() {
	//$J('#stylepicker_switch')
	var picker = $J('#lt2_stylepicker');
	if (picker.is(':hidden')) {
		picker.css('display', 'flex').toggle().fadeIn();
	}
	else {
		picker.fadeOut();
	}
};

lt.stylepick = function(style) {
	if (style !== undefined) {
		var b = $J('html');
		var available_styles = b.data('available_styles');
		b.removeClass(available_styles).addClass(style);
		$J('.stylepicker_item').removeClass('selected');
		$J('.stylepicker_item[data-style='+style+']').addClass('selected');
		setCookie('lt2_style', style);
	}
};
/*
lt.attachMainTabHovers = function() {
	//lt.lt2_subnav_hovercaret = $J('#lt2_subnav_hovercaret');
	$J('[data-lt2menu_source]').hover( lt.hover_maintab_in, lt.hover_maintab_out );
};
 */
/*
lt.hide_subnavs = function() {
	$J('[data-lt2subnavgroup]').hide();
};

lt.hover_maintab_in = function() {
	var that = $J(this);
	lt.hide_subnavs();
	if (that.hasClass('selected')) {
		return;
	}
	that.addClass('hover');
	var source_name = that.data('lt2subnavsource');
	mmlog('hovering over main tab: '+source_name);
	var subnavgroup = $J('[data-lt2menu="'+source_name+'"]');


	subnavgroup.css('display', 'flex');
};
lt.hover_maintab_out = function () {
	//mmlog('lt.hover_out');
	mmlog('leaving main tab');
	var that = $J(this);

	var source_name = that.data('lt2subnavsource');
	mmlog('leaving main tab: '+source_name);
	var subnavgroup = $J('[data-lt2subnavgroup="'+source_name+'"]');

	lt.maintabout_timeout = setTimeout( function () {
		// figure out which subnav we need to watch based on maintab's data attrib
		mmlog(subnavgroup);

		// This is a horrible jquery bug hack: should just be _hover(':hover') here
		if ( subnavgroup.parent().find('[data-lt2subnavgroup="'+source_name+'"]:hover').length ) {
			// we are hovering over the popup. attach a mouse out to it.
			subnavgroup.off('mouseleave').mouseleave(function(e) {
				e.stopPropagation();
				that.removeClass('hover');
				subnavgroup.hide();
			});
		}
		else {
			// If you're not hovering over the popup, hide it immediately
			//LibraryThingConnector.log('hover outside of popup');
			that.removeClass('hover');
			subnavgroup.off('mouseleave');
			subnavgroup.hide();

		}
		clearTimeout(maintabout_timeout);
	}, 25);
};
*/



/*
lt.attachMainMenuHandlers_old = function() {
	mmlog('Finding menu sources');
	//var menuSources = $J('[data-lt2menu_source]');
	//mmlog(menuSources);

	$J('[data-lt2menu_source]').on('click', function(e) {
		mmlog('clicked lt2menu source');
		var that = $J(this);
		var menuid = that.data("lt2menu_source");

		if ( (window.innerWidth >= 768) && (menuid !== 'profile') ) {
			lt.hide_menus(); // this one is a problem. Need to hide them EXCEPT the one you clicked on.
			return false;
		}
		if ( window.innerWidth < 768 || (menuid === 'profile')) {
			mmlog('stopping event propagation!');
			e.stopImmediatePropagation();
			var menu = $J('[data-lt2menu="' + menuid + '"]');
			var showingAtStart = menu.is(':visible');
			lt.hide_menus(); // this one is a problem. Need to hide them EXCEPT the one you clicked on.

			// If we're in mobile sizes, no menus. Continue.
			mmlog(window.innerWidth);


			if (!showingAtStart) {
				that.addClass('active');
				var animation_finishFunc = function () {
					//nothing yet.
				};
				var os = that.offset();
				if (that.hasClass('rightmenu')) {
					menu.css({
						right: os.left + that.offsetWidth,
						left: 'unset'
					});
				} else {
					menu.css({
						left: os.left,
						right: 'unset'
					});
				}
				lt.page_overlay_show(1);
				menu.slideToggle(200).addClass('visible', animation_finishFunc());
			}
		}
	});
};
*/


/*
lt.handle_menuselect = function(e, target) {
	e.stopImmediatePropagation();
	mmlog('lt.handle_menuselect');

	var that = $J(target);
	if (that.hasClass('selected')) { return false; }
	lt.hide_submenus();
	that.addClass('selected');
	var subid = that.data('show');
	var submenu = $J('#'+subid);
	submenu.slideToggle({
		duration:200,
		start: function () {
			$J(this).css({
				display: "flex"
			})
		}
	});
	return false;
};
lt.hide_submenus = function() {
	var menu = $J('.menu_subsection');
	menu.slideUp(100);
	$J('.menu_item.selected').removeClass('selected');

};
 */

lt.collection_menu = false;
(function($){
	$.fn.lt_collection_menu = function() {
		const fadeDuration = 300;
		return this.each( function() {
			var obj = $J(this);
			if (obj.attr('lt_inited')) { return; }
			obj.attr('lt_inited', 1);

			/*
			var main_button = obj.children('button[role=menu]').first();
			obj._h = main_button.outerHeight();
			obj._w = main_button.outerWidth();
			*/

			/*
			var relative_root = $J('#lt2_content_interior');
			var menuPos = obj.position();
			relative_root.css({
				top: menuPos.top,
				left: menuPos.left
			});
			 */

			obj.lt_collection_menu_default_handler = function(val, usernum, clickedItem) {
				var jitem = $J(clickedItem);
			};
			var click_func = obj.data('handler');

			obj.click(function(ev) {
				// If it is disabled, bail out.
				var _child_button = obj.find('button').first();
				if (_child_button.hasClass('disabled')) {
					return;
				}

				ev.stopImmediatePropagation();
				// Close this menu if you click on the menu again, then bail out.
				if (obj.attr('data-active')) {
					obj.triggerHandler('close_menu');
					return false;
				}

				// Close ALL (other) menus when you click on one.
				$J('.lt_collection_menu').trigger('close_menu');


				if (obj.attr('data-active')) {
					obj.triggerHandler('close_menu');
				}
				else if (obj.length) {
					obj.triggerHandler('open_menu', {event: ev});
				}

				$J(document).keyup(function(event) { //keypress event, fadeout on 'escape'
					if(event.keyCode == 27) {
						obj.triggerHandler('close_menu');
					}
				});


				/*
				obj.find('.popup_list').hover(function(){ },
					function(){
						$J(this).fadeOut(400);
					});

				 */
			});


			obj.find('.popup_list li').click(function(ev) { //onclick event, change field value with selected 'list' item and fadeout 'list'
				mmlog('.popup_list li -> click handler');
				ev.stopImmediatePropagation();
				var clickedItem = $J(this);
				var val = clickedItem.attr('data-value');
				var usernum = obj.attr('data-usernum');
				if (typeof window[click_func] == 'function') {
					window[click_func](val, usernum, clickedItem); // call the function defined by the data-handler set on the parent popup div
				}
				else {
					if (typeof obj[click_func] == 'function') {
						obj[click_func](val, usernum, clickedItem);
						mmlog('using built-in popup menu handler function to handle individual element onclick or hrefs');
					}
					else {
						mmlog('No function matching: "' + click_func + '()" available for popup menu handler', 'error');
					}
				}
				obj.find('.popup_list').fadeOut(fadeDuration);
			});


			obj.on('open_menu', function() {
				obj.attr('data-active', true);
				/*
				var relative_root = $J('#lt2_content_interior');
				var menuPos = obj.position();
				relative_root.css({
					top: menuPos.top,
					left: menuPos.left
				});

				 */
				var _popup_menu = obj.find('.popup_list, .popup_view');
				var objchildren = obj.children();
				var main_button = obj.children('button[role="menu"]').first();
				obj._h = main_button.outerHeight();
				obj._w = main_button.outerWidth();

				/* have to display the menu to get its natural size first, to see if we need to change the scroll */
				/* this is because of a bug in safari. See below. */
				_popup_menu.css({'left': '-4000px', 'max-height': 'unset', 'overflow-y':'hidden', 'max-width':'unset'}).show();
				var _raw_height = _popup_menu.height();
				var _raw_width = _popup_menu.width();
				_popup_menu.hide();

				obj._pos = main_button.offset();
				//obj._abs = AbsoluteCoordinates(main_button);
				var _winscroll = $J(window).scrollTop();
				obj._buttonoffset = obj._pos.top - _winscroll;

				mmlog('Button offset: '+obj._pos.top);
				//mmlog('Button abs: '+obj._abs.top);
				mmlog('Window scrollTop: '+ _winscroll);
				mmlog('Offset - scroll: ' + (obj._pos.top - _winscroll));

				var win = $J(window);
				var winheight = window.innerHeight; // $J(window).height(); // jquery buggy
				var winwidth = $J(window).width();
				obj._menu_top = (obj._h + 2);
				obj._maxh = Math.round(winheight - (obj._buttonoffset + obj._menu_top) - 20);
				obj._scrolly = (_raw_height > obj._maxh ) ? 'scroll' : 'auto'; /* have to do this because of safari bug with overflow-y: auto not really working */

				var menu_left = 0;
				var maxwidth = '100vw';

				// Check to see if it's going to run off the right side of the screen.
				// if so, let's align it with the right side of the element or screen.
				if (winwidth < (obj._pos.left + _raw_width)) {
					mmlog('_raw_width: '+_raw_width);
					mmlog('button_width: ' + obj.width());
					menu_left = Math.round(Math.abs(obj.width() - _raw_width));
					maxwidth = 'calc(100vw + '+menu_left+'px)';
					menu_left = menu_left * -1;
				}

				_popup_menu.css({'left':menu_left, 'top': obj._menu_top, 'max-width': maxwidth,'min-width': obj._w, 'max-height': obj._maxh, 'overflow-y': obj._scrolly}).fadeIn(fadeDuration);
				//obj.find('.popup_list').fadeIn(fadeDuration);
				//obj.find('.popup_view').fadeIn(fadeDuration);
			});

			obj.on('close_menu', function() {
				obj.removeAttr('data-active');
				obj.find('.popup_list').hide();
				obj.find('.popup_view').hide();
			});
		});
	};
})($J);


lt.helpdrawer = false;
lt.helpdrawercontent = false;
lt.helpdrawer_close = function() {
	var hd = lt.helpdrawer ? lt.helpdrawer : $J('#helpdrawer');
	hd.removeClass('active');

	var helpbutton = $J('#mainhelpbutton');
	helpbutton.removeClass('selected');
};
lt.toggleHelpDrawer = function(topic) {
	var hd = lt.helpdrawer ? lt.helpdrawer : $J('#helpdrawer');
	var hdc = lt.helpdrawercontent ? lt.helpdrawercontent : hd.find('#hdc');
	var helpbutton = $J('#mainhelpbutton');
	if (hd.hasClass('active')) {
		hd.removeClass('active');

		// button
		helpbutton.removeClass('selected');
		//hdc.html('');
	}
	else {
		var helpstring = LibraryThing.ltstrings.getting_help || 'Getting some help...';
		helpstring = '<i class="fas fa-circle-notch fa-spin lt2_loading_spinner"></i> ' + helpstring;
		hdc.html(helpstring);
		hd.addClass('active');
		helpbutton.addClass('selected');

		var helpurl = hd.data('helpurl');
		var url = false;

		try {
			if (topic) {
				var baseurl = hd.data('baseurl');
				url = '/lt2_wiki_proxy.php?u='+baseurl+topic;
			}
			else if (helpurl) {
				url = '/lt2_wiki_proxy.php?u='+helpurl;
			}

			if (url) {
				var basic_callback = function(r) {
					//mmlog(r);
					hdc.html(r);
				};
				var json_callback = function (r) {
					//mmlog('help callback');
					//mmlog(r);
					const ro = r.responseJSON.parse;
					if (ro != undefined) {
						//mmlog(ro);
						var _t = ro.text['*'];
						//mmlog(_t);
						_t.replace('href="/', 'href="https://i77696b69o6c6962726172797468696e67o636f6dz.oszar.com/');
						_t.replace('src="/', 'src="https://i77696b69o6c6962726172797468696e67o636f6dz.oszar.com/');
						//mmlog(_t);
						hdc.html(_t);
					}
				};
				var params = {
					//u: url
					//crossDomain: true
					dataType: 'json'
				};
				//mmlog(url);
				// Could do basic_ajax_updater here instead
				var req = $J.ajax({
					url: url,
					params: params,
					async: true
				});
				req.done(function(r) {
					//mmlog(r);
					hdc.hide();
					hdc.html(r);
					hdc.fadeIn();
				});
			}
		} catch(err) {
			mmlog(err, 'error');
		}

	}
};

lt.reload = function(bypass_cache) {
	bypass_cache = bypass_cache || true;
	window.location.reload(bypass_cache);
}

lt.reload_while_showing_loader = function() {
	fancy_ajax_updater_fake('',{},'lt2_content_interior');
	lt.reload();
}

lt.scroll_handle_mini_lede = function() {
	//mmlog('lt.scroll_handle_mini_lede');
	var leftnav = $J('.lt_mainsidebar');
	var divs = $J('.work_mini_lede, .work_mini_lede_wide').first(),
		leftnav = $J('.lt_mainsidebar'),
		opac = 0,
		threshold = 100,
		limit = 300;  /* scrolltop value when opacity should be 0 */
	if (divs.length == 0) { return; }
	if (leftnav.hasClass('hover')) { return; }
	var st = $J('body').scrollTop() || window.scrollY || 0;

	/*
	var w = window.innerWidth;
	if (w <= 960) {
		if (leftnav.css('top') == 80) {
			leftnav.css('top', st + 170);
		}
	}
	else {
		leftnav.css('top', 80);
	}
	 */
	/* avoid unnecessary call to jQuery function */
	if (st > threshold && st <= limit) {
		opac = (st-threshold)/(limit-threshold);
	}
	else if (st <= threshold) {
		opac = 0;
	}
	else if (st > limit) {
		opac = 1;
	}
	dsp = 'none';
	if (opac > 0) {
		dsp = 'grid';
	}
	divs.css({
		'opacity' : opac,
		'display' : dsp
	});
};

/* Toggles the display on/off of an item */
function toggleItemsWithSelector(selector, parent) {
	parent = parent || 0;
	var _top;
	if (parent) {
		_top = $J(parent).find(selector);
	}
	else {
		_top = $J(selector);
	}
	_top.toggle();
}
/* Toggles the display on/off of an item using a slide animation if possible */
function toggleItemsWithSelector_slide(selector, parent) {
	parent = parent || 0;
	var _top;
	if (parent) {
		_top = $J(parent).find(selector);
	}
	else {
		_top = $J(selector);
	}
	_top.slideToggle();
}

/* Random things brought over from basics1 */
function confirmAction(action)
{
	return confirm(action+"?");
}

/* stuff that will get moved to work.js */
lt.work = lt.work || {};


lt.work.toggle_worksection = function(el) {
	mmlog(el);
	el = $J(el);
	var scaleY = '1';
	var opacity = 1.0;
	var section = el.closest('section').first();
	mmlog(section);
	var section_content = section.find('.section_content');
	mmlog(section_content);
	if (section_content.is(':visible')) {
		section_content.slideUp(200, 'easeOutQuint');
		el.addClass('flipped');
	}
	else {
		section_content.slideDown(200, 'easeOutQuint');
		el.removeClass('flipped');
		scaleY = '-1';
		opacity = 0.6;
	}

	/*
	el.css({
		'transform': 'scaleY(' + scaleY + ')',
		'opacity': opacity
	});

	 */

};

/*
var lt_actionarea;
var lt_actionarea_alt;
lt.action_area_move = function() {
	mmgroup('lt.action_area_move');
	let b = lt.body || $J('body');

	lt_actionarea = lt_actionarea || $J('#lt2_action_area');
	lt_actionarea_alt = lt_actionarea_alt || $J('#lt2_action_area_alt');

	if (b.hasClass('lt1060')) {
		mmlog('less than 1060. Put actions in the middle.');

		if (lt_actionarea.parent().hasClass('dyn_nav_menu')) {
			// Only do it if it's in the sidebar now.
			lt_actionarea.detach();
			lt_actionarea.appendTo(lt_actionarea_alt);
		}
	}
	else {
		var sidebar_dyn = $J('#lt_altsidebar > .dyn_nav_menu').first();
		mmlog('more than 1060. Put actions in sidebar.');

		if (lt_actionarea.parent().hasClass('dyn_nav_menu')) {
			// do nothing...it's already there.
		}
		else {
			lt_actionarea.detach();
			lt_actionarea.prependTo(sidebar_dyn);
		}


	}
	mmgroupend();
};

lt.copy_action_area = function() {
	var aa = $J('#lt_altsidebar .action_area .sidebar_items');
	var content_alt_action_area = $J('#content_alt_action_area');
	if (content_alt_action_area.length && aa.length)
	{
		content_alt_action_area.html(aa.html());
		setTimeout(function() { content_alt_action_area.find('.btn-block').removeClass('btn-block').removeClass('btn-block').removeClass('btn-block'); }, 100);
	}
};
lt.prettify_action_area = function() {
	var aa = lt.copy_action_area();
};
 */

lt.form_select_all = function(elid) {
	mmlog(elid);
	var el = $J('#'+elid);
	mmlog(el);
	el.find(':checkbox').attr('checked',true);
	// This is terrible. This is used on the settings/genres page
	// but it is no harm doing it any time this is called. For now.
	$J('#is_default').val(0);
}
lt.form_select_none = function(elid) {
	mmlog(elid);
	var el = $J('#'+elid);
	mmlog(el);
	el.find(':checkbox').attr('checked',false);
	// This is terrible. This is used on the settings/genres page
	// but it is no harm doing it any time this is called. For now.
	$J('#is_default').val(0);
}

lt.sitesearch_submit = function(input_id) {
	mmlog('sitesearch_submit');
	var term = $J('#'+input_id).val();
	// I want to do a dropdown quick search results with an option to "See all results" in it.
	// But for now, let's do the standard search.

	goToURL('/search.php?term='+term);
	return false;
};


lt.resize_charts = function() {
	mmgroup('lt.resize_charts');
	mmlog(LibraryThing.pageCharts);
	if (typeof LibraryThing.pageCharts != 'undefined' && LibraryThing.pageCharts.length) {
		LibraryThing.pageCharts.forEach(function (item, index) {
			//mmlog('barrrrrrrrr');
			lt_chart_update(item);
			//Plotly.relayout(item, {});
			mmlog(item);
		});
	}
	mmgroupend();
};

lt.debounce_old = function(ms, fn, immediate) {
  var timer;
  return function() {
  	var that = this;
    clearTimeout(timer);
    var args = Array.prototype.slice.call(arguments);
    args.unshift(this);
    timer = setTimeout(fn.bind.apply(fn, args), ms);
  };
};

/* mostly from underscore.js */
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.

// IMPORTANT: this returns a function, useful for feeding into event handlers.
// But if you want to call it directly you'll need to invoke the function with a trailing ();
// ie: lt.debounce(100, function() {}, true)();
lt.debounce = function(wait, func, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) func.apply(context, args);
		};
		var callNow = immediate && !timeout;
		clearTimeout(timeout);
		if (callNow) {
			func.apply(context, args);
		}
		else {
			timeout = setTimeout(later, wait);
		}
	};
};

// Returns a function, that, as long as it continues to be invoked, will only
// trigger every N milliseconds. If immediate is passed, trigger the
// function on the leading edge, instead of the trailing.
lt.throttle = function(wait, func, immediate) {
	var timeout;
	return function() {
		var context = this, args = arguments;
		var later = function() {
			timeout = null;
			if (!immediate) {
				func.apply(context, args);
			}
		};
		var callNow = immediate && !timeout;
		if ( !timeout ) timeout = setTimeout( later, wait );
		if (callNow) func.apply(context, args);
	};
};

(function ( $J ) {
	$J.fn.intAttr = function(a) {
		return parseInt('0'+ this.attr( a ));
	};
}( $J ));


// Resize handlers.

lt.resize_handlers = [];
lt.fire_resize_handlers = function() {
	if (lt.resize_handlers.length) {
		lt.resize_handlers.forEach((item) => {
			//item();
			eval(item);
		});
	}
};

// These functions get called whenever there is a resize of the window
lt.resize_handlers.push('lt.fix_sidenavs_if_possible()');
lt.resize_handlers.push('lt.check_cardlists()');



/* Container Queries
	Original code from https://i7068696c697077616c746f6eo636f6dz.oszar.com/articles/responsive-components-a-solution-to-the-container-queries-problem/
	But modified by Christopher Holland to handle non-observer code when needed.
	Also modified to use the lt/mt format for the breakpoint classes (less than/more than)
	and to use a space-separated set of breakpoints on the element instead of named breakpoints.
	The new non-observer code does not (yet) have an equivalent mutation observer aspect to it.

	To use: add 'data-observe-resizes' as an attribute of the element.
	then you can add optional breakpoints for that element by adding something like 'data-breakpoints="500 1000"'

 */
lt.window = $J(window);
lt.resizeObserverInited = 0;
lt.resizeObserver = {
	defaultBreakpoints: [376, 394, 415, 450, 500, 576, 600, 650, 700, 730, 767, 769, 800, 900, 960, 1060],
	resize_debounce_time: 400,
	prev_width: 0,
	prev_height: 0,

	init: function() {
		var that = this;
		this.prev_width  = lt.window.width();
		this.prev_height = window.innerHeight;//lt.window.height();

		// handle vertical resizing because the resizeObserver doesn't seem to catch it after page loads for ajax content?!
		lt.window.off('vresize.ltresizeobserver').on('vresize.ltresizeobserver', function () {
			mmlog('vertical resize');
			lt.fix_sidenavs_if_possible();
		});

		lt.window.off('hresize.ltresizeobserver').on('hresize.ltresizeobserver', function () {
			mmlog('horizontal resize');
			if (!window.ResizeObserver) {
				//lt.action_area_move();
				//lt.resize_charts();
			}
			lt.resize_charts();
		});

		lt.resizeObserverInited = 1;
		// This isn't, technically, the domain of a resize observer, this is a different beast
		// But it catches vertical resizing of content based on content injection that the resizeobserver doesn't.
		lt.window.off('resize.ltresizeobserver').on('resize.ltresizeobserver', lt.debounce(this.resize_debounce_time, function () {
			mmgroup('window.on(\'resize\') - CONTENT RESIZED', 1);
			iOSSafari_100vh_pollyfill_setVh();

			var width = lt.window.width(),
				height = window.innerHeight; //lt.window.height();

			var resize_debug = {
				'height' : that.prev_height+' --> '+height,
				'width'	: that.prev_width+' --> '+width,
			};
			mmlog({resize_debug});

			if (width !== that.prev_width) {
				lt.window.trigger('hresize.ltresizeobserver');
			}
			if (height !== that.prev_height) {
				lt.window.trigger('vresize.ltresizeobserver');
			}
			that.prev_width = width;
			that.prev_height = height;
			mmgroupend();
		}));


		// Start observing.
		if (window.ResizeObserver) {
			// Browsers that support ResizeObserver run `observeResizes()` immediately.
			var _lt_resizeObserver = this;
			$J(function() {
				_lt_resizeObserver.observeResizes();
			});
		}
		else {
			// If we don't have a resizeobserver then run this once when the page loads.
			//lt.action_area_move();

			// Browsers that DO NOT support the ResizeObserver use this function.
			var windowResizeHandler = lt.debounce(this.resize_debounce_time, function() {
				mmlog('hey, this is the deprecated resize handler firing.');
				//lt.hide_menus();

				$J('[data-observe-resizes]').each(function(){
					// If breakpoints are defined on the observed element,
					// use them. Otherwise use the defaults.
					var that = $J(this);
					var breakpoints = that.data('breakpoints') ?
						that.data('breakpoints').split(" ") :
						this.defaultBreakpoints;
					var rect = that[0].getBoundingClientRect();
					if (rect.width === 0) {
						that.attr('data-observing',false);
					} else {
						that.attr('data-observing',true);
						that.attr('data-observing-width', rect.width);
					}

					// Update the matching breakpoints on the target element.
					if (typeof Object.values !== typeof undefined) {
						Object.values(breakpoints).forEach(function (breakpoint) {
							var minWidth = breakpoint;
							if (rect.width >= minWidth) {
								that.addClass('mt' + breakpoint);
								that.removeClass('lt' + breakpoint);
							} else {
								that.addClass('lt' + breakpoint);
								that.removeClass('mt' + breakpoint);
							}
						});
					}
				});

				lt.fire_resize_handlers();
			});

			lt.window.resize(windowResizeHandler);
			windowResizeHandler(); // run it once at page load
		}
	},

	calculate_breakpoints: function(entries, from_ro) {
		from_ro = from_ro || true;

		if (entries) {
			entries.forEach(function (entry) {
				var breakpoints;
				if (!from_ro) {

				}
				// If breakpoints are defined on the observed element,
				// use them. Otherwise use the defaults.
				if (!entry.target) {
					entry.target = entry;
				}

				var entrydata_attribs = entry.target.dataset.breakpoints;
				breakpoints = entry.target.dataset.breakpoints ?
					entry.target.dataset.breakpoints.split(" ") :
					lt.resizeObserver.defaultBreakpoints;

				entry.contentRect = entry.contentRect || {
					height: entry.offsetHeight,
					width: entry.offsetWidth
				}

				if (entry.contentRect.width === 0) {
					entry.target.dataset.observing = false;
				} else {
					entry.target.dataset.observing = true;
					entry.target.dataset.observedwidth = entry.contentRect.width;
				}

				// Update the matching breakpoints on the target element.
				try {
					Object.values(breakpoints).forEach(function (breakpoint) {
						var minWidth = breakpoint;
						//mmlog(entry);
						//mmlog('minwidth: '+breakpoint);

						if (entry.contentRect.width >= minWidth) {
							entry.target.classList.add('mt' + breakpoint);
							entry.target.classList.remove('lt' + breakpoint);
						} else {
							entry.target.classList.add('lt' + breakpoint);
							entry.target.classList.remove('mt' + breakpoint);
						}
					});
				} catch(err) { mmlog('There was an error while setting containerQuery classes', 'error'); }
			});
		}
		else {
			throw('NO ENTRIES IN RESIZE OBSERVER');
		}
	},

	observeResizes: function() {
		// Only run if ResizeObserver is supported.
		if ('ResizeObserver' in self) {
			// Create a single ResizeObserver instance to handle all
			// container elements. The instance is created with a callback,
			// which is invoked as soon as an element is observed as well
			// as any time that element's size changes.
			//var ro = new ResizeObserver(function(entries) {

			if (typeof this.observer !== 'object') {
				this.observer = new ResizeObserver(lt.debounce(this.resize_debounce_time, (entries) => {

					mmgroup('ResizeObserver firing: observeResizes()', 1);
					// UI updating...
					//lt.hide_menus();


					lt.fire_resize_handlers();



					if ($J.fn.dataTable) {
						var _datatables = $J('table.dataTable');
						_datatables.each(function() {
							//mmlog(this);
							var table = $J(this);
							var options = table.init();

							if (table.init().responsive) {
								table.responsive.rebuild();
								table.responsive.recalc();
								table.draw();
							}
							else {
								mmlog('not resizing table')
								// TODO: ch: resize of datatables isn't working!
							}

						})
					}


					// Update container query breakpoints...
					// Default breakpoints that should apply to all observed
					// elements that don't define their own custom breakpoints.
					this.calculate_breakpoints(entries);

					mmgroupend();
				}));
			}

			// Find all elements with the `data-observe-resizes` attribute
			// and start observing them.
			var elements = document.querySelectorAll('[data-observe-resizes]');
			this.calculate_breakpoints(elements);
			for (var element, i = 0; element = elements[i]; i++) {
				//mmlog('observing resizes for...');
				//mmlog(element);
				lt.resizeObserver.observer.observe(element);
				element.dispatchEvent(new Event('resize'));
			}

			/*
			var clamps = document.querySelectorAll('[data-clamp]');
			for (var clamp, i = 0; clamp = clamps[i]; i++) {
				//lt.resizeObserver.observer.observe(clamp);
			}
			 */




			// Iterates through the subtree
			var eachObserveableElement = function(nodes, fn) {
				if (nodes) {
					[].slice.call(nodes).forEach(function(node) {
						if (node.nodeType === 1) {
							var containers = [].slice.call(node.querySelectorAll('[data-observe-resizes]'));
							if (node.hasAttribute('data-observe-resizes')) {
								containers.push(node);
							}
							for (var container, i = 0; container = containers[i]; i++) {
								fn(container);
							}
						}
					});
				}
			};

			// Monitor the DOM for changes
			var mo = new MutationObserver(function(entries) {
				//mmlog('mutation observer firing');
				entries.forEach(function(entry) {
					eachObserveableElement(entry.addedNodes, lt.resizeObserver.observer.observe.bind(lt.resizeObserver.observer));
				});
			});
			mo.observe(document.body, {childList: true, subtree: true});
		}
	},

	// A technique for loading polyfills only when needed. Details here:
	// https://i7068696c697077616c746f6eo636f6dz.oszar.com/articles/loading-polyfills-only-when-needed/

}

$J(function() {
	lt.resizeObserver.init();
});

/* SUPPORT FOR COVER LOADING ANIMATION and other interesting cover stuff */
lt.onload_c = function(c) {
	var cover = $J(c); //$J(this);
	//mmlog('handling cover load');
	//mmlog(cover);
	cover.addClass('lt2_loaded').removeClass('loading');
};
var $C = lt.onload_c;

lt.force_containerQuery_update = function(selector) {
	//mmlog('force_containerQuery_update:'+selector);
	var elements = document.querySelectorAll(selector + '[data-observe-resizes], '+selector + ' [data-observe-resizes]');
	lt.resizeObserver.calculate_breakpoints(elements);
	for (var element, i = 0; element = elements[i]; i++) {
		//mmlog('observing resizes for...');
		//mmlog(element);
		lt.resizeObserver.observer.observe(element);
		element.dispatchEvent(new Event('resize'));
	}
}

lt.scrollHandler = function(event) {
	//mmlog('SCROLL HANDLER');
	lt.update_last_interaction();
	lt.mobileScrollHandler();

	/*
	if (typeof lt.handle_scroll_pagecard === 'function') {
		lt.handle_scroll_pagecard(event);
	}
	 */

	//lt.scroll_handle_mini_lede();
	//lt.ltlazyload();
	lt.hideHoverImmediate();
};

function button_in_process( )
	{
	var linkclicked = event.target; //get the a that was clicked
	//$J(linkclicked).removeClass('btn-default');
	//$J(linkclicked).removeClass('btn-action');
	//$J(linkclicked).removeClass('btn-danger');
	//$J(linkclicked).addClass('btn-success');
	$J(linkclicked).attr("disabled", true);
	return true;
	}


lt.toggle_tag_numbers = function(btn) {
	$J('.tagcloud').toggleClass('showCounts');
	var button = $J(btn);
	button.toggleClass('btn-default ltbtn-selected btn-primary');
};


lt.admin_change_display_language = function() {
	var menu = $J('#lt2_admin_display_language');
	var v = menu.val();
	if (v) {
		if (v === 'eng') {
			setCookie('lt2_admin_display_language', '');
		}
		else {
			setCookie('lt2_admin_display_language', v);
		}
	}
	//location.href = location.href;
	window.location.reload(false);
}



var buildDatePickers = function () {
	mmlog('buildDatePickers no longer supported');
	return;
	mmlog('Initializing date pickers on the page');
	// These are the defaults for datepicker params. Some can be changed via attributes on the attached element.
	var paramsA = {};
	paramsA.altField = "#someid"; // needs to be replaced via the attribute lookups below
	paramsA.altFormat = "yy-mm-dd";
	paramsA.showOn = 'button';
	//paramsA.buttonImage = '/pics/fugue32/calendar.png';
	paramsA.buttonImage = 'https://i696d616765o6c6962726172797468696e67o636f6dz.oszar.com/pics/calendar_brown.png';
	paramsA.buttonImageOnly = true;
	paramsA.buttonText = 'Select date';
	paramsA.changeMonth = true;
	paramsA.changeYear = true;
	paramsA.dateFormat = 'yy-mm-dd';
	var pickerElements = $J(".ltdatepicker_proto:not(.hasDatepicker)");
	pickerElements.each(function () {
		var that = $J(this);
		//mmlog(that);
		var settingsS = that.attr('data-ltdatepicker-settings');
		if (settingsS) {
		var settingsA = JSON.parse(settingsS);
		//mmlog(settingsA);

		//AA 08/20/2015 - ok, this if for this bug https://i777777o6c6962726172797468696e67o636f6dz.oszar.com/topic/185901
		//If you want to use beforeShow in the future, make a global function and pass in its name from the
		//php side so that its parsed along with the other data-ltdatepicker-settings above
		//window was used to avoid using eval())
		if (settingsA.beforeShow) {
		paramsA.beforeShow = window[settingsA.beforeShow];
		delete settingsA.beforeShow;
		}
		$J.each(settingsA, function (key, value) {
			paramsA[key] = value;
		});
		}

		/*
		var altField = that.attr("data-ltdatepicker-altField");
		if (altField) {
			paramsA.altField = '#'+altField;
		}

		var changeMonth = that.attr("data-ltdatepicker-changeMonth");
		if (changeMonth) {
			paramsA.changeMonth = stringToBool(changeMonth);
		}

		var changeYear = that.attr("data-ltdatepicker-changeYear");
		if (changeYear) {
			paramsA.changeYear = stringToBool(changeYear);
		}

		var yearRange = that.attr("data-ltdatepicker-yearRange");
		if (yearRange) {
			paramsA.yearRange = yearRange;
		}

		var numberOfMonths = that.attr("data-ltdatepicker-numberOfMonths");
		paramsA.numberOfMonths = parseInt('0'+numberOfMonths,10) || 1;

		var defaultDate = that.attr("data-ltdatepicker-defaultDate");
		if (defaultDate) {
			paramsA.defaultDate = defaultDate;
		}
		*/

		/*
		paramsA.numberOfMonths = that.attr("data-ltdatepicker-numberOfMonths");
		paramsA.dateFormat = that.attr("data-ltdatepicker-dateFormat");
		paramsA.yearRange = that.attr("data-ltdatepicker-yearRange");
		paramsA.defaultDate = that.attr("data-ltdatepicker-defaultDate");
		*/
		//mmlog(paramsA);
		that.datepicker(paramsA);
	});
}

lt.analytics = {};
lt.analytics.register = function(el, key, event_type) {
	try {
		event_type = event_type || 'click';
		var elt = lt.elf(el);
		const url = '/ajax_lt2_analytics.php';

		elt.on('click.lt_analytics', function () {
			var fd = new FormData();

			fd.append('key', key);
			fd.append('event', event_type);
			fd.append('url', document.URL);

			mmlog('sending analytics beacon');
			navigator.sendBeacon(url, fd);

		});
	} catch(err) {};
}

$J(function() {
	try {
		// Register analytics on top tab clicks (default event_type)
		var tabs = $J('#masthead nav#tabs .sitenav_item');
		tabs.each(function () {
			var that = $J(this);
			var thatid = that.attr('id');
			if (!thatid) {
				thatid = that.attr('href');
			}
			lt.analytics.register(this, 'maintab.' + thatid);
		});

		// Register mobile menu items
		var mobiletabs = $J('#masthead #mobile_topmenu_content .sitenav_item');
		mobiletabs.each(function () {
			var that = $J(this);
			var thatid = that.attr('id');
			if (!thatid) {
				thatid = that.attr('href');
			}
			lt.analytics.register(this, 'mobile_menu.' + thatid);
		});
	} catch(err) {}
});




// developer functions. To be moved to developer.js at some point
// putting them here for ease/speed of development.
/* Some various ajax stuff next */
lt.developer = lt.developer || {};
lt.developer.app_delete = function(app_id) {
	if (confirm('This is permanent! Are you sure you want to delete this app?')) {
		const url = '/developer_app_delete.php';
		var params = {
			app_id: app_id
		};
		$('.applist_item#app_' + app_id).fadeOut();

		basic_ajax(url, params, function (response) {
			if (typeof response !== typeof undefined) {
				if (typeof response.responseText !== typeof undefined) {
					var j = JSON.parse(response.responseText);
					if (parseInt(j.result) !== 1) {
						$('.applist_item#app_' + app_id).show();
						lt.toast('There was an error while deleting the app.', 'error');
					}
				}
			}
		});
	}
};

lt.developer.playground_handle_method_menu = function(menu_el) {
	var form = lt.elf(menu_el);


};

lt.developer.playground_request = function() {
	var form = lt.elf('playground_form');
	var input = lt.elf('searchline');
	var url = '/ajax_api_playground_response.php';
	var params = form.serializeObject();

	if (params.url) {
		url = params.url;
	}

	// Need to change the name of the query param
	if (params.method === 'librarything.local.searchvenues') {
		params.q = params.id;
	}
	/*
	if (params.type === 'talpa') {
		url = '/api_talpa.php';
		params.search = params.query;
	}


	if (params.type === 'webservices') {
		url = '/services/rest/1.1/index.php';
	}
	 */

	fancy_ajax_updater(url+lt.param(params), null, 'playground_response', function() {
		var target = lt.elf('playground_response');
		Prism.highlightElement(target[0]);
	}, 'playground');

}

lt.developer.playground_handle_venues_near_menu = function(menu_el) {
	var menu = lt.elf(menu_el);
	var mval = menu.val();
	//lt.toast(mval);

	// Get hidden translation text divs and values
	var transtext = {};
	$J('[transid]').each(function() {
		var that = $J(this);
		transtext[that.attr('transid')] = that.html();
	});

	var mval_stripped = mval.replaceAll('.','_');
	$J('[showhide_based_on_classes]').hide();
	$J('.'+mval_stripped).find('input').attr('disabled', 1);

	//$J('#centeronq').hide();
	if (mval === 'librarything.local.searchvenues' ||
		mval === 'librarything.local.getvenuesnear') {
		var q_label = transtext['location'] || 'Location';
		$J('.'+mval_stripped).show();
		$J('.'+mval_stripped).find('input').removeAttr('disabled');
		$J('input#id').val('Boston');
		$J('label[for="id"]').html(q_label);
		if (mval === 'librarything.local.searchvenues') {
			$J('#centeronq').show();
		}
		//lt.toast(mval_stripped);
	}
	else
	{
		var q_label = transtext['id'] || 'ID';
		$J('input#id').val('1060');
		$J('label[for="id"]').html(q_label);

	}
}