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
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 DOMText 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); } }