1(function(){ // begin ns 2const NEW_ELEM_NS_VAR_NAME = null; 3const NEW_ELEM_NS_NAME = '_S_T'; 4if (!self[NEW_ELEM_NS_NAME]) self[NEW_ELEM_NS_NAME] = {}; 5const NS = self[NEW_ELEM_NS_NAME]; 6const NEW_ELEM_NDEBUG = true; 7const NEW_ELEM_OS = 'default'; 8const NEW_ELEM_AGENT = 'default'; 9 10const CONF__PARTIAL_BOOTSTRAP_CLASSES = "S-0T S-1T S-2T S-3T S-4T"; 11const CONF__PARTIAL_BOOTSTRAP_CLASSES_WEAK = "S-0T S-1T S-2T S-3T"; // remove one class to make selectors weaker 12const CONF__BIG_SCREEN_WIDTH = 600; 13const CONF__COUNTER_WWW_HOSTNAME = "qacounter.personyze.com"; 14const CONF__COUNTER_WWW_HOSTNAME_NO_SERVER_CACHE = "qacounter.personyze.com"; 15const CONF__COUNTER_WWW_HOSTNAME_EXTERNAL_MEDIA = "qapic.personyze.com"; 16const CONF__GUI_HOST = "qa.personyze.com"; 17const CONF__USER_AGENT_HTML_ID_DOCUMENT = "s7i9fh"; 18const CONF__MIN_HTML_ID = -43; 19const CONF__USER_AGENT_CSS_NOT = "*[class^='cke_']"; 20const CONF__LIVE_PROXY_URL = "\/\/qa.personyze.com\/live\/proxy.php"; 21const CONF__CLIENT_OVERRIDES_PARAM_NAME = "_S_T"; 22const CONF__CLIENT_OVERRIDES_PARAM_NAME_2 = "personyze"; 23const CONF__SITE_DB_ITEM_STATUSES = ["viewed","delivered","extra","favorite","goal"]; 24const CONF__SITE_DB_ITEM_STATUSES_TITLES = {"products":["Shown","Confirmed view","Added to cart","Added to favorites","Purchased"],"articles":["Shown","Confirmed view","Commented","Liked","Reached goal","Delivered"]}; 25const CONF__SOCIAL_FIELDS = ["custom_i_1","custom_i_2","custom_i_3","custom_i_4","custom_f_1","custom_f_2","custom_t_1","custom_t_2","custom_t_3","custom_t_4","custom_t_5","custom_t_6","custom_d_1","custom_d_2","fb_id","first_name","last_name","email","sex","birthday","timezone","current_city","current_state","current_country","hometown_country","website","interests","birth_year_from","birth_year_to","phone","opted_in","conversion_last","conversion_history","last_interest","latitude","longitude","postal_code","industry","account_type","lead_source","first_click_id"]; 26const CONF__PRODUCT_STATS_LAST_GOAL_USER_REQUIRE_SOCIAL_FIELDS = ["first_name"]; 27const CONF__STATE_LIMIT = 64; 28const CONF__SESSION_FINAL_EXPIRE_TIME = 5400; 29const CONF__DOT_IMAGE_SRC = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; 30const CONF__HTML_CUSTOMIZER_OPTIMAL_LIMIT = 5; 31const CONF__HTML_CUSTOMIZER_MAX_SHOW_LIMIT = 5; 32const CONF__HTML_CUSTOMIZER_MAX_LIMIT = 100; 33const CONF__IMPORT_ROWS_FIRST_PHASE_VOLUME = 0.3; 34const CONF__WEBPUSH_PUBLIC_KEY = "BDJAHUDJ\/vrFTXMfGDsoQqtyEfKh9BqWzpS+JDo2lGAeK75y7dYvwirc0fdqGpUrpSPiBv3OwSCnFYfMXLcBIHI="; 35const CONF__EXTERNAL_MEDIA_IMAGE_TYPES = {"png":"PNG (transparent)","jpeg":"JPEG"}; 36const CONF__PRODUCT_STATS_N_LAST_GOAL_USERS = 3;
37const CONF__PERSONYZE_COMMANDS_JS_VAR_NAME = "personyze"; 38const CONF__GUI_TEXT_TRANSLATION_ATTRS = ["title","label","alt","placeholder"]; 39const CONF__STAT_TRACK_LIB_JS = "\/\/qacounter.personyze.com\/stat-track-lib.js"; 40const CONF__INTEREST_TITLE = {"interest":"Interest","category":"Category","tag":"Tag","family":"Family","cross category":"Cross-sell category","upsale category":"Upsell category"}; 41const CONF__INTEREST_TITLES = {"interest":"Interests","category":"Categories","tag":"Tags","family":"Families","cross category":"Cross-sell categories","upsale category":"Upsell categories"}; 42const CONF__INTEREST_LAST_EVENT_NAMES = {"interest":"Interests","category":"Categories","tag":"Tags","family":"Families","cross category":"Cross-sell categories","upsale category":"Upsell categories"}; 43const CONF__SESSION_EXPIRE_TIME = 5400; 44const CONF__EVENT_SEND_MODE = ["page_events","mouse over event","grab url","placeholder (no)","focus","grab meta tag","placeholder","grab html","placeholder (extra)","placeholder (closed)","link_in","text input","text input (blur)","click event","click event page","load event","grab volatile html","grab cookie","placeholder (goal)","custom event","social","log interaction","log interest","fire action","grab ga","grab gtm","grab integration","log item delivery"]; 45const CONF__EVENTS_REMAIN_IN_COOKIE_FOR_N_MSECS = 5000; 46const CONF__IDENTITY_PROVIDERS = {"auth0":{"title":"Auth0","logo":"auth0-logo.svg"},"xecurify":{"title":"","logo":"xecurify-139x37.png"},"onelogin":{"title":"","logo":"onelogin-logo.svg"},"okta":{"title":"","logo":"okta-logo.svg"}}; 47const CONF__GOOGLE_SSO_CLIENT_ID = "768289160396-ts0ee5f34kr5nkcdrj3cbnrl5jfhiq2e.apps.googleusercontent.com"; 48const CONF__MICROSOFT_SSO_CLIENT_ID = "0851e243-6c5e-4fc5-a0c4-2dd6cfd315f4"; 49const CONF__JSAPI_DELAY_TO_ACCUMULATE_QUERIES = 0; 50const CONF__ACCOUNT_FEATURES = {"sdk":"Mobile App Personalization","ab-wizard":"A\/B testing","products":"E-commerce recommendations","articles":"Content recommendations","email":"Email Personalization (send email from Personyze)","external-media":"Email Personalization for third party email templates","webpush":"Push notification","crm":"CRM targeting (the CRM wizard)"}; 51const CONF__DEFAULT_PLACEHOLDER_ORDER = 999; 52 53const CONF__SPINNER = `<!--By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL--><svg width="100" height="100" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#000"><g fill="none" fill-rule="evenodd"><g transform="translate(1 1)" stroke-width="2"><circle stroke-opacity=".5" cx="18" cy="18" r="18"/><path d="M36 18c0-9.94-8.06-18-18-18"><animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/></path></g></g></svg>`; 54const CONF__SPINNER_COLOR_REPLACE_FROM = '#000'; 55const CONF__SPINNER_WIDTH_REPLACE_FROM = ' width="100"'; 56const CONF__SPINNER_HEIGHT_REPLACE_FROM = ' height="100"'; 57const CONF__SPINNER_ADD_PADDING_REPLACE_FROM = 'viewBox="0 0 38 38"'; 58const CONF__SPINNER_ADD_PADDING_REPLACE_TO = 'viewBox="-20 -20 78 78"'; 59 60const CONF__ACTIONS_EXTRA_NOT_CLICK_STR = `1504958690,1622875429,492188746,3568666300,3901546772,2013142013`; 61const CONF__ACTIONS_RECOM_STR = `1504958690,1393331489,2704108379,4240931881,2270359400,3978514375,2528431140,2013142013,4216680521`; 62const CONF__ACTIONS_RECOM_NONFORM_STR = `1393331489,2704108379,4240931881,2270359400,3978514375,2528431140,4216680521`; 63const CONF__ACTIONS_RECOM_FORM_STR = `1504958690,2013142013`; 64const CONF__ACTIONS_OFFLINE_STR = `951527869,886063759,1335662688,3949927194,1273336257,1199766259,3298344050`; 65const CONF__ACTIONS_EXTERNAL_MEDIA_STR = `2533288044,2593751902,1869681987`; 66const CONF__ACTION_TYPES = 67{ "Live": {id: 4080894865}, 68"Popup Trigger Part": {id: 902801468}, 69}; 70const CONF__ACTIONS_WITHOUT_BLANK = 71{ '1504958690': true,'2704108379': true,'2533288044': true,'2593751902': true,'492188746': true,'2672959221': true,'3901546772': true,'2270359400': true,'3978514375': true,'2528431140': true,'2013142013': true,'4216680521': true}; 72const CONF__DEBUGGER_REDIR_URL_Q = "\/debugger\/redir.tsx?"; 73const CONF__COLORS_STOCK = ["#E9586E","#2061B2","#FF9900","#920092","#008CB5","#DD4477","#66AA00","#316395","#b82e2e","#22aa99"]; 74const CONF__GUI_NO_YOUTUBE_GUIDES = false;
75const CONF__HOST_ROLES = ["tracker","archive","slave","master"]; 76const CONF__REFERRER_TYPE_TYPE = ["regular referrer","direct","organic search","commercial search","internal search","commercial","internal","social","general affiliate network","contextual affiliate network","contextual ads","contextual ads search","index","bookmarking","marketing","review","blog","media","government"]; 77const CONF__AB_TEST_USE_GAMMA = false; 78const CONF__GOOGLE_MAPS_APP_ID_FOR_SCREENSHOTS = ""; 79const CONF__CALC_SUMMARY_FOR_N_GOAL_EVENTS = 12; 80const CONF__SITE_DB_INTERACTION_STATS_FOR_PERIOD = ["all","recently","week","4day","2day","1day"]; 81const CONF__PAYMENT_CURRENCY_FORMAT = "* \u20aa"; 82const CONF__PAYMENT_CURRENCY_FORMAT_PREFIX = ""; 83const CONF__PAYMENT_CURRENCY_FORMAT_SUFFIX = " \u20aa"; 84const CONF__ACTION_SCREENSHOT_DIV_WIDTH = 320; 85const CONF__ACTION_SCREENSHOT_DIV_HEIGHT = 240; 86 87const CONF__CAMPAIGNS_MENUS = 88{ all: 89 [ { text: 'Web Browser', 90 icon: 'desktop', 91 options: 92 [ {"value":"actions","text":"Targeting and Personalization","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/actions.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/targeting-and-personalization-2","title":"Tailor banners, popups, and on-site messages based on over 70 visitor traits.","condition_type":"tracker"}, 93{"value":"ab","text":"A\/B Testing","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/ab.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/a-b-testing","title":"Test pages, banners, or promotions and automatically select the winning variation.","condition_type":"tracker"}, 94{"value":"products_recom","text":"Product Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/product-recommendations-wizard","title":"Recommend products tailored to each visitor\u2019s interests, enhanced by aggregate trends and dynamic filters.","condition_type":"tracker"}, 95{"value":"articles_recom","text":"Content Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/articles_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/content-recommendations-wizard","title":"Recommend relevant articles to each visitor based on their interests, popular content, and smart targeting filters.","condition_type":"tracker"} ] 96 }, 97 { text: 'Email', 98 icon: 'envelope', 99 options: 100 [ {"value":"offline_email","text":"Email Personalization","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/personalized-re-targeting-emails\/","title":"Send automated, personalized emails with dynamic content and AI-based recommendations. Broadcast directly or integrate with your third-party email provider.","condition_type":"offline"}, 101{"value":"external_media_products_recom","text":"Add Product Recommendations to 3rd Party Emails","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-to-insert-recommendations-into-third-party-emails","title":"Generate HTML code to embed Personyze recommendations into third-party email templates, such as shipping confirmations or invoices.","condition_type":"offline"}, 102{"value":"external_media_articles_recom","text":"Add Content Recommendations to 3rd Party Emails","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-to-insert-recommendations-into-third-party-emails","title":"Generate HTML code to embed Personyze content recommendations into third-party email templates, such as newsletters or system alerts.","condition_type":"offline"}, 103{"value":"external_media","text":"Add Personalized Banners to 3rd Party Emails","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-to-insert-recommendations-into-third-party-emails","title":"Generate HTML snippets to embed personalized banners directly into your emails.","condition_type":"offline"} ] 104 }, 105 { text: 'Application/API', 106 icon: 'mobile', 107 options: 108 [ {"value":"sdk","text":"Targeting and Personalization for Apps","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/actions_app.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/targeting-and-personalization-2","title":"Deliver personalized banners, popups, and messages within your Android\/iOS app using behavioral data and 70+ visitor attributes.","condition_type":"tracker"}, 109{"value":"products_recom_sdk","text":"Product Recommendations for Apps","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/product-recommendations-wizard","title":"Deliver tailored product recommendations in your Android\/iOS app using a mix of user interests, crowd trends, and custom logic.","condition_type":"tracker"}, 110{"value":"articles_recom_sdk","text":"Content Recommendations for Apps","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/articles_recom.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/content-recommendations-wizard","title":"Deliver personalized article recommendations in your Android\/iOS app by combining user interests, crowd behavior, and smart filtering.","condition_type":"tracker"}, 111{"value":"external_json_products_recom","text":"JSON API Product Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom_json.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-for-third-party-applications","title":"Enable your application to fetch personalized product recommendations from Personyze via JSON API.","condition_type":"offline"}, 112{"value":"external_json_articles_recom","text":"JSON API Content Recommendations","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/products_recom_json.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/get-code-for-third-party-applications","title":"Fetch personalized content recommendations from Personyze using a simple JSON API query.","condition_type":"offline"} ] 113 }, 114 { text: 'Web Push', 115 icon: 'bell-o', 116 options: 117 [ {"value":"webpush_subscribe","text":"Web Push Subscription","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/how-to-send-web-push-notifications-with-personyze\/","title":"Set up a popup or banner prompting visitors to subscribe to your site's push notifications.","condition_type":"tracker"}, 118{"value":"webpush","text":"Send Web Push","icon":"\/\/counter-backend.personyze.com\/images\/campaigns-icons\/broadcast.svg","href":"https:\/\/wiki.personyze.com\/knowledgebase\/how-to-send-web-push-notifications-with-personyze\/","title":"Create and send targeted push notifications to your subscribed visitors.","condition_type":"offline"} ] 119 } 120 ] 121}; 122 123const CONF__CONDBUILDER_MENUS = 124{ tracker: 125 [ { text: 'Visitor context & attributes', 126 icon: 'star', 127 options: 128 [ {"value":"pages","text":"Pages Visited","icon":"files-o","title":"Target visitors who are currently viewing, or have viewed, specific pages during this session.\n\t\t\tExample: Full URL equals \"http:\/\/www.website.com\/\" or URL parameter \"Plan\" equals \"Free Trial\"."}, 129{"value":"countries_cities","text":"Countries\/Cities","icon":"map-marker","title":"Target visitors located in specific countries, regions, or cities."}, 130{"value":"user_profile","text":"User CRM\/ABM\/Profile data","icon":"id-card-o","title":"Target visitors based on CRM or profile data (demographics, company, account type, etc.). Requires configuration in Settings > Visitor Attributes.\n\t\t\t<a href=\"https:\/\/wiki.personyze.com\/knowledgebase\/crm-integration\/\">Learn more<\/a>"}, 131{"value":"user_lists","text":"User Lists (profile data)","icon":"list-alt","title":"Target specific users by uploading a list (Spreadsheet, CSV, RSS, etc.)."}, 132{"value":"system","text":"Device and System","icon":"desktop","title":"Target visitors based on their system specifications (Device, OS, Browser, etc.)."}, 133{"value":"visit_type","text":"Visitor Type","icon":"user","title":"Target specific visitor types, such as new visitors, returning visitors, or those who have visited the site a specific number of times."}, 134{"value":"date_time","text":"Date and Time","icon":"calendar","title":"Trigger the campaign based on specific dates and times. Configure date ranges, days of the week\/month, and select either visitor-local or fixed time zones."}, 135{"value":"container_last_value","text":"Session Attributes","icon":"database","title":"Target visitors based on real-time data extracted from the page, such as cookies, variables, or form inputs (e.g., Cart Value > $100)."} ] 136 }, 137 { text: 'Behavior & engagement', 138 icon: 'star', 139 options: 140 [ {"value":"site_db_products_many","text":"E-Commerce","icon":"shopping-cart","title":"Target based on what products the visitor viewed, added to cart, or bought. You can utilize all product attributes, such as product categories or brands.\n\t\t\tExample: Include visitors who had Apple products worth more than $1000 in their cart in the last 7 days.\n\t\t\t(Requires Personyze product interaction tracking)."}, 141{"value":"site_db_articles_many","text":"Content Interaction","icon":"newspaper-o","title":"Target based on content interactions (views, favorites, subscriptions, etc.). Example: Target visitors who read at least 3 articles by \"John Smith\" in the last 30 days. (Requires Personyze content interaction tracking)."}, 142{"value":"time_total","text":"Time on Site","icon":"clock-o","title":"Target visitors based on the total time spent on the site. Note: Checks occur periodically, so activation may not be precise to the second."}, 143{"value":"landing_pages","text":"Landing Pages","icon":"paper-plane","title":"Target visitors based on their landing page (the first page they visited in the current session). You can include or exclude visitors who arrived via specific URLs, paths, or parameters."}, 144{"value":"referrers","text":"Traffic Sources","icon":"bus","title":"Target visitors arriving from specific referring websites. Note: Referrer data may be unavailable when navigating from HTTPS to HTTP sites."}, 145{"value":"first_source","text":"First Source","icon":"globe","title":"<p><font color=\"brown\">Where the visitor came from the <b>first time they were ever seen<\/b> — not this session.<\/font><\/p><p>\n\t\t\t\t\"Traffic Sources\", \"Keywords\" and \"Ad campaigns\" all describe the visit happening now. This describes the visit that first brought the person to the site, recorded once on their first ever page view and never overwritten afterwards.\n\t\t\t<\/p><p><strong>For Example:<\/strong> a visitor who first arrived from a Google ad, left, and came back a week later by typing your domain reads as \"direct\" to every other rule and as <i>Paid: Google Ads<\/i> to this one. That is the visit the ad platform is billing you for.\n\t\t\t<\/p>"}, 146{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Interests. You need to set up Interests in <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20interests\">recommendation settings<\/a>. To send information about visitor's interests, use containers with \"Store to social table\" set to \"Interest\". Or use client API."}, 147{"value":"actions_executed","text":"Personyze Actions State","icon":"gears","title":"Target based on visitor interactions with campaign content during the current session.\n\t\t\tExample: Exclude visitors who clicked on the \"Campaign 7 banner\"."}, 148{"value":"conditions_matched","text":"Personyze Campaigns Matched","icon":"bullhorn","title":"Target visitors who have matched specific Personyze campaigns during the current session. Example: Include only visitors who matched the \"Signup from PPC\" campaign."}, 149{"value":"network","text":"IP addresses","icon":"hdd-o","title":"Target specific IP addresses or network ranges (CIDR)."}, 150{"value":"page_views","text":"Number of Page Views","icon":"eye","title":"The number of documents viewed in the current session. Each PersonyzeTracker.inst.navigate() call is considered one document view."}, 151{"value":"page_views_count","text":"Views of Specific Pages","icon":"files-o","title":"<p>\n\t\t\t\tTarget visitors who have visited specific pages more (or less) frequently than other pages on the site.\n\t\t\t<\/p>"}, 152{"value":"page_popularity","text":"Most Popular Page","icon":"heart","title":"Target visitors based on their most frequently visited pages across all sessions."}, 153{"value":"keywords_v2","text":"Keywords","icon":"search","title":"<p>Target based on Search Keywords. Note: This primarily applies to commercial (PPC) keywords, as search engines often encrypt organic keyword data.<\/p>"}, 154{"value":"landing_url_params_v2","text":"Ad campaigns (\"utm\")","icon":"bullseye","title":"<p><font color=\"brown\">Matches parameters on the <b>landing page<\/b> (the first page of the session).<\/font><\/p><p>
154\n\t\t\t\tTarget Campaign Data or Keywords found in the URL (e.g., UTM parameters from Google Ads, custom parameters).\n\t\t\t<\/p><p><strong>For Example:<\/strong> A visitor arrived at your homepage from a specific Google Adwords Campaign, is redirected to the most relevant product page\n\t\t\t<\/p>"}, 155{"value":"weather_all","text":"Weather","icon":"sun-o","title":"Target visitors based on the local weather forecast (determined by IP\/Network location or GPS)."}, 156{"value":"rotation","text":"Limit Audience","icon":"pie-chart","title":"Limit the campaign's deployment to a random percentage of visitors, sessions, or page views. This rule is designed to control audience volume (e.g., for gradual rollouts) and is not intended for A\/B testing."} ] 157 }, 158 { text: 'Integrations', 159 icon: 'star', 160 options: 161 [ {"value":"tealium","text":"Tealium Audiences","icon":"bullhorn","title":"<p>\n\t\t\t\tTarget based on Tealium Audiences and Badges (Requires Tealium Integration).\n\t\t\t<\/p>"}, 162{"value":"hubspot","text":"Hubspot integration","icon":"bullhorn","title":"Target based on Hubspot data (Account Type, Funnel Stage, etc.). Requires Hubspot Integration."}, 163{"value":"leadfeeder","text":"Leadfeeder integration","icon":"bullhorn","title":"Target based on Leadfeeder ABM data (Company Name, Industry, etc.) via reverse IP lookup. Requires Leadfeeder Integration."}, 164{"value":"sixsense","text":"6sense integration","icon":"bullhorn","title":"Target based on 6sense ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires 6sense Integration."}, 165{"value":"leadrebel","text":"LeadRebel integration","icon":"bullhorn","title":"Target based on LeadRebel ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires LeadRebel Integration."}, 166{"value":"clearbit","text":"Clearbit integration","icon":"bullhorn","title":"Target based on Clearbit ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires Clearbit Integration."}, 167{"value":"albacross","text":"Albacross integration","icon":"bullhorn","title":"Target based on Albacross ABM data (Company Name, Industry, Account Type, etc.) via reverse IP lookup. Requires Albacross Integration."}, 168{"value":"zoominfo","text":"ZoomInfo integration","icon":"bullhorn","title":"Target based on ZoomInfo B2B data (Company Name, Industry, Revenue, Employee Count, etc.) via reverse IP lookup. Requires ZoomInfo Integration."} ] 169 } 170 ], 171 offline: 172 [ { text: 'Add targeting rules:', 173 icon: 'star', 174 options: 175 [ {"value":"social","text":"User Profile Data","icon":"id-card-o","title":"Include or exclude visitors based on persistent profile attributes (e.g., demographics, account type) synced from CRM or other external sources."}, 176{"value":"date","text":"Date Range (Active From\/To)","icon":"calendar","title":"Specify the date range during which this audience is active."}, 177{"value":"time_since_last_visit","text":"Time Since Last Visit","icon":"calendar","title":"Target users based on the duration elapsed since their last session.\n\t\t\t<p><b> Example: <\/b> Target visitors who haven't visited the website for over a month. <\/p>"}, 178{"value":"session_counter","text":"Total Session Count","icon":"refresh","title":"The total number of sessions tracked for this visitor.\n\t\t\t<p><b> Example: <\/b> Target visitors who have browsed the website more than 5 times. <\/p>"}, 179{"value":"site_db_products_many","text":"Product Interactions (E-Commerce)","icon":"shopping-cart","title":"<p>Target users based on specific product interactions (viewed, added to cart, purchased).<\/p><p>Note: Requires interaction logs sent to Personyze via <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20products\" target=\"_blank\">containers<\/a>.<\/p>"}, 180{"value":"site_db_articles_many","text":"Content Interactions (Articles)","icon":"newspaper-o","title":"<p>Target users based on article viewing history.<\/p><p>Note: Requires interaction logs sent to Personyze via <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20articles\" target=\"_blank\">containers<\/a>.<\/p>"}, 181{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Target users based on interests derived from content consumption or CRM data. Ensure interests are configured in <a href=\"\/panel#cat=Account%20settings%2FRecommendations%2FInterests\">recommendation settings<\/a>."}, 182{"value":"actions_executed","text":"Actions executed","icon":"gears","title":"Target users who have previously been exposed to specific campaigns or actions."} ] 183 } 184 ], 185 page_group: 186 [ {"value":"pages","text":"Page Visits","icon":"files-o","title":"Match users based on specific page visits."}, 187{"value":"device_type","text":"Device Type","icon":"mobile-phone","title":"Target users based on their device type (Desktop, Tablet, or Mobile)."} ], 188 sdk: 189 [ { text: 'Add targeting rules:', 190 icon: 'star', 191 options: 192 [ {"value":"behavior","text":"Behavioral and demographic","icon":"street-view","title":"Target visitors based on their visit frequency, timing, page view count, geographic location, or GPS coordinates."}, 193{"value":"pages_sdk","text":"Documents","icon":"files-o","title":"Target based on documents the user is viewing or has viewed during the session (via PersonyzeTracker)."}, 194{"value":"social","text":"User CRM\/ABM\/Profile data","icon":"id-card-o","title":"Target visitors based on CRM or profile data (e.g., demographics, company, account type). Requires configuration in <a href=\"https:\/\/personyze.com\/panel\/id\/#cat=Account%20settings%2FCRM%20integration%2FSite%20dynamic%20data\/newcondition\">Settings > Visitor Attributes<\/a>. Data can be sourced from CRM integrations, real-time extraction, API calls, or file uploads. <a href=\"https:\/\/wiki.personyze.com\/knowledgebase\/crm-integration\/\">Learn more<\/a>"}, 195{"value":"site_db_products_many","text":"E-Commerce","icon":"shopping-cart","title":"Target based on what products the visitor viewed, added to cart, or bought. You can utilize all product attributes, such as product categories or brands.\n\t\t\tExample: Include visitors who had Apple products worth more than $1000 in their cart in the last 7 days.\n\t\t\t(Requires Personyze product interaction tracking)."}, 196{"value":"site_db_articles_many","text":"Content Interaction","icon":"newspaper-o","title":"Target based on content interactions (views, favorites, subscriptions, etc.). Example: Target visitors who read at least 3 articles by \"John Smith\" in the last 30 days. (Requires Personyze content interaction tracking)."}, 197{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Interests. You need to set up Interests in <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20interests\">recommendation settings<\/a>. To send information about visitor's interests, use containers with \"Store to social table\" set to \"Interest\". Or use client API."}, 198{"value":"actions_executed","text":"Personyze Actions State","icon":"gears","title":"Target based on visitor interactions with campaign content during the current session.\n\t\t\tExample: Exclude visitors who clicked on the \"Campaign 7 banner\"."}, 199{"value":"conditions_matched","text":"Personyze Campaigns Matched","icon":"bullhorn","title":"Target visitors who have matched specific Personyze campaigns during the current session. Example: Include only visitors who matched the \"Signup from PPC\" campaign."}, 200{"value":"system","text":"Device and System","icon":"desktop","title":"Target visitors based on their system specifications (Device, OS, Browser, etc.)."}, 201{"value":"network","text":"IP addresses","icon":"hdd-o","title":"Target specific IP addresses or network ranges (CIDR)."}, 202{"value":"weather_all","text":"Weather","icon":"sun-o","title":"Target visitors based on the local weather forecast (determined by IP\/Network location or GPS)."} ] 203 } 204 ], 205 alert: 206 [ { text: "Add rules", 207 options: 208 [ {"value":"n_executed","text":"Executions","icon":"","title":"Total times the action was executed or the audience matched."}, 209{"value":"bounce_rate","text":"Bounce Rate","icon":"","title":"Average bounce rate over the peri
209od.\n\t\t\t<p><strong> Example: <\/strong> Less than 60% <\/p>"}, 210{"value":"conversion_rate","text":"Conversion Rate","icon":"","title":"Average conversion rate over the period."}, 211{"value":"ctr","text":"Click-Through Rate (CTR)","icon":"","title":"Average CTR over the period."}, 212{"value":"n_sessions","text":"Visits (Sessions)","icon":"","title":"Total visits (sessions) where the audience matched or the action executed."}, 213{"value":"n_sessions_extra","text":"Sessions with Engagement (Clicks\/Actions)","icon":"","title":"Number of sessions involving clicks or form submissions."}, 214{"value":"n_times","text":"Reach (Impressions)","icon":"","title":"Total times the action was shown or the audience matched."}, 215{"value":"n_visits_per_session","text":"Average Page Views per Visit","icon":"","title":"Average number of page views per visit (how many pages a single visitor viewed).\n\t\t\tThis number will be tested for each given time period."}, 216{"value":"time","text":"Date","icon":"","title":"Trigger alert on a specific date."}, 217{"value":"bounce_rate_cmp","text":"Bounce Rate Comparison","icon":"","title":"Compare the bounce rate of two actions or audiences."}, 218{"value":"conversion_rate_cmp","text":"Conversion Rate Comparison","icon":"","title":"Compare the conversion rates of two actions or audiences."}, 219{"value":"ctr_cmp","text":"Click-Through Rate (CTR) Comparison","icon":"","title":"Compare the CTR of two actions or audiences."}, 220{"value":"n_sessions_cmp","text":"Visits (Sessions) Comparison","icon":"","title":"Compare the visit counts of two actions or audiences."}, 221{"value":"n_sessions_extra_cmp","text":"Engagement Comparison","icon":"","title":"Compare the number of clicks between two actions or audiences."}, 222{"value":"n_times_cmp","text":"Impression Comparison","icon":"","title":"Compare the impression counts of one action or audience against another."}, 223{"value":"n_visits_per_session_cmp","text":"Average Page Views Comparison","icon":"","title":"Compare the average page views per visit between two actions or audiences."} ] 224 } 225 ], 226 report_users: 227 [ { text: "Add rules", 228 options: 229 [ {"value":"days_till_birthday","text":"Days Until Birthday","icon":"birthday-cake","title":"Days remaining until the visitor's birthday. Zero means today, 1 means tomorrow, etc. <p> Requires Facebook Integration. <\/p><p><a href=\"https:\/\/wiki.personyze.com\/audience-targeting\/actions-extended-page\/action-facebook-connect\/\" target=\"_blank\">Read more.<\/a><\/p><p><b> Example: <\/b> Target visitors whose birthday is within 7 days to offer a special \"Happy Birthday\" gift. <\/p>"}, 230{"value":"date","text":"Date Range (Active From\/To)","icon":"calendar","title":"Specify the date range during which this audience is active."}, 231{"value":"email","text":"User's Email","icon":"envelope","title":"The visitor's email address. <p> Requires Facebook Integration or CRM sync. <\/p><p><a href=\"https:\/\/wiki.personyze.com\/audience-targeting\/actions-extended-page\/action-facebook-connect\/\" target=\"_blank\">Read more.<\/a><\/p><p><b> Example: <\/b> The visitor's email contains \"@microsoft.com\". <\/p>"}, 232{"value":"time_since_last_visit","text":"Time Since Last Visit","icon":"calendar","title":"Time elapsed since the visitor's previous session.\n\t\t\t<p><b> Example: <\/b> Target visitors who haven't visited the website for over a month. <\/p>"}, 233{"value":"session_counter","text":"Total Session Count","icon":"refresh","title":"The total number of sessions tracked for this visitor.\n\t\t\t<p><b> Example: <\/b> The visitor has browsed the website 5 times since their first visit. <\/p>"}, 234{"value":"actions","text":"Campaign Actions History","icon":"gears","title":"Target users who have been served specific campaign actions (e.g. banners shown, emails sent)."}, 235{"value":"actions_executed","text":"Detailed Campaign History","icon":"gears","title":"Target users based on detailed campaign execution logs (e.g., specific emails delivered or banners viewed)."}, 236{"value":"user_products_interaction","text":"Product Interaction History","icon":"shopping-cart","title":"Target users based on their history with products (Views, Cart, Purchases, or Wishlist)."}, 237{"value":"site_db_products_many","text":"Product Interactions (E-Commerce)","icon":"shopping-cart","title":"<p>Target users who viewed, added to cart, or purchased products.<\/p><p>Note: Requires interaction logs sent to Personyze via <a href=\"#cat=Account%20settings%2FRecommendations%2FInteractions%20with%20products\" target=\"_blank\">containers<\/a>.<\/p>"}, 238{"value":"subst_user_data_exists","text":"Recommendation Results Exist","icon":"","title":"Target users for whom the selected recommendation algorithm returns valid results."}, 239{"value":"social","text":"User Profile Data","icon":"id-card-o","title":"Target visitors based on persistent profile attributes (e.g., demographics, account type) synced from CRM or external sources."}, 240{"value":"interests","text":"Interests","icon":"lightbulb-o","title":"Target users based on interests derived from content consumption or CRM data. Note: Interests must be configured in <a href=\"\/panel#cat=Account%20settings%2FRecommendations%2FInterests\">recommendation settings<\/a>."}, 241{"value":"date_column_updated","text":"Date Column Updated","icon":"","title":"Target users based on when a specific database column was last updated."} ] 242 } 243 ] 244 }; 245 246const CONF__ACTIONS_MENU_TRACKER = 247[ ['Most popular', 83635035,4080894865,3417805607,40972697,2704108379,4216680521,1623025020,492188746], 248 ['Change page content or look', 4080894865,83635035], 249 ['Find and replace content', 1623025020], 250 ['Integration with other systems', 1170812618,3890590501], 251 ['Recommendations', 4216680521,2704108379,4240931881,1393331489,3978514375,2528431140], 252 ['Forms, remarketing emails', 492188746,2013142013,1504958690,642995056,3648728633], 253 ['Advanced', 3417805607,2613519016,981138557], 254 //['New actions', 3308639366], 255]; 256const CONF__ACTIONS_MENU_OFFLINE = 257[ ['Select action', 3949927194,886063759,951527869,2773691522,3162034379,3298344050,1199766259,1273336257] 258]; 259const CONF__ACTION_JS_IDS_TRACKER_DEV = [804653952,2120610408,764541747,3167387044,3473854072,2672959221]; 260const CONF__ACTION_JS_IDS_OFFLINE_DEV = []; 261const CONF__ACTION_JS_IDS_SDK_FILTER = [83635035,4216680521,4240931881,2704108379,1170812618]; 262const CONF__ACTION_JS_IDS_TRACKER_DEFAULT = [4080894865,83635035,492188746,3417805607]; 263const CONF__ACTION_JS_IDS_ALERT = [3987952863,2557502939,1170812618]; 264const CONF__ACTION_JS_IDS_HTML_NO_CLICK = [1504958690,1748090321,2704108379,1622875429,4294311668,492188746,2672959221,1792104046,83635035,1143393089,3568666300,4080894865,3308639366,3901546772,1062827661,2270359400,3978514375,2528431140,2013142013,4216680521,2999110396,2120610408,40972697,2870079774,316
2647387044]; 265const CONF__ACTION_JS_IDS_FORM_NO_CLICK = [1504958690,492188746,3568666300,3901546772,2013142013]; 266const CONF__ACTION_JS_IDS_FB_NO_CLICK = [1622875429]; 267const CONF__ACTION_JS_IDS_REPORT_USERS = [3003926892]; 268const CONF__ACTION_JS_IDS_FORM = [1504958690,492188746,3568666300,3901546772,2013142013]; 269const CONF__ACTION_JS_IDS_RECOM_PRODUCTS = [4240931881,3978514375,2528431140,2013142013,4216680521]; 270const CONF__ACTION_JS_IDS_RECOM_ARTICLES = [1504958690,1393331489,2704108379,2270359400]; 271const CONF__ACTION_JS_IDS_OFFLINE_EMAIL = [951527869,886063759,1335662688,3949927194]; 272const CONF__ACTION_JS_IDS_EMAIL = [951527869,886063759,1335662688,3949927194]; 273const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA = [2533288044,2593751902,1869681987]; 274const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA_NO_RECOM = [1869681987]; 275const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA_PRODUCTS = [2593751902]; 276const CONF__ACTION_JS_IDS_EXTERNAL_MEDIA_ARTICLES = [2533288044]; 277const CONF__ACTION_JS_IDS_HTML_CONTENT = [1504958690,1748090321,2704108379,1622875429,4294311668,492188746,2672959221,1792104046,83635035,1143393089,3568666300,4080894865,3308639366,3901546772,1062827661,2270359400,3978514375,2528431140,2013142013,4216680521,2999110396,2120610408,40972697,2870079774,3167387044]; 278const CONF__ACTION_JS_IDS_RECOM_PRODUCTS_SDK = [4240931881,3978514375,2528431140,4216680521]; 279const CONF__ACTION_JS_IDS_RECOM_ARTICLES_SDK = [1393331489,2704108379,2270359400]; 280const CONF__ACTION_JS_IDS_OFFLINE_EMAIL_WEBHOOK = [951527869,886063759,1335662688,3949927194]; // 'webhook' tag removed: the Webhook action is client-only (no exec_archive), so offering it in offline campaigns configured an action that never ran 281const CONF__ACTION_JS_IDS_WEBPUSH_SUBSCRIBE = [3648728633]; 282const CONF__ACTION_JS_IDS_WEBPUSH = [1273336257,1199766259,3298344050]; 283const CONF__ACTION_JS_IDS_EXTRA_NOT_CLICK = [1504958690,1622875429,492188746,3568666300,3901546772,2013142013]; 284const CONF__ACTION_JS_ID_LIVE = 4080894865; 285const CONF__ACTION_JS_ID_OFFLINE_EMAIL_WIZARD = 1335662688; 286const CONF__ACTION_JS_ID_EXTERNAL_JSON_PRODUCTS = 3281157928; 287const CONF__ACTION_JS_ID_EXTERNAL_JSON_ARTICLES = 3488945178; 288const CONF__ACTION_JS_ID_GOOGLE_MAPS = 4294311668; 289 290const CONF__CAMPAIGNS_STATS_WIDGETS = {"actions":"tracker","ab":"tracker","ai_assistant":"tracker","meetings":"tracker","social":"tracker","legacy":"tracker","products_recom":"products_recom","products_recom_sdk":"products_recom","articles_recom":"articles_recom","articles_recom_sdk":"articles_recom","products_extra":"products_extra","offline_email":"offline_email","external_media":"external_media","external_media_products_recom":"external_media_products_recom","external_media_articles_recom":"external_media","external_json_products_recom":"external_media","external_json_articles_recom":"external_media","webpush_subscribe":"webpush_subscribe","webpush":"webpush"}; 291 292const CONF__TRACKER_CODE_HIDE_DESC = "\t<h3>Code that hides content during loading<\/h3>\n\t\t<p>\n\t\t\tAsynchronous version of Personyze tracking code. You should embed this code in the <head> of your master page or theme. We don't recommend using this version in a tag manger.\n\t\t<\/p>\n\t\t<p>\n\t\t\tIn this version, Personyze code is loaded asynchronously with the rest of the page and detects all page elements designated as placeholders for personalization actions to potentially alter their content and makes them invisible to the visitor until the personalization content is loaded successfully. We recommend using this code when your personalized content contains big file images or editing existing homepage sliders and want to make sure that generic content is showing even for a split second.\n\t\t<\/p>\n\t"; 293const CONF__TRACKER_CODE_NOHIDE_DESC = "\t<h3>Asynchronous tracking code<\/h3>\n\t\t<p>\n\t\t\tAsynchronous tracking code - Embed in the <head> of your master page or use a tag manager.\n\t\t<\/p>\n\t\t<p>\n\t\t\tAsynchronous version of Personyze tracking code should be embedded in the <head> of your master page or theme. You can use a tag manager. We recommend to set it to run with the highest priority.\n\t\t<\/p>\n\t"; 294 295if (!NS['declare_template']) 296{ NS['template_sources'] = []; 297 NS['declare_template'] = function(name, wrap_id, html, arg_names, func) 298 { this['template_sources'].push({name, wrap_id, html, arg_names, func}); 299 } 300} 301 302// ElemsAnnotator 303function _new_elem_tmp_0(ns) 304 { var annotated_elems = []; 305 306 function do_annotate(node, with_message, icon_name) 307 { if (!node) 308 { alert(with_message); 309 return; 310 } 311 var doc = node.ownerDocument || document; 312 var color = icon_name=='info' ? 'var(--color-info, #4694FB)' : icon_name=='var(--color-warning, #FACC23)' ? 'rgb(144, 152, 16)' : 'var(--color-danger, #BD362F)'; 313 var dim = with_message ? null : node.cloneNode ? ns.new_elem(node).dim() : node; 314 var p_by_id = {}; 315 var close_html = '<hr id="x" class="$round_x" style="float:right; font-size:9px; margin-left:1.2em; background-color:var(--color-info, #4694FB); color:var(--color-info_text, #FFF)">'; 316 ns.new_elem 317 ( ( with_message ? 318 '<div id="popup" class="$popup" data-scroll_to_near_node="1" style="position:absolute; border:solid '+color+' 1px;
318 color:'+color+'" data-design="hint">'+close_html+with_message+'</div>' 319 : 320 '<div id="popup" class="$popup" data-scroll_to_near_node="1" style="position:absolute; border:solid '+color+' 2px; border-radius:15px; z-index:'+0x7FFFFFFF+'; width:'+(dim.width+8)+'px; height:'+(dim.height+8)+'px">'+close_html+'</div>' 321 ), 322 null, 323 { x: 324 { onclick() 325 { popup._hide({template: 'animator_fade'}); 326 } 327 } 328 }, 329 p_by_id 330 ); 331 var popup = p_by_id.popup; 332 var is_form_tip = false; 333 // Stable hooks for site/template CSS: the tip renders into the global popup 334 // parent on document.body, so overrides must be UNSCOPED - these attributes 335 // are the only way to target it. data-form-input carries the annotated 336 // input's name; data-form the owning $cform's name (first class token). 337 popup.classList.add('pz-form-tip'); 338 if (node?.getAttribute) 339 { var input_name = node.getAttribute('data-cform-input'); 340 if (input_name) 341 { popup.setAttribute('data-form-input', input_name); 342 } 343 for (var fe=node; fe && fe.getAttribute; fe=fe.parentNode) 344 { if (typeof(fe._show_message)=='function' && typeof(fe._validate)=='function') 345 { is_form_tip = true; 346 var form_name = ((fe.getAttribute('class') || '').match(/^\s*(\S+)/) || [])[1]; 347 if (form_name) 348 { popup.setAttribute('data-form', form_name); 349 } 350 break; 351 } 352 } 353 } 354 if (is_form_tip && with_message) 355 { popup.style.cssText += ';box-sizing:border-box;max-width:280px;padding:11px 13px;'+ 356 'border:1px solid '+color+';border-left:4px solid '+color+';border-radius:10px;'+ 357 'background:#fff;font:600 13px/1.45 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;'+ 358 'box-shadow:0 6px 20px rgba(15,23,42,0.16)'; 359 } 360 popup._show 361 ( {template: 'animator_fade'}, 362 with_message ? node : 363 { left: dim.left - 4, 364 top: dim.top - 4 365 } 366 ); 367 annotated_elems.push(popup); 368 return {elem: popup}; 369 } 370 371 return class ElemsAnnotator 372 { constructor(icon_name_or_parent_elems_annotator, show_elem_func) 373 { var icon_name = typeof(icon_name_or_parent_elems_annotator)=='string' ? icon_name_or_parent_elems_annotator : null; 374 var parent = typeof(icon_name_or_parent_elems_annotator)=='object' ? icon_name_or_parent_elems_annotator : null; 375 var show_elem_funcs_stack = parent ? parent.show_elem_funcs_stack : null; 376 if (show_elem_funcs_stack) 377 { show_elem_funcs_stack = show_elem_funcs_stack.concat(show_elem_func ? [show_elem_func] : []); // clone 378 } 379 else if (show_elem_func) 380 { show_elem_funcs_stack = [show_elem_func]; 381 } 382 this.items = parent ? parent.items : []; 383 this.show_elem_funcs = parent ? parent.show_elem_funcs : []; 384 this.show_elem_funcs_stack = show_elem_funcs_stack; 385 this.icon_name = parent ? parent.icon_name : icon_name; 386 this.timer = 0; 387 this.is_top = !parent; 388 } 389 390 add(elem, message, show_elem_func, show_elem_func_owner) 391 { if (elem) 392 { this.items.push({elem, message}); 393 } 394 var show_elem_funcs_stack = this.show_elem_funcs_stack; 395 if (show_elem_funcs_stack) 396 { for (var i=0, i_end=show_elem_funcs_stack.length; i<i_end; i++) 397 { this.show_elem_funcs.push({func:show_elem_funcs_stack[i], owner:null}); 398 } 399 this.show_elem_funcs_stack = null; 400 } 401 if (show_elem_func) 402 { this.show_elem_funcs.push({func:show_elem_func, owner:show_elem_func_owner}); 403 } 404 return this; 405 } 406 407 add_if_empty(elem, message, show_elem_func, show_elem_func_owner) 408 { if (this.items.length == 0) 409 { this.add(elem, message, show_elem_func, show_elem_func_owner); 410 } 411 return this; 412 } 413 414 merge(elems_annotator) 415 { for (var i=0, i_end=elems_annotator.items.length; i<i_end; i++) 416 { this.items.push(elems_annotator.items[i]); 417 } 418 if (i_end > 0) 419 { var show_elem_funcs_stack = this.show_elem_funcs_stack; 420 if (show_elem_funcs_stack) 421 { for (var i=0, i_end=show_elem_funcs_stack.length; i<i_end; i++) 422 { this.show_elem_funcs.push({func:show_elem_funcs_stack[i], owner:null}); 423 } 424 this.show_elem_funcs_stack = null; 425 } 426 for (var i=0, i_end=elems_annotator.show_elem_funcs.length; i<i_end; i++) 427 { this.show_elem_funcs.push(elems_annotator.show_elem_funcs[i]); 428 } 429 } 430 return this; 431 } 432 433 get_first_message() 434 { return this.items.length ? this.items[0].message : null; 435 } 436 437 annotate(hide_after, hide_onclick) 438 { this.unannotate(true); 439 var elems = null; 440 var remove = null; 441 var unannotate = () => 442 { this.unannotate(); 443 if (remove) 444 { remove(); 445 remove = null; 446 } 447 }; 448 var call_balancer = new ns.CallBalancer 449 ( -1, 450 () => 451 { var scroll_to_elem = null; 452 for (var i=0, i_end=this.items.length; i<i_end; i++) 453 { var item = this.items[i]; 454 let message = item.message || ''; 455 if (message instanceof Error) 456 { ns.error(message); 457 message = message.message || message.description; 458 } 459 let elem = item.elem;
460 let cb = new ns.CallBalancer 461 ( -1, 462 () => 463 { for (; elem && !elem.offsetLeft && !elem.offsetTop; elem=elem._ne__switch_elem || elem.parentNode); 464 elems = do_annotate.call(this, elem, message, this.icon_name); 465 if (!scroll_to_elem) 466 { scroll_to_elem = elem; 467 } 468 if (hide_onclick && elem) 469 { var listeners = {}; 470 ns.new_elem(elem).add_props 471 ( { onclick: 472 { func: unannotate, 473 listeners 474 } 475 } 476 ); 477 remove = ns.pair_funcs(remove, listeners.onclick.remove); 478 } 479 } 480 ); 481 if (!elem.offsetWidth) 482 { for (var e=elem; e && !e.offsetWidth; e=e._ne__switch_elem || e.parentNode) 483 { if (typeof(e._show) == 'function') 484 { e._show({template: 'animator_swap_top', ontransitionend: cb.dec}); 485 } 486 } 487 } 488 cb.inc(); 489 } 490 if (scroll_to_elem) 491 { ns.new_elem(scroll_to_elem).scroll_into_view({}); 492 } 493 } 494 ); 495 var show_elem_funcs = this.show_elem_funcs; 496 for (var i=0, i_end=show_elem_funcs.length; i<i_end; i++) 497 { show_elem_funcs[i].func.call(show_elem_funcs[i].owner || this, call_balancer.dec()); 498 } 499 call_balancer.inc(); 500 if (hide_after) 501 { this.timer = setTimeout(unannotate, hide_after); 502 } 503 return elems; // returns only if there were no show_elem_func funcs (otherwise do_annotate() happens asynchronously) 504 } 505 506 unannotate(no_animation, ondone) 507 { if (this.timer) 508 { clearTimeout(this.timer); 509 this.timer = 0; 510 } 511 for (var i=0; i<annotated_elems.length; i++) 512 { annotated_elems[i]._hide(no_animation ? null : {template: 'animator_fade'}); 513 } 514 annotated_elems.splice(0, annotated_elems.length); 515 if (ondone) ondone.call(this); 516 } 517 518 annotate_if_top(no_hide_onclick) 519 { if (this.is_top) 520 { this.annotate(0, !no_hide_onclick); 521 } 522 return this.items.length == 0; 523 } 524 } 525 } 526 527NS['declare_template'] 528( 'personyze_button_dont_show_again', 529 null, 530 null, 531 ['ns', 'elem', 'action_id', 'n_sessions'], 532 function(ns, elem, action_id, n_sessions) 533 { elem.style.cursor = 'pointer'; 534 ns.new_elem(elem).add_props 535 ( { onclick: 536 { func(event) 537 { if (!action_id) 538 { action_id = ns.get_owner_action?.(elem)?.action_id; 539 } 540 if (n_sessions > 0) 541 { ns.disable_action(action_id, n_sessions); 542 } 543 ns.dismiss_action(action_id); 544 event.preventDefault(); 545 } 546 } 547 } 548 ); 549 } 550); 551 552NS['declare_template'] 553( 'personyze_button_popup_close', 554 null, 555 null, 556 ['ns', 'elem', 'action_id'], 557 function(ns, elem, action_id) 558 { elem.style.cursor = 'pointer'; 559 ns.new_elem(elem).add_props 560 ( { onclick: 561 { func(event) 562 { if (!action_id) 563 { action_id = ns.get_owner_action?.(elem)?.action_id; 564 } 565 ns.dismiss_action(action_id); 566 event.preventDefault(); 567 } 568 } 569 } 570 ); 571 } 572); 573 574NS['declare_template'] 575( 'personyze_button_popup_minimize', 576 null, 577 null, 578 ['ns', 'elem', 'value', 'action_id'], 579 function(ns, elem, value, action_id) 580 { elem.style.cursor = 'pointer'; 581 ns.new_elem(elem).add_props 582 ( { onclick: 583 { func(event) 584 { var action = action_id ? ns.act_params[action_id] : ns.get_owner_action && ns.get_owner_action(elem); 585 if (action) 586 { if (action._minimize_switch) 587 { action._minimize_switch(value); 588 } 589 else if (action.popup?._hide) 590 { action.popup._hide({template: 'animator_fade'}); 591 } 592 else 593 { ns.dismiss_action(action.action_id); 594 } 595 } 596 event.preventDefault(); 597 } 598 } 599 } 600 ); 601 } 602); 603 604NS['declare_template'] 605( 'personyze_message_box', 606 null, 607 null, 608 ['ns', 'elem'], 609 function(ns, elem) 610 { ns.new_elem 611 ( elem, 612 function(subelem) 613 { if (subelem.classList.contains('message-box-close')) 614 { ns.new_elem(subelem).add_props 615 ( { style: 'cursor:pointer', 616 onclick: 617 { func() 618 { elem.parentNode.removeChild(elem); 619 } 620 } 621 } 622 ); 623 } 624 } 625 ); 626 } 627); 628 629NS['declare_template'] 630( 'a_add_params', 631 null, 632 null, 633 ['ns', 'elem', 'params'], 634 function(ns, elem, params) { 635 var a = elem.getElementsByTagName('a'), 636 d, l, p; 637 638 for (var i = a.length - 1; i >= 0; i--) { 639 l = new ns.Location(a[i].href); 640 641 if (!l.hostname) continue; 642 643 if (params) { 644 p = {}; 645 646 for (var j = 0, n = params.length; j < n; j++) { 647 d = params[j].domain.trim(); 648 649 if (d && d !== '*') { 650 if (!params[j].regexp) params[j].regexp = new RegExp('^' + d.replace(/([^\w*])/g, '\\$1').replace(/\*/g, '[^\.]*?') + '$'); 651 if (!params[j].regexp.test(l.hostname)) continue; 652 } 653 654 p[params[j].name] = params[j].value; 655 } 656 657 l.params(p); 658 } 659 a[i].href = l; 660 } 661 } 662); 663 664NS['declare_template'] 665( 'html_loader', 666 null, 667 null, 668 ['ns', 'document', 'elem', 'params', 'report_tplload'], 669 function(ns, document, elem, params, report_tplload) 670 { if (!params || typeof(params)!='object' || !ns.setup) 671 { if (!ns.setup) 672 { ns.new_elem('<hr class="$icon spinner $hint" title="HTML LOADER - will be substituted with the loaded content">', elem); 673 } 674 report_tplload(); 675 return; 676 } 677 let url = ns.compose_url(params); 678 let {selector}
678 = params; 679 680 let action = ns.get_owner_action(elem); 681 682 let xhr = new XMLHttpRequest; 683 xhr.open("GET", url, true); 684 xhr.onreadystatechange = () => 685 { if (xhr.readyState == 4) 686 { if (xhr.status != 200) 687 { onerror("Status: "+xhr.status); 688 } 689 else 690 { let response = xhr.response || xhr.responseXML?.documentElement || xhr.responseText; 691 if (response != null) 692 { onsuccess(response); 693 } 694 else 695 { onerror("Invalid response from server"); 696 } 697 } 698 } 699 }; 700 xhr.send(); 701 702 function onerror(message) 703 { action.fire_event('onerror', new Error(message)); 704 report_tplload(); 705 } 706 707 function onsuccess(data) 708 { let is_external_doc = typeof(data) != 'string'; 709 let is_wrapper = false; 710 711 if (!is_external_doc) 712 { data = ns.new_elem('div', null, {innerHTML: data}); 713 is_wrapper = true; 714 } 715 716 if (selector) 717 { if (/^[\w\-]+$/.test(selector)) 718 { selector = '#'+selector+','+selector; 719 } 720 data = ns.joyquery(selector, data).get(0); 721 if (!data) 722 { return onerror(`Element not found: ${selector}`); 723 } 724 is_wrapper = false;
725 } 726 727 if (is_external_doc) 728 { data = document.importNode(data, true); 729 } 730 if (!is_wrapper) 731 { data = ns.new_elem('div', null, {innerHTML: data}); 732 } 733 734 while (data.firstChild) 735 { elem.appendChild(data.firstChild); 736 } 737 738 report_tplload(); 739 } 740 } 741); 742 743// _s_t_loaded 744function _new_elem_tmp_1(ns, infrequent_func, ftime, new_elem) 745 { const SESSION_COOKIE_PROPS = 746 [ '_si', 747 '_sid', 748 '_inew', 749 '_ls', 750 '_lurl', // last URL 751 '_lrfr', // last referrer 752 '_la', // last activity 753 '_so', // social sources 754 '_pp', // popup state, e.g. '12,20' - for action IDs 12 and 20 the initial state is not default 755 '_bh', 756 '_ane', // actions not executed 757 '_te', // switch 1: the explicit testing-mode switch - see ns.te() 758 '_te_ip', // switch 2: the account's testing-mode IP - see ns.te_ip() 759 '_tev', // format version of '_te' - see init_testing_mode() 760 '_nay', // counter for action 'yes' event 761 '_nae', // counter for action 'extra' event 762 '_nac', // counter for action 'close' event 763 ]; 764 // Bumping this discards every '_te' written by an older lib. See init_testing_mode(). 765 const TE_VERSION = 1; 766 const RE_SUBST_USER_DATA = /\$\{([\w\-]*)((?::[\w\-]+(?:\((?:'[^']*'|"[^"]*"|[^'"\)])*\))?)*)\}/g; 767 const RE_SUBST_USER_DATA_FUNC = /\s*:([\w\-]+)(?:\(((?:'[^']*'|"[^"]*"|[^'"\)]+)*)\))?\s*/g; 768 const RE_SUBST_USER_DATA_FUNC_ARGS = /\s*(?:'[^']*')+\s*|\s*(?:"[^"]*")+\s*|[^'"\),]+/g; 769 const RE_SUBST_USER_DATA_AP = /''/g; 770 const RE_SUBST_USER_DATA_QT = /""/g; 771 const RE_SUBST_USER_DATA_ESC = /([\.*+?^$|{}()\[\]\/\\])/g; 772 const RE_CUT_SCRIPTS = /<script((?:\s+[\w\-:]+(?:\s*=\s*(?:[^\s</>="']+|"[^"]*"|'[^']*')))*)>([\S\s]*?)<\/script>/gi; 773 const RE_CSS_ID = /^[^\.#*\[:>+~,'"]+$/; 774 const RE_SPACES_G = /\s+/g; 775 const RE_GOOD_URL_PARAM = /^[^\[\]]+$/; 776 const RE_GHM_NUMBER = /([\\+\\-]?\\b\\d+\\.?\\d*)\\b/; 777 const RE_OBJ_FOLLOW_PATH = /(?:^|\.)\s*([^\.\[\]"']+)|\s*\[\s*([^\]]+)\s*\]/g; 778 const RE_LOOKS_LIKE_VIEWED = /impression|view.?item|item.?view/i; 779 const RE_LOOKS_LIKE_ADD_TO_CART = /add[_\-]?to[_\-]?(?:cart|basket)|add[_\-]?product|add[_\-]?item[_\-]?cart|cart[_\-]?add[_\-]?item/i; 780 const RE_LOOKS_LIKE_REMOVE_FROM_CART = /remove[_\-]?from[_\-]?(?:cart|basket)|remove[_\-]?product|remove[_\-]?item[_\-]?cart|cart[_\-]?remove[_\-]?item/i; 781 const RE_LOOKS_LIKE_PURCHASE = /purchase|bought|buy[_\-]?item|buy[_\-]?prod/i; 782 const RE_PRODUCT_REC = /price|quantity|^sku$/i; 783 const RE_PRODUCT_ID = /^id$|\bsku\b|\bbar[_\-]?code|bar[_\-]?code\b|prod(?:uct)?[_\-]?id\b|item[_\-]?id\b|\bitem[_\-]?id|id[_\-]?item\b|\bid[_\-]?item|cart[_\-]?item\b|\bcart[_\-]?item|item[_\-]?cart\b|\bitem[_\-]?cart/i; 784 785 const LOG_EVENT_VALUE_NO_OVERRIDE = 1; 786 const LOG_EVENT_VALUE_OVERRIDE = 2; 787 const LOG_EVENT_VALUE_SUM = 4; 788 const LOG_EVENT_VALUE_LIST_COMMA = 8; 789 const LOG_EVENT_VALUE_LIST_AMP = 16; 790 const LOG_EVENT_SEND_WITH_DELAY = 32; // send with delay (if none of LOG_EVENT_SEND_* is specified, will not send, only will save to cookie to send next time) 791 const LOG_EVENT_SEND_NOW = 64; // let other events to accumulate (0 sec), then send (with delay of 0) 792 const LOG_EVENT_SEND_IMMED = 128; // send immediately 793 794 const XML_ELEMENT_NODE = 1; 795 const XML_TEXT_NODE = 3; 796 const XML_CDATA_SECTION_NODE = 4; 797 798 /* Back-off for container element searches. init_containers() re-runs the search of every 799 container that is still un-inited, on the retry timer and on every DOM change, and a selector 800 that matches nothing on this page never stops being un-inited - so the search repeats for as 801 long as the tab lives, always walking the whole document to reach the same empty answer. Our 802 own panel is the worst case (it carries account 362's tracking code, and 86 of that account's 803 87 containers describe elements of the marketing site, none of which exist there): a pass cost 804 77ms, the timer fired every 1.5s, and an open panel tab burned about a tenth of a core doing 805 nothing else. 806 The delay applies to ONE case only: nothing about the element tree has changed since this 807 container last looked. Then the same walk must reach the same empty answer, and the only 808 thing that could answer differently is something nothing observes - an attribute edit, or 809 text that a `:empty` or custom-function selector reads - so it is worth re-asking on a 810 doubling delay up to CONT_RETRY_IDLE_MAX rather than every 1.5s for ever. The first 811 CONT_RETRY_FREE passes are not delayed even then, so a page that settles still gets a few 812 free looks. 813 A structural change is never delayed, whatever the container's history. That distinction 814 cost us: an earlier version also delayed those (up to 8s, "the answer is at most 8s stale"),
815 which is wrong in kind rather than in degree - the element a container waits for may exist 816 for only a moment (a toast, a step in a checkout flow, a view the visitor passes through), 817 and skipping the look it appeared in is not a late answer but no answer at all. Measured on 818 the dev tracker: with that delay, an element that lived 2s was missed outright. Searching on 819 every structural batch is what the tracker always did, and it is affordable - the selector 820 engine hands plain CSS to the browser (joyquery's try_builtin), so a pending container costs 821 about 0.02ms a look. 822 Anything that genuinely changes the world (a new page in an SPA, a fresh set of actions or 823 conditions) calls reset_conts_retry() and starts over. */ 824 const CONT_RETRY_FREE = 6; 825 const CONT_RETRY_STEP = 1500; 826 const CONT_RETRY_IDLE_MAX = 30000; 827 /* The container types whose init walks the DOM (see the `ns.id()` call in init_container) - the 828 only ones the back-off applies to. A container that still owes a cookie, variable, storage, 829 url or meta-tag read is cheap and keeps running on every pass, because 'grab variable' in 830 particular is deliberately watched for ever. */ 831 const CONT_SELECTOR_TYPES = 3071; 832 833 var commands_queue = window[CONF__PERSONYZE_COMMANDS_JS_VAR_NAME] || []; 834 var commands_override_action_id_for = null; 835 var commands_cur_override_action_id = 0; 836 var commands_cur_manually_reported = false; 837 var want_end_session = false; 838 var want_new_navigation = false; 839 var send_timer; 840 var unblock_all_timer; 841 var unblock_qelem_timer; 842 /** What `#-S-T-H` currently covers: [{c, html_id}] per hidden container. **/ 843 var hidden_conts = null; 844 var conts_timer; // setInterval for initializing containers 845 var conts_observer = null; // MutationObserver for initializing containers 846 var conts_dom_gen = 0; // bumped by on_conts_mutation() whenever the element tree changed - see conts_retry_wait() 847 var conts_read_text = false; // some container re-reads an element it already found, so even a text-only change is worth a pass - see on_conts_mutation() 848 var executed_actions = {}; // {action_id: state}, where state===0 means started stage_1 of execution, state===1 means auto set to shown, state===2 means reported shown, state===3 means shown and "already shown" message reported, typeof(state)=='string' means error 849 var html_input_state = {}; 850 var events_sent_times = 0; 851 var start_time = 0; // is used by install_focus 852 var is_sync_blocking = false; 853 var wants_to_block = false; 854 var blocks = null; // currently blocking action IDs 855 var current_conditions = {}; // e.g. {c1:0, c2:0, c10:[9,0,0]} - keys - IDs of conditions matched on this page. only conditions that appear in containers or sticky are listed. regular conds appear as c1:0, sticky conds appear as c10:[remain_sessions, match_again, hours?1:0] 856 var current_pagegroups = []; 857 var current_interests = []; // TODO: strongen interests according to page content 858 var time_formats = {time: '%H:%M:%S', time_short: '%H:%M', date: '%Y %b %d', date_short: '%y/%m/%d', datetime: '%Y %b %d %H:%M:%S', datetime_short: '%y/%m/%d %H:%M'}; 859 var first_log_visit_sent = false; 860 var is_parallel = false; // parallel asynchronous load 861 var is_ready = false; // will be set to true when first log-visit will finish (bringing actions and containers) 862 var actions_q = []; 863 var personyze_override = null; 864 var default_containers = {}; 865 var conts_map = {}; 866 var timers_ac = {}; // timers for delayed actions 867 var not_found_html_changes = new Map; 868 var ondropcss = null; 869 var onevent = {}; 870 var onerr = {}; 871 var use_location = null; 872 var actions_subst_user_data = {}; 873 var cur_visit_id = 0; 874 var event_obj = null; 875 var flush_events = () => {}; 876 var ins_status_install = false; 877 var ins_status_log_visit = false; 878 var ins_status = null; 879 var h_onscroll = null; 880 var onscroll_funcs = null; 881 var send_request_onload = null; 882 var send_request_onerror = null; 883 var actions_history = null; 884 var add_to_cart_func = null; 885 var add_to_wishlist_func = null; 886 var remove_from_cart_func = null; 887 var event_send_with_delay = 0; 888 var event_send_now = 0; 889 var send_after_unload = false;
890 var ga_is_active = false; 891 /* Account-level default destinations for automatic per-action reporting 892 (acc_settings.default_send_to_dests). Google Analytics is not in this 893 list: it has its own switch and its own call, unchanged since forever. */ 894 var default_send_dests = ''; 895 var ga_is_gtm = false; 896 var ga_queue = []; 897 var gtm_data_layer_name = null; 898 var gtm_grab = []; 899 var gtm_product_id_column = null; 900 var gtm_key = null; 901 var gtm_inst = null; 902 var gtm_history = []; 903 var ga_history = []; 904 var tealium_try_init_till = Date.now() + 20_000; // 20 seconds 905 var tealium_init_timer = null; 906 var tealium_storage_name = null; 907 var tealium_audiences = null; 908 var when_action_executed = {}; 909 var host_id = 0; 910 var force_unblock_all = false; 911 /* Consent hold. Only ever set when the site's snippet asked for it (3rd argument to 912 ns.setup); every other installation behaves exactly as before, which is the point -- 913 this file is served from ONE url to every account, so the default path must not change. 914 915 `consent_denied` makes the existing `is_on()` gate in ns.setup() take its OFF branch 916 without touching the cookie. Writing an opt-out cookie to represent "hasn't consented 917 yet" would be wrong twice over: it is storage before consent, and it would persist a 918 refusal the visitor never made. */ 919 var consent_denied = false; 920 var consent_pending = null; // [server_id, domains] captured while waiting 921 /* Sent as `cs=1` on every request once consent has been granted, and never sent otherwise. 922 It is what lets the server tell "this account has consent mode on and the visitor agreed" 923 apart from "this account has consent mode on and the snippet was pasted without the third 924 argument, so the library never waited for anyone". The second case is a misconfiguration 925 that would otherwise track silently and look identical to a working install; the server 926 refuses it. Absent rather than `cs=0` when not granted, so that the request every existing 927 installation sends is byte-for-byte what it sends today. */ 928 var consent_granted = false; 929 var tracker_events = new TrackerEvents; 930 var before_unload_time = 0; 931 var notifications = null; 932 var cur_notification_a = 0; 933 var add_to_cart_success_html = null; 934 var add_to_cart_error_html = null; 935 var add_to_wishlist_success_html = null; 936 var add_to_wishlist_error_html = null; 937 var articles_delivered = []; 938 var articles_delivered_timer = null; 939 var stat_track_u_id = null; // cookie backup 940 var _stat_track_s_id = null; // cookie backup 941 942 943 function Mark1() {}; 944 function Mark2() {}; 945 946 function ins_status_done(is_log_visit) 947 { if (!is_log_visit) 948 { ins_status_install = true; 949 } 950 else 951 { ins_status_log_visit = true; 952 } 953 if (ins_status_install && ins_status_log_visit) 954 { log_visit_2(); 955 } 956 } 957 958 function onend() 959 { use_location = null; 960 if (want_end_session) 961 { want_end_session = false; 962 ns.cookie_obj('_stat_track_s_id', null, true); 963 tracker_events.set(null); 964 } 965 var {containers} = ns; 966 for (var c_id in containers) 967 { if (containers.hasOwnProperty(c_id)) 968 { var c = containers[c_id]; 969 if (c?.onflush) 970 { c.onflush(); 971 c.onflush = null; 972 } 973 c.inited &= ~8192; 974 c.tr_clicked = null; 975 } 976 } 977 events_sent_times = 0; 978 var aa = executed_actions; 979 executed_actions = {}; 980 for (var a in aa) 981 { if (aa.hasOwnProperty(a) && !ns.dismiss_action(a) && typeof(aa[a])!='string') 982 { executed_actions[a] = aa[a]; 983 } 984 } 985 } 986 987 function TrackerEvents() 988 { this.sent_events = {}; 989 this.buffered_mode = 0; 990 991 this.get = function() 992 { if (window.sessionStorage) 993 { try 994 { var value = sessionStorage['_S_T.bf']; 995 return !value || value=='[]' || value=='"[]"' ? {} : JSON.parse(value) || {}; // i can't catch who is setting [] on Safari 996 } 997 catch (e) 998 { return {}; 999 } 1000 } 1001 else 1002 { return ns.cookie_obj('_stat_track_s_id'); 1003 } 1004 }; 1005 1006 this.set = function(stored_events) 1007 { if (window.sessionStorage) 1008 { sessionStorage['_S_T.bf'] = JSON.stringify(stored_events || {}); 1009 } 1010 else 1011 { return ns.cookie_obj('_stat_track_s_id', stored_events, true); 1012 } 1013 }; 1014 1015 this.add = function(c_id, value, mode, container_mask, rid_e) 1016 { let m = this.get(); 1017 let reset = true; 1018 let no_replace_add = 1; 1019 let time = Date.now(); 1020 this.buffered_mode |= mode & (LOG_EVENT_SEND_WITH_DELAY|LOG_EVENT_SEND_NOW|LOG_EVENT_SEND_IMMED); 1021 // if rid_e < 0 then mode must be LOG_EVENT_VALUE_LIST_COMMA, and value must be single integer 1022 if (mode & (LOG_EVENT_VALUE_NO_OVERRIDE|LOG_EVENT_VALUE_OVERRIDE|LOG_EVENT_VALUE_SUM|LOG_EVENT_VALUE_LIST_COMMA|LOG_EVENT_VALUE_LIST_AMP)) 1023 { no_replace_add = 0; 1024 for (let i in m) 1025 { if (m.hasOwnProperty(i)) 1026 { let sen_time = this.sent_events[i]; 1027 if (sen_time && time-sen_time>=5000 && i.charAt(0)!='_') 1028 { delete m[i]; 1029 delete this.sent_events[i]; 1030 continue; 1031 } 1032 var prefix = '_'+container_mask+'_'+rid_e+'_'; 1033 if (i.substring(i.lastIndexOf(prefix) + prefix.length) == c_id) 1034 { // found 1035 if (mode & LOG_EVENT_VALUE_NO_OVERRIDE) 1036 { if (m[i] == value) reset = false;
1037 } 1038 else if (mode & LOG_EVENT_VALUE_SUM) 1039 { value -= -m[i]; 1040 } 1041 else if (mode & (LOG_EVENT_VALUE_LIST_COMMA|LOG_EVENT_VALUE_LIST_AMP)) 1042 { let delim = mode & LOG_EVENT_VALUE_LIST_AMP ? '&' : ','; 1043 let res = (m[i]+'').split(delim); 1044 function key(kv) 1045 { let pos = mode&LOG_EVENT_VALUE_LIST_AMP ? kv.indexOf('=') : -1; 1046 return pos==-1 ? kv : kv.substr(0, pos); 1047 } 1048 let keys = []; 1049 for (let j=0; j<res.length; j++) 1050 { keys[j] = key(res[j]); 1051 } 1052 for (let new_kv of (value+'').split(delim)) 1053 { let k = key(new_kv); 1054 let pos = keys.indexOf(k); 1055 if (pos!=-1 && k=='TP' && (mode&LOG_EVENT_VALUE_LIST_AMP)) 1056 { res[pos] += ','+new_kv.substring(3); 1057 } 1058 else 1059 { res[pos==-1 ? res.length : pos] = new_kv; 1060 } 1061 } 1062 m[i] = res.join(delim); 1063 reset = false; 1064 } 1065 if (reset) delete m[i]; 1066 } 1067 } 1068 } 1069 } 1070 if (reset && c_id) 1071 { let sid_obj = ns.cookie_obj('_stat_track_s_id'); 1072 m[((ns.secs() - sid_obj._sid + no_replace_add) | 0)+'_'+container_mask+'_'+rid_e+'_'+c_id] = value; // make "replace events" be earlier 1073 } 1074 this.set(m); 1075 }; 1076 1077 this.cleanup = function(error) 1078 { var m = this.get(); 1079 var time = Date.now(); 1080 for (var i in this.sent_events) 1081 { if (this.sent_events.hasOwnProperty(i)) 1082 { if (!error || time-this.sent_events[i]>=5000) 1083 { delete m[i]; 1084 delete this.sent_events[i]; 1085 } 1086 } 1087 } 1088 this.set(m); 1089 }; 1090 } 1091 1092 function get_personyze_override() 1093 { if (!personyze_override) 1094 { personyze_override = {a: null, t: null, u: null}; 1095 var str = ns.get_param(CONF__CLIENT_OVERRIDES_PARAM_NAME) || ns.get_param(CONF__CLIENT_OVERRIDES_PARAM_NAME_2); 1096 if (str) 1097 { var arr = str.split(','); 1098 for (var i=0; i<arr.length; i++) 1099 { if (arr[i] == 'testing') 1100 { personyze_override.t = 1; 1101 } 1102 else if (arr[i] == 'production') 1103 { personyze_override.t = 0; 1104 } 1105 else 1106 { var what = arr[i].charAt(0); 1107 var data = arr[i].substring(1); 1108 if (what == 'a') 1109 { if (parseInt(data)) 1110 { if (!personyze_override.a) 1111 { personyze_override.a = []; 1112 } 1113 personyze_override.a.push(parseInt(data)); 1114 } 1115 } 1116 else if (what == 'u') 1117 { data = data.match(/^(\-?\d+)(n?)(o?)(u?)_(\d+)_(\d+)_([+\-\d:;]*)_(\d+)_(\d+)$/); 1118 if (data) 1119 { personyze_override.u = 1120 { uid: data[1], 1121 no_count: !!data[2], 1122 off: !!data[3], 1123 inew: data[4] ? 2 : 0, 1124 st: data[5], 1125 ls: data[6], 1126 sy: data[7].replace(/;/g, ','), 1127 sid: data[8], 1128 bh: data[9] 1129 }; 1130 } 1131 } 1132 } 1133 } 1134 } 1135 } 1136 return personyze_override; 1137 } 1138 1139 function exec_ondropcss() 1140 { if (ondropcss) 1141 { ondropcss(); 1142 if (ns.IS_MSIE) 1143 { document.body.style.visibility = 'visible'; // fixes many bugs 1144 } 1145 ondropcss = null; 1146 } 1147 } 1148 1149 function exec_onevent() 1150 { for (var f in onevent) 1151 { if (onevent.hasOwnProperty(f) && typeof(onevent[f])=='function') 1152 { ns.pcall(onevent[f]); 1153 } 1154 } 1155 } 1156 1157 function exec_onerr(args) 1158 { for (var f in onerr) 1159 { if (onerr.hasOwnProperty(f) && typeof(onerr[f])=='function') 1160 { ns.pcall(onerr[f], null, args); 1161 } 1162 } 1163 } 1164 1165 function click_event_page(c_id, onload, onerror) 1166 { want_new_navigation = true; 1167 log_event(c_id, 1, (event_send_with_delay&16384 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&16384 ? LOG_EVENT_SEND_NOW : 0), 16, 0, onload, onerror); 1168 } 1169 1170 /** HOW WIDELY A WYSIWYG CHANGE MAY LAND. 1171 1172 `qelem_id` is the shortest selector that was unique on the page the change was recorded on, 1173 so everywhere else it is deliberately under-specified: `div.left > h1` finds a heading on 1174 almost any page and the change lands there looking like it worked. The editor now records 1175 what the author meant, and this is where that is honoured. 1176 1177 change.scope 'page' - only change.scope_url, compared by scope_url_key() 1178 change.scope 'shape' - only where change.scope_fp still describes the element 1179 change.scope 'any', or NO scope at all - wherever the selector matches 1180 1181 No scope means the change was recorded before the setting existed. It keeps applying exactly
1182 where it always did; nothing about an existing account changes because this shipped. 1183 1184 Twin of `scopeUrlKey`/`fingerprintMatches` in webdriver-apps (fresh-service). Pinned by 1185 src/fresh-service/webdriver-apps/src/modules/pageeditor/scopedChanges.test.ts, which reads 1186 both files - there is no runtime where they meet. 1187 **/ 1188 1189 /** Query parameters that say where the visitor came from, not which page this is. A change 1190 scoped to one URL has to survive the same page arriving from an ad. **/ 1191 var SCOPE_URL_NOISE = /^(utm_|_S_T$|gclid$|fbclid$|msclkid$|dclid$|ttclid$|twclid$|igshid$|mc_eid$|_ga$)/i; 1192 1193 function scope_url_key(href) 1194 { try 1195 { var url = new URL(href); 1196 var names = []; 1197 url.searchParams.forEach(function(v, name) {if (!SCOPE_URL_NOISE.test(name)) names.push(name)}); 1198 names.sort(); 1199 var query = names.map(function(name) {return name+'='+url.searchParams.get(name)}).join('&'); 1200 return url.origin + url.pathname.replace(/\/$/, '') + (query ? '?'+query : ''); 1201 } 1202 catch (e) 1203 { return href; 1204 } 1205 } 1206 1207 /** Is this element the same thing the fingerprint was taken from? Every recorded level must 1208 line up with an ancestor: same tag, and - where classes were recorded - at least ONE of them 1209 still present. One is enough on purpose: a page that renamed a modifier class is still the 1210 same page, and a change that silently stops applying is worse than one that lands wrong, 1211 because nobody notices it for weeks. 1212 **/ 1213 function scope_fp_matches(elem, fp) 1214 { if (!fp || !fp.length) 1215 { return true; 1216 } 1217 var node = elem; 1218 for (var i=0; i<fp.length; i++) 1219 { var step = fp[i]; 1220 if (!node || !node.localName || node.localName!=step.t) 1221 { return false; 1222 } 1223 if (step.c && step.c.length) 1224 { var ok = false; 1225 for (var j=0; j<step.c.length; j++) 1226 { if (node.classList && node.classList.contains(step.c[j])) 1227 { ok = true; 1228 break; 1229 } 1230 } 1231 if (!ok) 1232 { return false; 1233 } 1234 } 1235 node = node.parentElement; 1236 } 1237 return true; 1238 } 1239 1240 /** Is this page one the change is allowed on at all? Only 'page' can answer no here; 'shape' 1241 is decided per element, by scope_fp_matches(). **/ 1242 function scope_allows_page(change) 1243 { return change.scope!='page' || !change.scope_url || scope_url_key(change.scope_url)==scope_url_key(location.href); 1244 } 1245 1246 /** This function is appreciated to be as similar to page_editor's.apply_html_changes() as possible 1247 **/ 1248 function apply_html_change_on_elem(elem, change, action_id, set_img_src) 1249 { function subst_attrs(attrs, action_id, except) 1250 { if (!attrs) return null; 1251 var c_attrs = {}; 1252 for (var i in attrs) 1253 { if (attrs.hasOwnProperty(i) && i!=except) 1254 { c_attrs[i] = ns.subst_user_data_action(attrs[i], '', action_id); 1255 } 1256 } 1257 return c_attrs; 1258 } 1259 1260 function put_elem(elem, put_elem, where) 1261 { ns.new_elem(put_elem).add_to_parent(where=='before' ? {nextSibling:elem} : where=='after' ? {previousSibling:elem} : elem); 1262 } 1263 1264 function pick_node(ref_node, where) 1265 { return where=='before' ? ref_node.previousSibling : where=='after' ? ref_node.nextSibling : ref_node.lastChild; 1266 } 1267 1268 function edit_text_or_html(elem, is_html, change_text, action_id) 1269 { let text = ''; 1270 let base = is_html ? elem.innerHTML : elem.data; 1271 for (let i=0, i_end=change_text.length; i<i_end; i++) 1272 { let h = change_text[i]; 1273 if (typeof(h) != 'string') 1274 { let pos=h[0], len=h[1]|0; 1275 if (pos < 0) pos += base.length; 1276 if (len <= 0) len = base.length + len - pos; 1277 h = base.substr(pos, len); 1278 } 1279 text += ns.subst_user_data_action(h, '', action_id); 1280 } 1281 if (!is_html) 1282 { elem.data = text; 1283 } 1284 else 1285 { patch_r(elem, ns.new_elem(elem.nodeName, null, {innerHTML: text})); 1286 function patch_r(e, new_e) 1287 { // patch children 1288 let child = e.firstChild; 1289 let new_child = new_e.firstChild; 1290 while (child || new_child) 1291 { let next = child?.nextSibling; 1292 let new_next = new_child?.nextSibling; 1293 if (!child) 1294 { e.appendChild(new_child); 1295 } 1296 else if (!new_child) 1297 { e.removeChild(child); 1298 } 1299 else if (child.nodeType != XML_ELEMENT_NODE) 1300 { if (child.nodeType==XML_TEXT_NODE && new_child.nodeType==XML_TEXT_NODE) 1301 { // assign text on the left to text on the right 1302 child.data = new_child.data; 1303 } 1304 else 1305 { // remove text on the left, and redo on the right 1306 e.removeChild(child); 1307 new_next = new_child; 1308 } 1309 } 1310 else if (new_child.nodeType != XML_ELEMENT_NODE) 1311 { // insert text from right to left, and redo on the left 1312 e.insertBefore(new_child, child); 1313 next = child; 1314 } 1315 else if (child.nodeName != new_child.nodeName) 1316 { let has_elem_to_patch = false;
1317 for (let n=new_next; n && !has_elem_to_patch; n=n.nextElementSibling) 1318 { has_elem_to_patch = child.nodeName == n.nodeName; 1319 } 1320 if (has_elem_to_patch) 1321 { // insert element from right to left, and redo on the left 1322 e.insertBefore(new_child, child); 1323 next = child; 1324 } 1325 else 1326 { // remove element on the left, and redo on the right 1327 e.removeChild(child); 1328 new_next = new_child; 1329 } 1330 } 1331 else 1332 { // patch attributes 1333 let names = []; 1334 for (let i=0, i_end=new_child.attributes.length; i<i_end; i++) 1335 { let attr = new_child.attributes[i]; 1336 try 1337 { child.setAttribute(attr.name, attr.value); 1338 names[names.length] = attr.name; 1339 } 1340 catch (e) 1341 { ns.error(e); 1342 } 1343 } 1344 for (let i=0; i<child.attributes.length; i++) 1345 { let attr = child.attributes[i]; 1346 if (names.indexOf(attr.name) == -1) 1347 { child.removeAttribute(attr.name); 1348 i--; 1349 } 1350 } 1351 // patch element 1352 patch_r(child, new_child); 1353 } 1354 child = next; 1355 new_child = new_next; 1356 } 1357 } 1358 } 1359 } 1360 1361 function new_elem_changes(elem_props, action_id) 1362 { if (typeof(elem_props) == 'string') 1363 { return document.createTextNode(ns.subst_user_data_action(elem_props, '', action_id)); 1364 } 1365 else 1366 { let [node_name, node_attrs, node_child_nodes, node_inner_html] = elem_props; 1367 let el_add = document.createElement(node_name); 1368 if ((node_name=='video' || node_name=='audio') && typeof(el_add.play)!='function') 1369 { el_add = document.createElement('embed'); 1370 } 1371 if (node_inner_html) 1372 { node_inner_html = ns.subst_user_data_action(node_inner_html, '', action_id); 1373 } 1374 ns.new_elem(el_add).add_props 1375 ( { attributes: subst_attrs(node_attrs, action_id), 1376 innerHTML: node_inner_html 1377 } 1378 ); 1379 if (node_child_nodes && node_inner_html==null)
1380 { for (let i=0, i_end=node_child_nodes.length; i<i_end; i++) 1381 { el_add.appendChild(new_elem_changes(node_child_nodes[i], action_id)); 1382 } 1383 } 1384 el_add = ns.new_elem(el_add, null); // process templates 1385 return el_add; 1386 } 1387 } 1388 1389 let last_add_elem = null; 1390 1391 // set_img_src 1392 let img_src = change.attrs?.src && elem.nodeName.toUpperCase()=='IMG' && change.attrs.src; 1393 if (img_src) 1394 { set_img_src.push 1395 ( { elem: elem, 1396 src: ns.subst_user_data_action(img_src, '', action_id) 1397 } 1398 ); 1399 } 1400 1401 if (change.inner_html) 1402 { edit_text_or_html(elem, true, change.inner_html, action_id); 1403 elem = ns.new_elem(elem, null); // process templates 1404 } 1405 if (change.text) 1406 { let {where} = change; 1407 // 1) pick the node 1408 let node = pick_node(elem, where); 1409 if (node.nodeType==XML_TEXT_NODE || node.nodeType==XML_CDATA_SECTION_NODE) 1410 { // handle adjacent text nodes 1411 let ref_node = node; 1412 while (true) 1413 { let further_node = pick_node(ref_node, where=='inside' ? 'before' : where); 1414 if (further_node.nodeType!=XML_TEXT_NODE && further_node.nodeType!=XML_CDATA_SECTION_NODE) 1415 { break; 1416 } 1417 if (further_node.length > 0) 1418 { if (where == 'after') 1419 { node.data = node.data + further_node.data; 1420 } 1421 else 1422 { node.data = further_node.data + node.data; 1423 } 1424 further_node.parentNode.removeChild(further_node); 1425 } 1426 else 1427 { // empty text nodes are used as stubs, so no touch 1428 ref_node = further_node; 1429 } 1430 } 1431 } 1432 else 1433 { // create new node if not found 1434 node = document.createTextNode(''); 1435 put_elem(elem, node, where); 1436 } 1437 // 2) edit node's text 1438 edit_text_or_html(node, false, change.text, action_id); 1439 } 1440 ns.new_elem(elem).add_props({attributes: subst_attrs(change.attrs, action_id, img_src ? 'src' : null)}); 1441 if (change.attrs) 1442 { if (change.attrs['class']) 1443 { elem.className = change.attrs['class']; 1444 } 1445 } 1446 if (change.dup) 1447 { for (let k=change.dup-1; k>=0; k--) 1448 { elem.parentNode.insertBefore(ns.new_elem(elem).clone_node(), elem); 1449 } 1450 } 1451 if (change.mv) 1452 { let el_add = ns.qelem(change.mv_qelem_id); 1453 if (el_add) 1454 { put_elem(el_add, elem, change.mv); 1455 } 1456 } 1457 if (change.rm_shallow) 1458 { while (elem.firstChild) 1459 { elem.parentNode.insertBefore(elem.firstChild, elem); 1460 } 1461 elem.parentNode.removeChild(elem); 1462 } 1463 if (change.rm) 1464 { elem.parentNode.removeChild(elem); 1465 } 1466 if (change.e_shallow || change.e) 1467 { let el_add = new_elem_changes(change.e_shallow || change.e, action_id); 1468 if (change.e) 1469 { put_elem(elem, el_add, change.where); 1470 } 1471 else 1472 { let n_next = change.n_next-0 || 1; 1473 elem.parentNode.insertBefore(el_add, elem); 1474 el_add.appendChild(elem); 1475 while (--n_next>0 && el_add.nextSibling) 1476 { el_add.appendChild(el_add.nextSibling); 1477 } 1478 } 1479 last_add_elem = el_add; 1480 } 1481 if (change.events) 1482 { for (let k=0; k<change.events.length; k++) 1483 { let event = change.events[k]; 1484 let {onwhat, custom_event, value, action_state, js, once, fire_action, rm_elem} = event; 1485 let e_action_id = event.action_id || event.action_goal; 1486 let func=null, remove=null; 1487 if (custom_event > 0) 1488 { func = function() 1489 { remove(); 1490 ns.custom_event(custom_event, value); 1491 }; 1492 } 1493 else if (fire_action > 0) 1494 { func = function() 1495 { remove(); 1496 do_exec_action(fire_action, null, null, null, true); // TODO: ns.push(['Fire action', fire_action]); 1497 }; 1498 } 1499 else if (e_action_id > 0) 1500 { func = function() 1501 { remove(); 1502 ns.action_state(e_action_id, action_state || 'goal'); 1503 }; 1504 } 1505 else if (e_action_id == -1) 1506 { func = function() 1507 { remove(); 1508 ns.action_state(change.action_id || action_id, action_state || 'goal'); 1509 }; 1510 } 1511 else if (js) 1512 { func = function() 1513 { if (once) remove(); 1514 ns.inject_code(js); 1515 }; 1516 } 1517 else if (rm_elem) 1518 { func = function() 1519 { var elem = ns.id(rm_elem); 1520 if (elem) 1521 { elem.style.display = 'none'; 1522 } 1523 }; 1524 } 1525 if (func) 1526 { let listeners = {}; 1527 ns.new_elem(elem).add_props({['on'+onwhat]: {func, capture: true, listeners}}); 1528 remove = listeners['on'+onwhat].remove; 1529 } 1530 } 1531 } 1532 return last_add_elem; 1533 } 1534 1535 function get_cookie_domain() 1536 { var domain = ''; 1537 var l_host = location.host; 1538 var ast = ''; 1539 if (l_host.indexOf('www.') == 0) 1540 { l_host = l_host.substring(4); 1541 ast = '.'; 1542 } 1543 var {domains} = ns; 1544 for (var host in domains) 1545 { if (domains.hasOwnProperty(host)) 1546 { if (l_host == host) 1547 { if (!domain) domain = ast+host; 1548 } 1549 else if (host.substr(0, 2) == '*.') 1550 { host = host.substring(2); 1551 var pos = l_host.lastIndexOf(host);
1552 if (pos!=-1 && pos+host.length==l_host.length) 1553 { if (host.indexOf('.')==-1 || host.match(/^co\.\w{2,3}$/)) // *.com or *.co.il 1554 { var parts = l_host.split('.'); 1555 if (parts.length>1 && parts[parts.length - 1].length<=2) 1556 { var p = parts.pop(); 1557 parts[parts.length - 1] += '.'+p; 1558 } 1559 host = ''; 1560 for (var i=0; i<2 && parts.length>0; i++) 1561 { var p = parts.pop(); 1562 host = '.'+p+host; 1563 } 1564 } 1565 else 1566 { host = '.'+host; 1567 } 1568 if (domain.charAt(0)!='.' || host.length<domain.length) 1569 { domain = host; 1570 } 1571 } 1572 } 1573 } 1574 } 1575 return domain || ast+l_host; 1576 } 1577 1578 function exec_actions(a, ondone) 1579 { if (!a) 1580 { a = []; 1581 } 1582 // override actions and conditions 1583 if (!window['_S_T_ACTIONS_DISABLED']) 1584 { if (!event_obj) 1585 { event_obj = {}; 1586 let props = get_request_props(1, 0, 1); 1587 if (props) 1588 { for (let i of Object.keys(props)) 1589 { event_obj[i] = props[i]; 1590 } 1591 } 1592 let {user_data} = ns; 1593 for (let i of Object.keys(user_data)) 1594 { event_obj[i] = user_data[i]; 1595 } 1596 } 1597 let call_balancer = new ns.CallBalancer(-1, ondone); 1598 // override actions 1599 let override_actions = cur_visit_id && get_personyze_override().a || null; 1600 if (override_actions) 1601 { a.unshift([cur_visit_id].concat(override_actions)); 1602 } 1603 // execute actions 1604 for (let i=0; i<a.length; i++) 1605 { let a_i = a[i]; 1606 let rid = a_i[0]; 1607 let aa = a_i.slice(1).sort 1608 ( (a, b) => 1609 ( (_S_T.act_params[a]?.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - (_S_T.act_params[b]?.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) 1610 || 1611 (a - b) 1612 ) 1613 ); 1614 for (let aid of aa) 1615 { let bypass_others = do_exec_action(aid+'', rid, call_balancer.dec(), null, false, override_actions?.indexOf(aid|0)>=0); 1616 if (bypass_others) 1617 { return true; 1618 } 1619 } 1620 } 1621 call_balancer.inc(); 1622 } 1623 } 1624 1625 function actions_q_done(a, rid) 1626 { var q = actions_q; 1627 for (var i=0; i<q.length; i++) 1628 { if (q[i][0] == rid) 1629 { for (var j=1; j<q[i].length; j++) 1630 { if (q[i][j] == a) 1631 { if (q[i].length == 2) 1632 { q.splice(i, 1); 1633 } 1634 else 1635 { q[i].splice(j, 1); 1636 } 1637 return; 1638 } 1639 } 1640 } 1641 } 1642 } 1643 1644 function actions_q_add(actions) 1645 { actions_q = actions_q.concat(ns.clone_obj(actions)); 1646 } 1647 1648 function do_clear_actions(action_ids) 1649 { let dismissed_ids = []; 1650 if (action_ids?.length) 1651 { for (let i=0; i<action_ids.length; i++) 1652 { action_ids[i] |= 0; 1653 let action_id = action_ids[i]; 1654 delete executed_actions[action_id]; 1655 let params = ns.act_params[action_id]; 1656 if (params?.dismiss) 1657 { try 1658 { params.dismiss(); 1659 dismissed_ids.push(action_id); 1660 } 1661 catch (e) 1662 { ns.error(e); 1663 } 1664 } 1665 } 1666 let cc = ns.containers; 1667 for (let c_id in cc) 1668 { let c = cc[c_id]; 1669 if (cc.hasOwnProperty(c_id) && c?.a) 1670 { for (let i=0; i<c.a.length; i++) 1671 { let action_id = c.a[i]|0; 1672 if (action_ids.indexOf(action_id) != -1) 1673 { delete executed_actions[action_id]; 1674 c.html[i].detach?.(); 1675 c.a.splice(i, 1); 1676 c.html.splice(i, 1); 1677 dismissed_ids.push(action_id); 1678 i--; 1679 } 1680 } 1681 } 1682 } 1683 } 1684 return dismissed_ids; 1685 } 1686 1687 function init_activity_events() 1688 { /*function is_url_in(u) 1689 { var loc = new ns.Location(u); 1690 return loc.hostname==location.hostname || (loc.hostname in ns.domains) || loc.protocol=='mailto:' || loc.protocol=='javascript:'; 1691 }*/ 1692 function init_activity_event(c_id) 1693 { if (c_id==-5 || c_id==-6 || c_id==-7 || c_id==-8) 1694 { var e_name = ['onmouseup', 'onmouseup', 'onkeyup', 'onscroll'][-5 - c_id]; 1695 var last = 0; 1696 ns.add_e 1697 ( e_name=='onscroll' ? window : document.body, e_name, function(event) 1698 { var now = Date.now(); 1699 if 1700 ( c_id==-5 && event.which==1 || 1701 c_id==-6 && event.which==3 || 1702 c_id==-7 || 1703 c_id==-8 && now-last>999 1704 )
1705 { log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&1 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&1 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id); 1706 if (c_id == -8) 1707 { last = now; 1708 } 1709 } 1710 }, 1711 c_id 1712 ); 1713 } 1714 } 1715 init_activity_event(-5); 1716 init_activity_event(-6); 1717 init_activity_event(-7); 1718 init_activity_event(-8); 1719 /*init_activity_event(-9); 1720 init_activity_event(-10);*/ 1721 } 1722 1723 function init_container(c_id, c, noraise, is_trigger_click) 1724 { let want_monitor_containers = false; 1725 if (c.cn?.length) 1726 { var ok=0, found=0; 1727 for (var i=c.cn.length-1; i>=0; i--) 1728 { if (c.cn[i] < 0) 1729 { if (current_conditions.hasOwnProperty('c'+(-c.cn[i]))) 1730 { return want_monitor_containers; // don't use container if condition matches 1731 } 1732 } 1733 else 1734 { found = 1; // if there were no positive containers - that means ok 1735 if (current_conditions.hasOwnProperty('c'+c.cn[i])) ok = 1; // use container if condition matches 1736 } 1737 } 1738 if (found && !ok) 1739 { return want_monitor_containers; 1740 } 1741 } 1742 var tp = c.tp & ~c.inited; 1743 // assume: tp!=0 1744 var html_id = c.hi; 1745 var html_attr = c.ha; 1746 if (((tp) & 3071)) 1747 { var elems = c.found_elems; 1748 if (elems?.find(e => !ns.new_elem(e).is_node_linked())) 1749 { elems = c.found_elems = null; 1750 } 1751 if (!elems) 1752 { try 1753 { let found = ns.id(c.hi, true); 1754 if (found.length) 1755 { if (!c.mm) 1756 { found.length = 1; 1757 } 1758 elems = c.found_elems = found; 1759 c.found_elems_tr = null; // refind 1760 } 1761 } 1762 catch (e) 1763 { 1764 } 1765 } 1766 } 1767 if (c.tr && !is_trigger_click) 1768 { let triggers = c.found_elems_tr; 1769 if (triggers?.find(e => !ns.new_elem(e).is_node_linked())) 1770 { triggers = c.found_elems_tr = null; 1771 } 1772 if (!triggers) 1773 { triggers = c.found_elems_tr = ns.id(c.tr, true); 1774 for (let tr of triggers) 1775 { ns.add_e 1776 ( tr, 1777 'onclick', 1778 () => 1779 { c.found_elems = null; // refind 1780 if (c.tr_clicked == null) // null: !clicked && !data; function: !clicked && data; true: clicked && !data; false: clicked && data (sent) 1781 { c.tr_clicked = true; 1782 init_container(c_id, c, noraise, 1); 1783 } 1784 else if (typeof(c.tr_clicked) == 'function') 1785 { c.tr_clicked(); 1786 c.tr_clicked = false; 1787 } 1788 }, 1789 'ctrigger' 1790 ); 1791 if (!c.tr_mm) 1792 { break; 1793 } 1794 } 1795 } 1796 return want_monitor_containers; 1797 } 1798 if (!elems && c.html?.length) 1799 { elems = c.found_elems = try_create_container(c_id); 1800 html_attr = ''; 1801 } 1802 for (let elem of elems || []) 1803 { if (((tp) & 1)) 1804 { ns.add_style(elem, c.em); 1805 if (c.a != null) 1806 { try 1807 { for (let i=0; i<c.html.length; i++) 1808 { if (i >= c.uc) 1809 { if (c.cc>0 && !c.h_cycle) 1810 { c.h_cycle = setInterval 1811 ( () => 1812 { if (c.html.length <= c.uc) 1813 { clearInterval(c.h_cycle); 1814 c.h_cycle = null; 1815 for (let i=0; i<c.html.length; i++) 1816 { c.html[i].attach(elem, i); 1817 } 1818 } 1819 else 1820 { let first = c.html.shift(); 1821 if (c.uc == 1) 1822 { c.html.push(first); 1823 c.html[i-1].swap(elem, i-1, first); 1824 } 1825 else 1826 { first.detach(); 1827 c.html.push(first); 1828 c.html[i-1].attach(elem, i-1); 1829 } 1830 } 1831 }, 1832 c.cc*1000 1833 ); 1834 } 1835 break; 1836 } 1837 c.html[i].attach(elem, i); // this calls unblock when done 1838 } 1839 if (!noraise) 1840 { log_event(c_id, c.a.join(), LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&64 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&64 ? LOG_EVENT_SEND_NOW : 0), 1, c.rid); 1841 } 1842 } 1843 catch (e) 1844 { ns.error('Couldn\'t insert HTML to container '+c_id, e.message, e); 1845 } 1846 c.inited |= 1; 1847 } 1848 } 1849 if (((tp) & 8)) 1850 { ns.add_e 1851 ( elem, 1852 'onclick', 1853 () => 1854 { log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&8192 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8192 ? LOG_EVENT_SEND_NOW : 0), 8); 1855 }, 1856 c_id 1857 ); 1858 c.inited |= 8; 1859 } 1860 if (((tp) & 16)) 1861 { ns.add_e
1862 ( elem, 1863 'onclick', 1864 () => 1865 { click_event_page(c_id); 1866 }, 1867 c_id 1868 ); 1869 c.inited |= 16; 1870 } 1871 if (((tp) & 32)) 1872 { ns.add_e 1873 ( elem, 1874 'onmouseover', 1875 () => 1876 { log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&2 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2 ? LOG_EVENT_SEND_NOW : 0), 32); 1877 }, 1878 c_id 1879 ); 1880 c.inited |= 32; 1881 } 1882 if (((tp) & 2048)) 1883 { log_event(c_id, 1, LOG_EVENT_VALUE_SUM|(event_send_with_delay&32768 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&32768 ? LOG_EVENT_SEND_NOW : 0), 2048); 1884 c.inited |= 2048; 1885 } 1886 if (((tp) & 454)) 1887 { var is_input = typeof(elem.value) == 'string'; 1888 if (is_input && elem.type!='hidden') 1889 { let grab = infrequent_func 1890 ( event => 1891 { var value = ns.apply_ghm(elem.value, c.ghm); 1892 // `c.vl` is the last value sent for this container, as it is on the 1893 // grab_html() path. Without it the unconditional read below would 1894 // re-send the same value on every init_containers pass, and those 1895 // run on every DOM mutation. 1896 if (value && value!=c.vl && (!is_radio || elem.checked)) 1897 { c.vl = value; 1898 // The html grab types are in this mask as well as the text-input ones, 1899 // because this branch runs for them too: it is entered for any of 1900 // 'grab static html / grab html / grab volatile html / text input / 1901 // text input search' whose element turns out to be an input. 1902 // Without them the mask came out 0 for an html-typed container - the 1903 // value travelled but was claimed by no container type, and was stored 1904 // empty. The event was logged, so the UI showed a row with "(empty)" 1905 // rather than nothing, which reads as "we grabbed nothing" instead of 1906 // "this container is the wrong type". 1907 // It matters because the panel cannot always say "this is an input": 1908 // a selector typed by hand saves as 'grab volatile html', and that is a 1909 // perfectly reasonable thing for a user to do. Reading an input's value 1910 // whatever page-element variant it was saved under is what they mean 1911 // either way - grab_html() below would only ever read its (empty) text. 1912 // Same mask grab_html() uses, so the server sees the shape it already 1913 // handles for a page grab. 1914 log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event.type=='blur' ? (event_send_with_delay&4096 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&4096 ? LOG_EVENT_SEND_NOW : 0) : (event_send_with_delay&2048 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2048 ? LOG_EVENT_SEND_NOW : 0)), c.tp & 49606); 1915 } 1916 }, 1917 1000 1918 ); 1919 function add_events(elem) 1920 { ns.add_e(elem, 'onkeyup', grab, c_id); 1921 ns.add_e(elem, 'onmouseup', grab, c_id); 1922 ns.add_e(elem, 'onblur', grab, c_id); 1923 ns.add_e(elem, 'onchange', grab, c_id); 1924 // Read what is ALREADY in the field, not only what the visitor types into 1925 // it. A checkout that fills in a returning customer's email, a logged-in 1926 // profile form, a browser autofill: in all of them not one of the events 1927 // above ever fires, and the container stayed empty forever. The 1928 // grab_html() path (everything that is not an input) has always read
1929 // immediately like this - inputs were the odd one out. 1930 // A field that is empty now and filled later is still caught: these 1931 // container types are deliberately never marked `inited` (see below), so 1932 // init_container runs again on every DOM change, and the `c.vl` guard 1933 // above is what keeps that from re-sending. 1934 // With a trigger configured this line is unchanged in effect: the branch 1935 // at the top of init_container returns early unless this pass IS the 1936 // trigger click, so we only get here when the trigger has just fired. 1937 grab.call(elem, {type: 'blur'}); 1938 } 1939 var is_radio = elem.type == 'radio'; 1940 if (!is_radio) 1941 { add_events(elem); 1942 } 1943 else 1944 { var elems = ns.id(c.hi, true); 1945 for (var i in elems) 1946 { if (elems[i]?.type == 'radio') 1947 { add_events(elems[i]); 1948 } 1949 } 1950 } 1951 //c.inited |= c.tp & 49606; 1952 } 1953 else if (!c.inited_event && ((tp) & 384)) 1954 { c.inited_event = 1; 1955 c.ha = html_attr; 1956 c.onflush = function() {grab_html(c_id)}; 1957 } 1958 if (grab_html(c_id) !== false) 1959 { c.inited |= c.tp & 49216; // 'grab html' and 'grab volatile html' (and 'text input' and 'text input search' which are synonyms) must never be initialized, so grab continues 1960 } 1961 } 1962 if (((tp) & 512) && cur_visit_id) // cur_visit_id is false before the user data are ready 1963 { var v = ns.subst_user_data(c.udf); 1964 if (html_attr=='<' || html_attr=='>') 1965 { var node = html_attr=='<' ? elem.previousSibling : elem.nextSibling; 1966 if (node && (node.nodeType==XML_TEXT_NODE || node.nodeType==XML_CDATA_SECTION_NODE)) 1967 { node.data = v; 1968 c.inited |= 512; 1969 } 1970 } 1971 else if (html_attr) 1972 { elem.setAttribute(html_attr, v); 1973 c.inited |= 512; 1974 } 1975 else if (typeof(elem.value) == 'string') 1976 { elem.value = v; 1977 c.inited |= 512; 1978 } 1979 else 1980 { elem.textContent = v; 1981 c.inited |= 512; 1982 } 1983 } 1984 } 1985 if (((tp) & 8192)) 1986 { var value = ns.apply_ghm(ns.get_param(html_id), c.ghm); 1987 if (value != '') 1988 { log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&4 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&4 ? LOG_EVENT_SEND_NOW : 0), tp & 8192); 1989 c.inited |= 8192; 1990 } 1991 } 1992 if (((tp) & 65536)) 1993 { var value = ns.apply_ghm(ns.get_page_grab_val._meta(html_id), c.ghm); 1994 if (value) log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&32 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&32 ? LOG_EVENT_SEND_NOW : 0), tp & 65536); 1995 c.inited |= tp & 65536; 1996 } 1997 if (((tp) & 4096)) 1998 { var value = ns.apply_ghm(ns.get_cookie(html_id), c.ghm); 1999 want_monitor_containers = !value; 2000 if (value) 2001 { log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 4096); 2002 c.inited |= tp & 4096; 2003 } 2004 } 2005 if (((tp) & 537001984)) 2006 { if (window.localStorage) 2007 { var value = ns.apply_ghm(obj_follow_path(((tp) & 536870912) ? localStorage._6senseCompanyDetails : localStorage, html_id), c.ghm); 2008 want_monitor_containers = !value; 2009 if (value) 2010 { log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 537001984); 2011 c.inited |= tp & 537001984; 2012 } 2013 } 2014 } 2015 if (((tp) & 262144)) 2016 { if ('sessionStorage' in window) 2017 { var value = ns.apply_ghm(obj_follow_path(sessionStorage, html_id), c.ghm); 2018 want_monitor_containers = !value; 2019 if (value) 2020 { log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 262144); 2021 c.inited |= tp & 262144; 2022 } 2023 } 2024 } 2025 if (((tp) & 1048576)) 2026 { var value = obj_follow_path(window, html_id, undefined, !c.wv ? null : grab); 2027 //want_monitor_containers = value == null; 2028 want_monitor_containers = true; // don't stop monitoring variables 2029 grab(value); 2030 function grab(value) 2031 { value = ns.apply_ghm(value, c.ghm); 2032 if (value)
2033 { log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&131072 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&131072 ? LOG_EVENT_SEND_NOW : 0), tp & 1048576); 2034 // don't stop monitoring variables 2035 //c.inited |= tp & 1048576; 2036 } 2037 } 2038 } 2039 if (((tp) & 1024)) 2040 { c.inited |= tp & 1024; 2041 } 2042 if(!c.vl) c.vl = value; 2043 if (!(c.tp & ~c.inited)) 2044 { c.found_elems = null; // not needed, so free memory 2045 } 2046 2047 return want_monitor_containers; 2048 } 2049 2050 function obj_follow_path(obj, path, set_value, onchange) 2051 { let orig_obj = obj; 2052 let last_obj = null; 2053 let last_w = null; 2054 let not_found = false; 2055 let uninstall_watchers = []; 2056 path += ''; 2057 RE_OBJ_FOLLOW_PATH.lastIndex = 0; 2058 let match; 2059 while ((match = RE_OBJ_FOLLOW_PATH.exec(path))) 2060 { let [a, dot_w, index_w] = match; 2061 if (typeof(obj) == 'string') 2062 { try 2063 { obj = JSON.parse(obj); 2064 } 2065 catch (e) 2066 { obj = undefined; 2067 } 2068 } 2069 if (typeof(obj)!='object' || !obj) 2070 { if (set_value===undefined || !last_obj || last_w==null) // last_w==null means not matched wildcard (or initial value) 2071 { obj = undefined; 2072 not_found = true; 2073 break; 2074 } 2075 obj = last_obj[last_w] = {}; 2076 } 2077 // assume: typeof(obj) == 'object' 2078 if (index_w) 2079 { let c = index_w.charAt(0); 2080 try 2081 { if (c == '"') 2082 { dot_w = JSON.parse(index_w)+''; 2083 } 2084 else if (c == "'") 2085 { dot_w = eval(index_w)+''; 2086 } 2087 else 2088 { dot_w = parseFloat(index_w); 2089 if (isNaN(dot_w)) 2090 { throw new Error; 2091 } 2092 dot_w += ''; 2093 } 2094 } 2095 catch (e) 2096 { throw new Error('Invalid expression: '+path); 2097 } 2098 } 2099 else 2100 { dot_w = !dot_w ? '' : dot_w.trim(); 2101 if (dot_w.indexOf('*')!=-1 || dot_w.indexOf('?')!=-1) 2102 { let re = new RegExp 2103 ( '^'+ 2104 dot_w.replace 2105 ( /[^\w\s]/g, 2106 c => c=='*' ? '[\\S\\s]*?' : c=='?' ? '[\\S\\s]' : '\\'+c 2107 )+ 2108 '$' 2109 ); 2110 let found = false; 2111 for (dot_w in obj) 2112 { if (obj.hasOwnProperty(dot_w) && re.test(dot_w)) 2113 { found = true; 2114 break; 2115 } 2116 } 2117 if (!found) 2118 { dot_w = null; // not matched wildcard 2119 } 2120 } 2121 } 2122 if (last_obj) 2123 { install_watcher(last_obj, last_w, false); 2124 } 2125 last_obj = obj; 2126 last_w = dot_w; // null for not matched wildcard 2127 if (dot_w == null) 2128 { obj = undefined; 2129 } 2130 else 2131 { obj = obj[dot_w]; 2132 if (obj===undefined && last_obj==window) 2133 { // consts cannot be accessed through window object, so try eval 2134 try 2135 { obj = eval(dot_w); 2136 } 2137 catch (e) 2138 { 2139 } 2140 } 2141 } 2142 } 2143 if (!not_found) 2144 { // set 2145 if (set_value!==undefined && !not_found) 2146 { if (last_obj == null) 2147 { throw new Error("No matching variable"); 2148 } 2149 obj = last_obj[last_w] = set_value; 2150 } 2151 // onchange 2152 install_watcher(last_obj, last_w, true); 2153 } 2154 else 2155 { install_watcher(last_obj, last_w, false); 2156 } 2157 function install_watcher(last_obj, last_w, is_final) 2158 { if (onchange && last_obj && Object.defineProperty && Object.getOwnPropertyDescriptor) 2159 { let desc = Object.getOwnPropertyDescriptor(last_obj, last_w) || {writable: true, configurable: true, enumerable: false}; 2160 if (desc.configurable && !(desc.get?.(new Mark1) instanceof Mark2)) // if watcher not yet installed 2161 { Object.defineProperty 2162 ( last_obj, 2163 last_w, 2164 { configurable: true, 2165 enumerable: true, 2166 get(mark) 2167 { if (mark instanceof Mark1) 2168 { return new Mark2; 2169 } 2170 else if ('get' in desc) 2171 { return desc.get?.(); 2172 } 2173 else 2174 { return desc.value; 2175 } 2176 }, 2177 set(new_value) 2178 { if ('set' in desc) 2179 { desc.set?.(new_value); 2180 } 2181 else 2182 { desc.value = new_value; 2183 } 2184 new_value = desc.get ? desc.get() : desc.value; 2185 if (is_final) 2186 { onchange(new_value); 2187 } 2188 else if (typeof(new_value)=='object' && new_value) 2189 { while (uninstall_watchers.length) 2190 { uninstall_watchers.shift()(); 2191 } 2192 obj = obj_follow_path(orig_obj, path, undefined, onchange); 2193 if (obj !== undefined) 2194 { onchange(obj); 2195 } 2196 } 2197 } 2198 } 2199 ); 2200 } 2201 uninstall_watchers[uninstall_watchers.length] = function() 2202 { Object.defineProperty(last_obj, last_w, desc); 2203 }; 2204 } 2205 } 2206 // get 2207 let type = typeof(obj); 2208 if (type == 'boolean') 2209 { obj = obj ? 1 : 0; 2210 } 2211 else if (type!='number' && type!='string') 2212 { obj = undefined; 2213 } 2214 return obj; 2215 } 2216 2217 function try_create_container(c_id) 2218 { if (default_containers.hasOwnProperty(c_id)) 2219 { return default_containers[c_id]; 2220 } 2221 var elem = null; 2222 if (document.body) 2223 { if (c_id==-19 || c_id==-20) 2224 { elem = ns.new_elem 2225 ( '<div style="text-align:center"><div style="display:inline-block; text-align:left; text-align:start"></div></div>', 2226 c_id==-20 ? document.body : {parentNode: document.body, nextSibling: document.body.firstChild} 2227 ).firstChild; 2228 default_containers[c_id] = elem; 2229 } 2230 } 2231 return elem ? [elem] : null; 2232 } 2233 2234 function grab_html(c_id) 2235 { let c = ns.containers[c_id]; 2236 let ha = c.ha; 2237 let obj = ns.id(c.hi); 2238 if (obj) 2239 { let s = ''; 2240 if (ha=='<' || ha=='>') 2241 { let node = ha=='<' ? obj.previousSibling : obj.nextSibling; 2242 if (node && (node.nodeType==XML_TEXT_NODE || node.nodeType==XML_CDATA_SECTION_NODE)) 2243 { s = node.data; 2244 } 2245 } 2246 else 2247 { s = ha ? obj.getAttribute(ha) : obj.nodeName=='META' ? obj.content : ns.inner_text(obj); 2248 } 2249 s = ns.apply_ghm(s, c.ghm); 2250 if (s) 2251 { ns.containers[c_id].vl = s;
2252 log_event(c_id, s, ((c.tp) & 256) ? (event_send_with_delay&65536 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&65536 ? LOG_EVENT_SEND_NOW : 0) : LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&128 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&128 ? LOG_EVENT_SEND_NOW : 0), c.tp & 49600); 2253 return true; // true: grabbed 2254 } 2255 } 2256 return false; // false: not grabbed 2257 } 2258 2259 function check_for_actions() 2260 { log_event(0, '', LOG_EVENT_SEND_NOW); 2261 } 2262 2263 function log_event(c_id, value, mode, container_mask, rid, onload, onerror) // if rid is specified then report about occupied container, otherwise initiate event 2264 { container_mask |= 0; 2265 rid |= 0; 2266 if (c_id && !ns.containers.hasOwnProperty(c_id)) 2267 { if (conts_map.hasOwnProperty(c_id)) 2268 { // legacy format: former version was like this: log_event(html_id, ...) 2269 c_id = conts_map[c_id][0]; // take first 2270 } 2271 else if (!(c_id < 0)) 2272 { // no such container 2273 var errmsg = 'No such container: '+c_id; 2274 ns.error(errmsg); 2275 (onerror || onload)?.(errmsg); 2276 return; 2277 } 2278 } 2279 else if ((mode & LOG_EVENT_SEND_WITH_DELAY) && c_id && ns.containers[c_id].qs) 2280 { // raise mode to LOG_EVENT_SEND_NOW 2281 mode &= ~LOG_EVENT_SEND_WITH_DELAY; 2282 mode |= LOG_EVENT_SEND_NOW; 2283 } 2284 if (c_id > 0) 2285 { let value_hash = ns.strhash(value); 2286 if (value_hash === html_input_state[c_id]) 2287 { return; // value isn't changed 2288 } 2289 html_input_state[c_id] = value_hash; 2290 } 2291 flush_events(mode & LOG_EVENT_SEND_IMMED); 2292 if (c_id) 2293 { let rid_e = rid || (cur_visit_id ? 'e'+cur_visit_id : 'e'); 2294 tracker_events.add(c_id, value, mode, container_mask, rid_e); 2295 } 2296 if (mode & (LOG_EVENT_SEND_WITH_DELAY|LOG_EVENT_SEND_NOW|LOG_EVENT_SEND_IMMED)) 2297 { if (c_id && events_sent_times++ >= 100) 2298 { return; 2299 } 2300 send_request 2301 ( mode, 2302 0, 2303 0, 2304 mode & LOG_EVENT_SEND_NOW ? 0 : null, 2305 ns.pair_funcs 2306 ( onload, 2307 error => {tracker_events.cleanup(error)} // if !onerror, in case of error cleanup(error) will be called 2308 ), 2309 onerror 2310 ); 2311 } 2312 else 2313 { setTimeout 2314 ( () => {tracker_events.cleanup('Error')}, 2315 5000 ); 2316 onload?.(); 2317 } 2318 } 2319 2320 function events_to_str(obj) 2321 { var n_html_ids = []; 2322 for (var n_html_id in obj) 2323 { if (obj.hasOwnProperty(n_html_id)) 2324 { n_html_ids.push(n_html_id); 2325 } 2326 } 2327 n_html_ids.sort(); 2328 var str='', delim=''; 2329 // event format: "{$session_time}_{$record_id}_{$container_id}_{$container_mask}_{$value}" 2330 function escape_value(str) // i want to use '&' as field delimiter, so it must not appear in value 2331 { return (str+'').replace 2332 ( /[\\&]/g, 2333 m => m=='\\' ? '\\\\' : '\\a' 2334 ); 2335 } 2336 var c = ns.containers; 2337 for (var i=0; i<n_html_ids.length; i++) 2338 { // m[1]: time offset since session start; m[2]: container_mask; m[3]: rid (record id); m[4]: container_id 2339 var m = n_html_ids[i].match(/^(\d+)_(\d+)_(e?-?\d*)_(-?\d+)/); 2340 if (m) 2341 { if 2342 ( ( c[m[4]] || (m[4]>=-43 && m[4]<=-1) 2343 ) 2344 // filter empty "text input", "text input search", "grab static html", "grab html", "grab volatile html", "grab cookie", "grab url" 2345 && 2346 !(c[m[4]] && ((c[m[4]].tp) & 537342406) && !(obj[n_html_ids[i]]+'').match(/\S/)) 2347 ) 2348 { /* {$session_time}_{$record_id}_{$container_id}_{$container_mask}_{$value} */ 2349 var cid = m[4]; 2350 str += delim+m[1]+'_'+m[3]+'_'+cid+'_'+m[2]+'_'+escape_value(obj[n_html_ids[i]] || ''); 2351 delim = '&'; 2352 } 2353 } 2354 } 2355 return str; 2356 } 2357 2358 function get_protocol() 2359 { //return (location.protocol=='https:' || ns.get_location().protocol=='https:' ? 'https:' : 'http:'); 2360 return 'https:'; 2361 } 2362 2363 function send_request(mode, is_load, unused, alt_timeout, onload, onerror, checker_func) 2364 { log_new_tealium_audiences(); 2365 if (is_load) 2366 { send_request_onload = ns.pair_funcs(send_request_onload, exec_onevent); 2367 } 2368 send_request_onload = ns.pair_funcs(send_request_onload, onload); 2369 send_request_onerror = ns.pair_funcs(send_request_onerror, onerror || onload); 2370 if (is_load) 2371 { first_log_visit_sent = true; 2372 } 2373 if (mode & LOG_EVENT_SEND_NOW) 2374 { clearTimeout(send_timer); 2375 send_timer = null; 2376 } 2377 if (mode&(LOG_EVENT_SEND_IMMED|LOG_EVENT_SEND_NOW|LOG_EVENT_SEND_WITH_DELAY) && !is_ready && !is_load) 2378 { mode = LOG_EVENT_SEND_WITH_DELAY; 2379 /* And drop the immediate timeout that came with the mode we just dropped. 2380 `log_event` picks `alt_timeout` from the ORIGINAL mode -- 0 for SEND_NOW -- 2381 so without this the deferral below re-fires `do_send()` 0ms later and the 2382 downgrade buys nothing at all. */ 2383 alt_timeout = null; 2384 } 2385 /* `first_log_visit_sent` means the page view was DISPATCHED, not that it came back. 2386 `is_ready` is the one that means answered (see its declaration). Bet
2386ween the two 2387 the page has no `cur_visit_id`, and st_log_visit_* refuses an events-only request 2388 for a session it holds no visit row for -- "Events not logged: this session has no 2389 page view on record". Sending on the weaker flag is what put ~15,000 events a day 2390 on guns.com (6864) into that error: its account has `grab cookie` in 2391 `event_send_now`, so container 48 fires a request of its own the moment the cookie 2392 is read, which is while the page view is still in flight. 2393 2394 Deferring instead costs at most `event_load_timeout`, and the timer path calls 2395 `do_send()` directly, so a page view that never answers still gets its events out 2396 rather than holding them for ever. Same reason the 'user data' containers and the 2397 focus event already wait for `cur_visit_id`. */ 2398 if ((is_load || is_ready) && (!send_timer || mode&LOG_EVENT_SEND_IMMED)) 2399 { clearTimeout(send_timer); 2400 send_timer = null; 2401 function do_send() 2402 { if (is_load) 2403 { exec_commands(); 2404 } 2405 var props = get_request_props(0, is_load); 2406 if (!props || !is_load && !props.ev) 2407 { return; 2408 } 2409 var params = ''; 2410 var delim = '?'; 2411 for (var i in props) 2412 { if (props.hasOwnProperty(i)) 2413 { params += delim+i+'='+encodeURIComponent(props[i]); 2414 delim = '&'; 2415 } 2416 } 2417 let log_visit_url = !NEW_ELEM_NDEBUG ? "\/\/counter-backend.personyze.com\/stat-track-log-visit.js.php" : '\/\/qacounter.personyze.com\/stat-track-log-visit.js.php'; 2418 if (Date.now()-before_unload_time > 4000) 2419 { ns.load_scripts 2420 ( { [get_protocol()+log_visit_url+params]: checker_func 2421 }, 2422 () => 2423 { var func = send_request_onload; 2424 send_request_onload = null; 2425 send_request_onerror = null; 2426 func?.(); 2427 }, 2428 error => 2429 { // The response script did not run: blocked by an extension or a CSP, 2430 // offline, or the server answered a non-2xx (a <script> is never 2431 // executed then). Used to be completely silent - the page simply had 2432 // no personalization and no explanation for it. `error` arrives as a 2433 // string from some load_scripts paths and as an Error from others. 2434 var reason = (error && (error.message || ''+error)) || 'blocked, offline, or server error'; 2435 te_errmsg(`tracker request failed, so no personalization on this page - ${reason}`); 2436 var func = send_request_onerror; 2437 send_request_onload = null; 2438 send_request_onerror = null; 2439 func?.(error); 2440 } 2441 ); 2442 } 2443 else 2444 { navigator.sendBeacon(get_protocol()+log_visit_url+params); 2445 } 2446 } 2447 if (mode & LOG_EVENT_SEND_IMMED) 2448 { ns.reg_onready(do_send); 2449 } 2450 else 2451 { /* send_timer must be forgotten the moment the send it stands for happens, because 2452 the guard above reads it as "a delayed send is already pending, this one will 2453 ride along with it". Leaving the fired timer's id in place made that claim 2454 permanent: nothing else clears send_timer except a SEND_NOW event or rollback(), 2455 so after the first delayed send every later one was answered with "already 2456 pending" and silently dropped - no request, no error. 2457 A page that reloads never noticed, because each load starts a fresh 2458 send_timer. An SPA does not reload, and log_navigation() is sent as 2459 'click event page', which an account can configure as send-with-delay (5219 2460 does) - so from its second navigation on, that tab asked the server for 2461 nothing ever again: no page views, and no actions for any page the visitor 2462 went to next. Reload was the only cure, which is exactly how it was reported. */ 2463 send_timer = setTimeout 2464 ( () => 2465 { send_timer = null; 2466 do_send(); 2467 }, 2468 alt_timeout==null ? 6000 : alt_timeout 2469 ); 2470 } 2471 } 2472 } 2473 2474 function get_pardot_visitor_id() 2475 { var v = document.cookie.match(/(?:^|;\s*)visitor_id\d{2,}=([^;]+)/); 2476 return v?.[1] || ''; 2477 } 2478 2479 function get_request_props(is_short, is_load, no_events) 2480 { let s=screen, n=navigator; 2481 let rotated = innerWidth>innerHeight && n.vendor=="Apple Computer, Inc." && (/\biPhone\b|\biPad\b/.test(n.userAgent) || n.maxTouchPoints>1); 2482 // props
2483 let id = make_id(is_load, no_events || !ns.containers); 2484 if (!id) return null; 2485 let events = events_to_str(id.events); 2486 ns.uid = id.id; 2487 let props = 2488 { fr: get_f(), 2489 te: ns.te(), 2490 sr: ns.server_id, 2491 ld: is_load ? 1 : 0, 2492 ur: ns.get_location().href, 2493 rf: id.rf, 2494 id: id.id, 2495 si: id.si, 2496 ss: id.ss, 2497 nw: id.nw, 2498 st: id.st, 2499 ls: id.ls, 2500 rs: id.rs, 2501 sy: id.sy, 2502 bh: id.bh, 2503 hs: id.hs, 2504 dg: id.dg, 2505 ps: id.ps, // must be sorted in descending order 2506 ev: events, 2507 hu: ns.get_cookie('hubspotutk') || '', 2508 ao: document.cookie.match(/(?:^|;)\s*wp\d+="?([^;"]{50,150})/)?.[1] || '', 2509 pr: get_pardot_visitor_id(), 2510 tm: ns.secs(), 2511 pl: n.platform + (n.cpuClass && n.platform.indexOf(n.cpuClass)==-1 ? ' '+n.cpuClass : ''), // important to send platform regardless to is_short 2512 sc: rotated ? s.height+'x'+s.width+'x'+s.colorDepth : s.width+'x'+s.height+'x'+s.colorDepth, 2513 th: ns.hash(document.title), 2514 ck: '?', 2515 aj: '?', 2516 jv: '?', 2517 fl: -1, 2518 sl: -1, 2519 mp: -1, 2520 cn: '?', 2521 vl: -1, 2522 qt: -1 2523 }; 2524 /* Set only under consent mode, and only after grant() -- see `consent_granted`. Added 2525 after the literal rather than in it so that a request from an installation that never
2526 asked for consent mode carries no extra parameter at all. (`cn` above is unrelated: 2527 it is one of the browser-plugin probes.) */ 2528 if (consent_granted) 2529 { props.cs = 1; 2530 } 2531 /* Hosted landing page impression token. Present ONLY on a page our own landing-page 2532 route served — it sets window._S_T_LP to a signed token naming the exact variant 2533 it rendered (see mod_www/pages/lp). Echoing it lets the server write the campaign 2534 log row the server render could not (the fork fix). Strictly guarded on the 2535 global, so every ordinary installation — the whole reason this file is hot — 2536 sends nothing. The server HMAC-verifies it; a forged value is ignored, not 2537 trusted. Only on the page-LOAD request: `!is_short` also let every EVENT ping 2538 re-send the same token (do_send passes is_short=0 regardless), and the server's 2539 duplicate suppression is a read-then-insert that two overlapping pings can race 2540 past. One view, one send. */ 2541 if (is_load && typeof(window._S_T_LP) == 'string' && window._S_T_LP) 2542 { props.lp = window._S_T_LP; 2543 } 2544 /* The same token, arriving the only other way it can: on the URL. A page test's CONTROL 2545 group serves no page of ours — the visitor is 302'd to a page on the customer's own 2546 site — so there is no document of ours to carry a global, and the token rides in 2547 `_pz_lp` instead (mod_landing_page/impression_url.ts). Without this the control arm 2548 books no impressions at all and the whole test reports one arm against silence. 2549 2550 Cheap and quiet for everyone else: an indexOf on a string we already have, and the 2551 parameter is namespaced so nothing else produces it. The value is HMAC-verified 2552 server-side exactly like the global, so a hand-typed one is ignored. 2553 2554 The parameter is then removed — from the address bar (the visitor arrived at the 2555 customer's page and should see the customer's URL, not our plumbing, and a clean URL 2556 is also what they copy, share and bookmark), and from `ur`, the URL this request 2557 RECORDS: the props literal above captured the href with the live token still in it, 2558 which persisted a replayable signed token into reporting data and fragmented the 2559 customer's landing page into one URL row per visitor. Load pings only, same reason 2560 as the global above. */ 2561 if (is_load && !props.lp && location.search.indexOf('_pz_lp=') != -1) 2562 { try 2563 { var _lp_u = new URL(location.href); 2564 var _lp_t = _lp_u.searchParams.get('_pz_lp'); 2565 if (_lp_t) 2566 { props.lp = _lp_t; 2567 _lp_u.searchParams.delete('_pz_lp'); 2568 props.ur = _lp_u.href; 2569 if (window.history && history.replaceState) 2570 { history.replaceState(null, '', _lp_u.href); 2571 } 2572 } 2573 } 2574 catch (e) {} // URL unsupported or a malformed href: measurement is never worth an exception on a customer's page 2575 } 2576 function get_versions() 2577 { var a = { 2578 "QuickTime": { 2579 progID: ["QuickTime.QuickTime", "QuickTimeCheckObject.QuickTimeCheck.1"], 2580 classID: "02BF25D5-8C17-4B23-BC80-D3488ABDDC6B", 2581 getAXVInfo(obj) { 2582 var v = obj?.QuickTimeVersion ? obj.QuickTimeVersion.toString(16) : -1; 2583 return v == -1 ? -1 : v.substring(0,1); 2584 } 2585 }, 2586 "VLC": { 2587 progID: [],//["VideoLAN.VLCPlugin"], popup alert in IE>=7 - do not use! 2588 classID: "" 2589 }, 2590 "Windows Media": { 2591 progID: ["WMPlayer.OCX", "MediaPlayer.MediaPlayer.1"], 2592 classID: "22D6f312-B0F6-11D0-94AB-0080C74C7E95", // WMP6 -> semms to work a lot better, don't know why 2593 getAXVInfo(obj) { 2594 var v = obj?.versionInfo || ""; 2595 v = parseInt(v); 2596 return isNaN(v) ? -1 : v; 2597 } 2598 }, 2599 "Silverlight": { 2600 progID: ["AgControl.AgControl"], 2601 classID: "" 2602 }, 2603 "Flash": { 2604 progID: ["ShockwaveFlash.ShockwaveFlash"], 2605 classID: "D27CDB6E-AE6D-11CF-96B8-444553540000" 2606 } 2607 }; 2608 var f=a["Flash"].progID; 2609 var f0=f[0]; 2610 for(var i=15;i>2;--i) { 2611 f[15-i]=f0+'.'+i; 2612 } 2613 2614 var getInfo = function(name) { 2615 var version = -1; 2616 var getVersionFromPlugin = function(plugin) { 2617 if (!plugin.name) plugin = { name: plugin, description: name }; 2618 var matches = /[\d][\d\.]*/.exec(plugin.name); 2619 if (matches && plugin.name.indexOf("Java") == -1) return matches[0]; 2620 matches = /[\d\.]+/.exec(plugin.description); 2621 return matches ? matches[0] : ""; 2622 }; 2623 if (navigator.plugins?.length) { 2624 for(var i=0;i<navigator.plugins.length;++i) { 2625 try 2626 { var plugin = navigator.plugins[i]; 2627 if (plugin.name.indexOf(name) != -1) { 2628 version = parseInt(getVersionFromPlugin(plugin)); 2629 if (isNaN(version)) version = -1; 2630 break; 2631 } 2632 } 2633 catch (e) 2634 { 2635 } 2636 } 2637 } else { 2638 var getProgIdForAX = function(progID) { 2639 if (!progID) return null; 2640 for (var i=0; i<progID.length; i++) { 2641 try { 2642 var obj = new ActiveXObject(progID[i]); 2643 return [progID[i], obj]; 2644 } 2645 catch(e) {} 2646 } 2647 return null; 2648 }; 2649 var progID_obj = getProgIdForAX(a[name]?.progID); 2650 if (progID_obj) { 2651 if (a[name].getAXVInfo) { 2652 version = a[name].getAXVInfo(progID_obj[1]); 2653 } else { 2654 version = getVersionFromPlugin(progID_obj[0]); 2655 } 2656 } else { 2657 version = -1; 2658 } 2659 } 2660 return version; 2661 }; 2662 var names = ['QuickTime', 'VLC', 'Windows Media', 'Silverlight', 'Flash']; 2663 var res = []; 2664 for(var i=0; i<names.length; ++i) { 2665 res[i] = getInfo(names[i]); 2666 } 2667 return res; 2668 } 2669 function yn(v) 2670 { return v===false ? 'n' : v===true ? 'y' : '?'; 2671 } 2672 if (!is_short) { 2673 var vv = get_versions(); 2674 var props_ex = { 2675 ck: yn(n.cookieEnabled), 2676 aj: window.XMLHttpRequest ? 'y' : 'n', 2677 jv: yn(n.javaEnabled ? n.javaEnabled() : false), 2678 fl: vv[4], 2679 sl: vv[3], 2680 mp: vv[2], 2681 cn: yn(!!document.createElement('canvas').getContext), 2682 vl: vv[1], 2683 qt: vv[0] 2684 }; 2685 for (var i in props_ex) 2686 { if (props_ex.hasOwnProperty(i)) 2687 { props[i] = props_ex[i]; 2688 } 2689 } 2690 } 2691 return props; 2692 } 2693 2694 function make_id(is_load, no_events) 2695 { function gen_uid() 2696 { var uid; 2697 do 2698 { uid = Math.floor(Math.random()*0xFFFFFFFF) & 0xFFFFFFFF; 2699 } 2700 while (uid>=0 && uid<500000000); 2701 return uid; 2702 } 2703 var secs = ns.secs(); 2704 var uid_obj = ns.cookie_obj('stat_track_u_id'); 2705 var sid_obj = ns.cookie_obj('_stat_track_s_id'); 2706 var override = get_personyze_override().u; 2707 var override_uid = false;
2708 if (override) 2709 { override_uid = override.uid && override.uid!=uid_obj.uid; 2710 for (var prop in override) 2711 { if (override.hasOwnProperty(prop)) 2712 { uid_obj[prop] = override[prop]; 2713 if (SESSION_COOKIE_PROPS.indexOf('_'+prop) != -1) 2714 { sid_obj['_'+prop] = override[prop]; 2715 } 2716 } 2717 } 2718 } 2719 // user 2720 var uid = uid_obj.uid - 0; 2721 var st = uid_obj.st || 1; 2722 var sy = uid_obj.sy || ''; 2723 var uls = uid_obj.ls; 2724 var off = uid_obj.off || ''; 2725 var dg = window['_S_T_PROXY_BUFFER_KEY'] || uid_obj.dg || ''; 2726 var noacts = uid_obj.noacts; 2727 var nocmps = uid_obj.nocmps; // campaign-level frequency-cap suppressions (see camp_cap_event) 2728 var f = uid_obj.f || ''; 2729 var hs = uid_obj.hs-0 || 0; 2730 // session 2731 var sid = sid_obj._sid; 2732 var ls = sid_obj._ls; 2733 var bh = sid_obj._bh || ''; 2734 // 2735 var nw = 3; 2736 if (!uid) 2737 { // new user and new session 2738 uid = gen_uid(); 2739 sid = secs; 2740 ls = secs; 2741 } 2742 else if (override) 2743 { nw = override_uid ? 3 : 1; 2744 } 2745 else if (((sid_obj._la-0)+5400>secs) && (sid-0)+5400>secs) 2746 { // old session (maybe of a new user) 2747 nw = sid_obj._inew*2; // inew can be 0 (not new user) or 1 (is new user) 2748 } 2749 else 2750 { // new session of (already) old user 2751 sid = secs; 2752 nw = 1; 2753 st++; 2754 ls = uls; 2755 } 2756 // 2757 var new_sessions_arr = []; 2758 if (nw & 1) 2759 { if (!is_load) 2760 { return null; // request that came before page load (maybe false request that is noticed in FF) or that came after user erased his cookies but before he refreshed the page 2761 } 2762 // past sessions 2763 new_sessions_arr = ns.get_ps(sid); 2764 } 2765 ns.cookie_obj('stat_track_u_id', {uid, f, st, sy, ls, off, noacts, nocmps, dg, hs}, 1); 2766 var sid_obj_no_events = {}; 2767 for (var i=0; i<SESSION_COOKIE_PROPS.length; i++) 2768 { var prop = SESSION_COOKIE_PROPS[i]; 2769 sid_obj_no_events[prop] = sid_obj[prop] || ''; 2770 delete sid_obj[prop]; 2771 } 2772 var is_using_session_storage = !!window.sessionStorage; 2773 var events = is_using_session_storage ? tracker_events.get() : sid_obj; 2774 sid_obj = nw&1 ? {_sid: sid, _inew: nw>>1, _ls: ls, _te: sid_obj_no_events._te, _te_ip: sid_obj_no_events._te_ip, _tev: sid_obj_no_events._tev} : sid_obj_no_events; 2775 // put events back, so they stay in cookie 2776 for (var prop in events) 2777 { if (events.hasOwnProperty(prop)) 2778 { if (!is_using_session_storage) 2779 { sid_obj[prop] = events[prop]; 2780 } 2781 if (!no_events) 2782 { if (tracker_events.sent_events[prop]) 2783 { delete events[prop]; 2784 } 2785 else 2786 { tracker_events.sent_events[prop] = Date.now(); 2787 } 2788 } 2789 } 2790 } 2791 if (no_events) 2792 { events = {}; 2793 } 2794 // 2795 sid_obj._la = secs; 2796 var lurl = sid_obj._lurl; 2797 var lrfr = sid_obj._lrfr; 2798 var is_refresh = 0; 2799 if (is_load && !override) 2800 { var curl = ns.hash(ns.get_location().href); 2801 var crfr = ns.hash(ns.get_referrer()); 2802 if (lurl==curl && lrfr==crfr) 2803 { is_refresh = 1; 2804 } 2805 sid_obj._lurl = curl; 2806 sid_obj._lrfr = crfr; 2807 } 2808 ns.cookie_obj('_stat_track_s_id', sid_obj, 1); 2809 if (is_using_session_storage && !no_events) 2810 { tracker_events.set(events); 2811 } 2812 if (nw & 1) 2813 { sy = new SessCnt('sy').set(0, 1); 2814 new SessCnt('noacts').set(0, 1); 2815 new SessCnt('nocmps').set(0, 1); 2816 } 2817 return { 2818 id: uid, // user_id 2819 ss: sid, // session_id 2820 si: sid_obj._si|0, 2821 nw, // is_new: is_new&1 means new session, is_new&2 means new user 2822 st, // session_counter 2823 ls, // last_session_time 2824 rs: is_refresh, // is_refresh 2825 sy, // sticky conditions 2826 rf: ns.get_referrer(), // referrer 2827 bh, // bh 2828 hs, // hs 2829 dg, // dg 2830 events, // events 2831 ps: new_sessions_arr.join(',') // past sessions 2832 }; 2833 } 2834 2835 function install_focus() 2836 { start_time = Date.now(); // presicion of 1 sec is not enough; 1.9+1.9+1.9 sec != 3 sec 2837 let curr = start_time; 2838 let focused = true; 2839 let t_on_p_total = 0; // time on page total (sec) 2840 let t_on_p_focused = 0; // time on page in focus (sec) 2841 let focus_last_call = 0; 2842 function onfocus() 2843 { if (!focused) 2844 { focused = true; 2845 curr = Date.now(); 2846 } 2847 } 2848 function onblur() 2849 { if (focused) 2850 { focused = false;
2851 let time = Date.now(); 2852 t_on_p_focused += time - curr; 2853 t_on_p_total = time - start_time; 2854 } 2855 } 2856 function raise_event_focus(immed) 2857 { let secs = ns.secs(); 2858 if (cur_visit_id && (immed || focus_last_call < secs-1)) 2859 { focus_last_call = secs; 2860 log_event(-2, Math.floor(t_on_p_focused/1000)+'/'+Math.floor(t_on_p_total/1000), LOG_EVENT_VALUE_OVERRIDE|(!immed ? (event_send_with_delay&16 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&16 ? LOG_EVENT_SEND_NOW : 0) : send_after_unload ? LOG_EVENT_SEND_IMMED : 0), 0, cur_visit_id); 2861 } 2862 } 2863 function do_flush_events(immed) 2864 { let time = Date.now(); 2865 t_on_p_total = time - start_time; 2866 if (focused) 2867 { t_on_p_focused += time - curr; 2868 curr = time; 2869 } 2870 flush_events = () => {}; 2871 raise_event_focus(immed); 2872 flush_events = do_flush_events; 2873 } 2874 ns.add_e(window, 'onfocus', onfocus, 'install focus'); 2875 ns.add_e(window, 'onblur', onblur, 'install focus'); 2876 ns.add_e 2877 ( window, 2878 'onbeforeunload', 2879 () => 2880 { before_unload_time = Date.now(); 2881 do_flush_events(true); 2882 }, 2883 'install focus' 2884 ); 2885 flush_events = do_flush_events; 2886 } 2887 2888 function set_conts(containers) 2889 { // set 2890 ns.containers = {}; 2891 conts_map = {}; 2892 for (var c_id in containers) 2893 { if (containers.hasOwnProperty(c_id)) 2894 { if ('hi' in containers[c_id]) 2895 { ns.containers[c_id] = containers[c_id]; 2896 } 2897 else // legacy format 2898 { containers[c_id].hi = c_id; 2899 ns.containers[containers[c_id].id] = containers[c_id]; 2900 } 2901 var html_id = containers[c_id].hi; 2902 if (!conts_map[html_id]) 2903 { conts_map[html_id] = [c_id]; 2904 } 2905 else 2906 { conts_map[html_id].push(c_id); 2907 } 2908 } 2909 } 2910 ns.reg_onready 2911 ( () => 2912 { init_activity_events(); 2913 init_containers(); 2914 } 2915 ); 2916 } 2917 2918 function set_domains(domains) 2919 { if (!ns.domains) 2920 { if (domains == '') 2921 { domains = ns.get_location().host; 2922 if (domains.substr(0,4) == 'www.') domains = domains.substring(4); 2923 } 2924 domains = domains.split(/\s+/); 2925 var ds = {}; 2926 for (var i=domains.length-1; i>=0; i--) 2927 { ds[domains[i]] = 1; 2928 } 2929 ns.domains = ds; 2930 } 2931 } 2932 2933 function set_user_data(user_data) 2934 { for (var i in user_data) 2935 { if (user_data.hasOwnProperty(i)) 2936 { ns.user_data[i] = user_data[i]; 2937 } 2938 } 2939 } 2940 2941 function nth_word(str, expr) 2942 { var arr=str.split(/\s+/), arr2=[], arr3=[]; 2943 expr = expr.replace(/[^\d\+\-\*\/Nn]/g, '').replace(/(\d)[Nn]/g, '$1*n').replace(/[Nn](\d)/g, 'n*$1').substr(0, 64); 2944 try 2945 { for (var n=0; n<arr.length*2; n++) 2946 { var res = eval(expr.replace(/[Nn]/g, n)); 2947 if (res>0 && res<=arr.length) arr2[res-1] = true; 2948 } 2949 for (n=0; n<arr.length; n++) if (arr2[n]) arr3.push(arr[n]); 2950 str = arr3.join(' '); 2951 } 2952 catch (e) 2953 { 2954 } 2955 return str; 2956 } 2957 2958 function get_f() 2959 { var f = ns.cookie_obj('stat_track_u_id').f; 2960 return f && parseInt(((','+f).match(new RegExp(','+ns.server_id+':(\\d+)')) || [])[1] || f.indexOf(':')==-1 && f) || 0; 2961 } 2962 2963 /* Whether the factor in the cookie is one that not every app host serves yet, so the js urls built from it 2964 must carry the 'b' suffix. Stored next to the factor rather than derived, because the very first script 2965 url of a page view is built before any log_visit answer for that view has come back. 2966 */ 2967 function get_fb() 2968 { var f = ns.cookie_obj('stat_track_u_id').f; 2969 return !!(f && (','+f).match(new RegExp(','+ns.server_id+':\\d+b'))); 2970 } 2971 2972 function set_f(f, dirty, and_load_index) 2973 { var old_f = get_f(); 2974 var obj = {}; 2975 obj[ns.server_id] = {remain_sessions: f, dirty: dirty ? 1 : 0}; 2976 new SessCnt('f').add(obj, 1); 2977 // Only a rising factor reloads index.js, never a change of the dirty flag alone: a flag flip means a 2978 // rebuild has just started somewhere, and the build we are already running is still a perfectly good 2979 // one. Reloading on it would make every publish stampede every open page. 2980 if (and_load_index && old_f && old_f<f && get_f()==f) // check that new factor was applied. otherwise eternal loop of reloading 'index.js' can be set 2981 { var obj = {}; 2982 obj[ns.get_js_url('index')] = null; 2983 ns.load_scripts(obj); 2984 } 2985 } 2986 2987 class SessCnt // sessions countdown 2988 { constructor(cookie_prop) 2989 { this.cookie_prop = cookie_prop; 2990 } 2991 2992 get() 2993 { var str = ns.cookie_obj('stat_track_u_id')[this.cookie_prop] || ''; 2994 var obj = {}; 2995 var now_min = Math.floor(ns.secs()/60); 2996 str.replace 2997 ( /(\d+):([\+\-]?)(t?)(\d+)(h?)(b?)/g, 2998 ($0, $1, $2, $3, $4, $5, $6) => 2999 { if ($3) 3000 { /* Absolute-expiry entry: the value is an expiry stamp in minutes since epoch 3001 (see disable_action). Expired entries are dropped right here, at read time, 3002 so a suppression can end mid-session — unlike sessions/hours counters, 3003 which are only re-examined when a new session starts. 3004 The 't' sits between the sign and the digits so that the pre-'t' tracker's 3005 regex cannot match the entry at all: a cached old build skips it (the 3006 suppression fails open) instead of reading the stamp as a sessions count. 3007 */ 3008 if (now_min < parseInt($4)) 3009 { obj[$1] = {till_min: parseInt($4), match_again: $2=='+'}; 3010 } 3011 else if ($2 != '+')
3012 { obj[$1] = {remain_sessions: -1, match_again: false}; // ended like a non-match_again counter 3013 } 3014 } 3015 else 3016 { obj[$1] = {remain_sessions: parseInt($2+$4), match_again: $2=='+', hours: $5?1:0, dirty: $6?1:0} 3017 } 3018 } 3019 ); 3020 return obj; 3021 } 3022 3023 set(obj, dec) // dec - decrement all counters (practical when starting a new session) 3024 { if (!obj) obj = this.get(); 3025 var ls = ns.cookie_obj('stat_track_u_id').ls || 0; 3026 var passed_hr = ls ? (ns.secs() - ls)/60/60 : 0; 3027 var str = ''; 3028 var ended; 3029 for (var i in obj) 3030 { if (parseInt(i) == i) 3031 { var oi = obj[i]; 3032 if (oi.till_min != null) 3033 { // absolute-expiry entry; get() already dropped it if expired, so nothing to decrement 3034 str += i+':'+(oi.match_again ? '+' : '')+'t'+oi.till_min+','; 3035 continue; 3036 } 3037 if (dec) 3038 { if (!oi.hours) 3039 { ended = oi.remain_sessions==0 ? 0 : --oi.remain_sessions <= 0; 3040 } 3041 else 3042 { ended = passed_hr > oi.remain_sessions; 3043 } 3044 if (ended) 3045 { if (!oi.match_again) 3046 { oi.remain_sessions = -1; // -1 indicates "cannot be matched in future" 3047 } 3048 else 3049 { continue; 3050 } 3051 } 3052 } 3053 // The trailing 'b' is only ever written by the 'f' (factor) counter -- see set_f(). It has no 3054 // meaning for the sticky-condition counters, and an old cookie without it simply reads as 0. 3055 str += i+':'+(oi.match_again ? '+' : '')+oi.remain_sessions+(oi.hours ? 'h' : '')+(oi.dirty ? 'b' : '')+','; 3056 } 3057 } 3058 str = str.substr(0, str.length-1); 3059 var set_cookie = {}; 3060 set_cookie[this.cookie_prop] = str; 3061 ns.cookie_obj('stat_track_u_id', set_cookie); 3062 return str; 3063 } 3064 3065 clear(arr) 3066 { var obj = this.get(); 3067 for (var i=0, j=arr.length; i<j; i++) 3068 { delete obj[arr[i]]; 3069 } 3070 this.set(obj); 3071 } 3072 3073 add(obj, override) 3074 { var cur_obj = this.get(); 3075 for (var i in obj) 3076 { if (override || !(i in cur_obj)) 3077 { cur_obj[i] = obj[i]; 3078 } 3079 } 3080 this.set(cur_obj); 3081 } 3082 } 3083 3084 function find_stylesheet(sel, all) 3085 { var found = []; 3086 for (var i=0; i<document.styleSheets.length; i++) 3087 { try 3088 { var style = document.styleSheets[i]; 3089 var rules = style.cssRules || style.rules; 3090 if (rules?.[0]?.selectorText?.toUpperCase()?.indexOf(sel) == 0) 3091 { if (!all) return style; 3092 found.push(style); 3093 } 3094 } 3095 catch (e) 3096 { 3097 } 3098 } 3099 return all ? found : null; 3100 } 3101 3102 function block_action(action_id, is_attached) 3103 { if (blocks) 3104 { blocks[action_id] = is_attached ? 2 : 1; 3105 } 3106 } 3107 3108 function unblock_action(action_id) 3109 { if (blocks) 3110 { delete blocks[action_id]; 3111 } 3112 } 3113 3114 /** Drop the WYSIWYG hide, whatever the containers are doing. 3115 3116 Called on its own short backstop, and again by unblock_all() - whichever comes first. 3117 Idempotent: find_stylesheet returns nothing the second time. 3118 **/ 3119 /** The decision has arrived. Stop hiding the containers it is not going to fill. 3120 3121 `pre_init_containers` runs BEFORE the round trip, so it hides every container an action 3122 could arrive in - it has no way to know which ones will. That is right, and it is also 3123 where the waiting used to end: the release is driven by delivered actions reporting, so 3124 a container the visitor was never going to be shown anything in stayed covered for the 3125 whole of `unblock_containers_after`. 3126 3127 On personyze.com's homepage that was five containers, three of them 562, 780 and 1446 3128 pixels tall, none with a delivered action - a second of missing page for nothing 3129 (Jacob, 27 Aug 26). And because the library loads after first paint, it is not a 3130 pre-hide at all by then: it removes content the visitor is already reading. 3131 3132 Here the answer is in hand. Every container carries `c.ac`, which is authoritative - 3133 an action not in it cannot be placed there - so a container with no DELIVERED action in 3134 its list has nothing coming, and its hide can go now rather than on a timer. The rest 3135 keep theirs and are released exactly as before, when their action reports. 3136 3137 CONSERVATIVE ON PURPOSE: releasing a hide early is the one failure that matters here - 3138 the visitor sees the original content, then sees it change, which is the whole thing 3139 this machinery exists to prevent. So anything unexpected in the shape of `actions` 3140 means nothing is released and the old timing stands. 3141 **/ 3142 function narrow_container_hide(actions) 3143 { if (!hidden_conts || !hidden_conts.length || !blocks) 3144 { return; 3145 } 3146 /* Each row is `[record_id, action_id, action_id, ...]` - the record
3146id first, which is 3147 why this starts at 1. Same shape `memorize_not_executed` relies on when it writes 3148 them out as `rid:aid aid`. */ 3149 if (!Array.isArray(actions)) 3150 { return; 3151 } 3152 let delivered = new Set(); 3153 for (let i=0; i<actions.length; i++) 3154 { let row = actions[i]; 3155 if (!Array.isArray(row)) 3156 { return; // not the shape we know - leave everything alone 3157 } 3158 for (let j=1; j<row.length; j++) 3159 { delivered.add(row[j]|0); 3160 } 3161 } 3162 let keep = []; 3163 for (let i=0; i<hidden_conts.length; i++) 3164 { let {c} = hidden_conts[i]; 3165 let wanted = false; 3166 if (Array.isArray(c.ac)) 3167 { for (let j=0; j<c.ac.length; j++) 3168 { if (c.ac[j]!=null && delivered.has(c.ac[j]|0)) 3169 { wanted = true; 3170 break; 3171 } 3172 } 3173 } 3174 if (wanted) 3175 { keep.push(hidden_conts[i]); 3176 } 3177 } 3178 if (keep.length == hidden_conts.length) 3179 { return; // every one of them is still expecting something 3180 } 3181 hidden_conts = keep; 3182 let found = find_stylesheet('#-S-T-H', true); 3183 for (let i=0; i<found.length; i++) 3184 { let st = found[i]; 3185 st.disabled = true; 3186 st.ownerNode?.parentNode?.removeChild(st.ownerNode); 3187 } 3188 if (keep.length) 3189 { let plain = '', evil = ''; 3190 for (let i=0; i<keep.length; i++) 3191 { let {html_id} = keep[i]; 3192 if (html_id.indexOf('[')!=-1 || html_id.indexOf('(')!=-1) 3193 { evil += ', ' + html_id; 3194 } 3195 else 3196 { plain += ', ' + html_id; 3197 } 3198 } 3199 add_stylesheet('#-S-T-H'+plain+' {visibility:hidden !important}'); 3200 add_stylesheet('#-S-T-H'+evil+' {visibility:hidden !important}'); 3201 } 3202 } 3203 3204 function unblock_qelem() 3205 { clearTimeout(unblock_qelem_timer); 3206 let found = find_stylesheet('#-S-T-Q', true); 3207 for (let i=0; i<found.length; i++) 3208 { let c = found[i]; 3209 c.disabled = true; 3210 c.ownerNode?.parentNode?.removeChild(c.ownerNode); 3211 } 3212 } 3213 3214 function unblock_all() 3215 { if (blocks && (force_unblock_all || !Object.keys(blocks).length)) 3216 { unblock_qelem(); 3217 let found = find_stylesheet('#-S-T-H', true); 3218 for (let i=0; i<found.length; i++) 3219 { let c = found[i]; 3220 c.disabled = true; 3221 c.ownerNode?.parentNode?.removeChild(c.ownerNode); 3222 blocks = null; 3223 } 3224 } 3225 } 3226 3227 /** Can any action ever be put into this container? 3228 3229 `c.ac` is the list of actions that may render into it, and it is authoritative rather than 3230 advisory: placing an action looks the container up by exactly this list 3231 (`c.ac.indexOf(action_id)`), so an action absent from it cannot arrive here by any route. 3232 An empty one therefore means a hide that is waiting for something that cannot happen - and 3233 it waits the full `unblock_containers_after`, because the release is driven by an action 3234 reporting and no action is coming. 3235 3236 These are placeholders whose actions were deleted, and they accumulate. On personyze.com's 3237 own account two of them - the nav spacer and the footer - had been hiding themselves on 3238 every page view of every page, indefinitely, for nobody. Found while chasing a flicker in 3239 the header (Jacob, 27 Aug 26): five containers were hidden on that page and not one had an 3240 action delivered to the visit. 3241 3242 The list is stored with a null in it when there are none (`ac:[null]`), so a length check is 3243 not enough and never was. 3244 **/ 3245 function add_stylesheet(cssText) 3246 { var s = document.createElement('style'); 3247 s.setAttribute("type", "text/css"); 3248 if (s.styleSheet) // IE does it this way 3249 { s.styleSheet.cssText = cssText; 3250 } 3251 else 3252 { s.appendChild(document.createTextNode(cssText)); 3253 } 3254 document.getElementsByTagName('head')[0].appendChild(s); 3255 } 3256 3257 function can_hold_an_action(c) 3258 { if (Array.isArray(c.ac)) 3259 { for (let i=0; i<c.ac.length; i++) 3260 { if (c.ac[i] != null) 3261 { return true; 3262 } 3263 } 3264 } 3265 return false; 3266 } 3267 3268 /** Has the visitor actually SEEN the page content yet? 3269 3270 This is the question the pre-hide's whole existence turns on. Hiding an element the 3271 visitor has never seen costs nothing and buys the anti-flicker; hiding one they are 3272 already looking at IS a flicker - the content vanishes mid-read and comes back 3273 changed or unchanged a moment later. Which of the two we are about to do depends on 3274 one thing only: whether a frame with this content has reached the screen. 3275 3276 Two ways the answer is "no" even on a page that is loading normally: 3277 3278 - No paint has happened. The browser says so directly (paint timing entries), and 3279 the sync-install case says it structurally: the library running before <body> 3280 exists means nothing can have painted. 3281 - The snippet's own inline pre-hide (`#pz-prehide`) has covered these elements since 3282 the FIRST BYTE of the page. Frames have painted, but the covered elements were in 3283 none of them - taking over that hide shows the visitor nothing new, and declining 3284 to take it over would REVEAL unpersonalized content mid-decision, manufacturing 3285 the exact flash the snippet author paid to prevent. 3286 3287 CONSERVATIVE BY CONSTRUCTION: "seen" requires PROOF (a paint-timing entry, in a 3288 browser that records them). No paint API, an error, an empty list - all read as 3289 "not seen", and the pre-hide behaves exactly as it always did. The gate can only 3290 ever remove a hide that would have been applied to visible content, never add one. 3291 **/ 3292 function content_already_seen() 3293 { if (document.getElementById('pz-prehide')) 3294 { return false;
3295 } 3296 try 3297 { if (performance.getEntriesByType('paint').length) 3298 { return true; 3299 } 3300 } 3301 catch (e) 3302 { 3303 } 3304 return false; 3305 } 3306 3307 function pre_init_containers(actions_qelem_ids) 3308 { if (!window['_S_T_ACTIONS_DISABLED']) 3309 { /* hide initial content of containers and try to set style before containers become visible on page */ 3310 blocks = {}; 3311 hidden_conts = []; 3312 var stylesheet = ''; // container styles (for empty/occupied containers) 3313 var stylesheet_hide = ''; // block containers 3314 var stylesheet_hide_evil = ''; // block containers for evil selectors (otherwise MSIE explodes) 3315 /* THE WYSIWYG SELECTORS HIDE SEPARATELY, and are released separately. 3316 3317 `actions_qelem_ids` is account-wide and page-blind: every element any page-editor 3318 action has ever touched, anywhere on the site, pre-hidden on every page. That is 3319 fine while it lasts a frame. It stopped being fine when one action held the page - 3320 the hide is ONE stylesheet, so an action waiting on a template held the header of 3321 a page it has nothing to do with for a full `unblock_containers_after`. 3322 3323 A container is worth waiting a second for: something is going to be attached to it 3324 and the wait is the point. A WYSIWYG change is not - it is applied synchronously 3325 inside the action's `onexec`, so if it has not happened by now it is not going to 3326 happen this pass. Hence its own sheet, its own sentinel, and its own much shorter 3327 backstop. */ 3328 var stylesheet_hide_q = ''; 3329 var stylesheet_hide_q_evil = ''; 3330 function add_hide(html_id, is_qelem) 3331 { var evil = html_id.indexOf('[')!=-1 || html_id.indexOf('(')!=-1; 3332 if (is_qelem) 3333 { if (evil) stylesheet_hide_q_evil += ', ' + html_id; 3334 else stylesheet_hide_q += ', ' + html_id; 3335 } 3336 else if (evil) 3337 { stylesheet_hide_evil += ', ' + html_id; 3338 } 3339 else 3340 { stylesheet_hide += ', ' + html_id; 3341 } 3342 } 3343 var {containers} = ns; 3344 for (var c_id in containers) 3345 { var c = containers[c_id]; 3346 if (((c.tp) & 1)) 3347 { var style = c.em; 3348 var html_id = c.hi; 3349 if (typeof(c.em) == 'string') 3350 { if (html_id.join) 3351 { html_id = ns.qelem_id_to_css(html_id); 3352 } 3353 if (typeof(html_id) == 'string') 3354 { //if (html_id.charAt(0)!='#' && html_id.charAt(0)!='.') html_id = '#' + html_id; 3355 if (html_id.indexOf('{')==-1 && html_id.indexOf('}')==-1 && c.em.indexOf('{')==-1 && c.em.indexOf('}')==-1) 3356 { stylesheet += html_id+'{'+c.em+'}\n'; 3357 } 3358 if (can_hold_an_action(c)) 3359 { hidden_conts.push({c: c, html_id: html_id}); 3360 add_hide(html_id); 3361 } 3362 } 3363 } 3364 } 3365 } 3366 // actions_qelem_ids 3367 for (var i=0; i<actions_qelem_ids.length; i++) 3368 { try 3369 { add_hide(ns.qelem_id_to_css(typeof(actions_qelem_ids[i])=='string' && actions_qelem_ids[i].charAt(0)=='[' ? eval(actions_qelem_ids[i]) : actions_qelem_ids[i]), true); 3370 } 3371 catch (e) 3372 { 3373 } 3374 } 3375 // inject 3376 stylesheet_hide = '#-S-T-H'+stylesheet_hide+' {visibility:hidden !important}'; 3377 stylesheet_hide_evil = '#-S-T-H'+stylesheet_hide_evil+' {visibility:hidden !important}'; 3378 stylesheet_hide_q = '#-S-T-Q'+stylesheet_hide_q+' {visibility:hidden !important}'; 3379 stylesheet_hide_q_evil = '#-S-T-Q'+stylesheet_hide_q_evil+' {visibility:hidden !important}'; 3380 add_stylesheet(stylesheet); 3381 add_stylesheet(stylesheet_hide); 3382 add_stylesheet(stylesheet_hide_evil); 3383 add_stylesheet(stylesheet_hide_q); 3384 add_stylesheet(stylesheet_hide_q_evil); 3385 /* THE HANDOVER, and AFTER our own hide is in place - not before, or the elements 3386 are uncovered for a frame and the flicker we came to prevent happens on the way 3387 in. The pasted snippet may carry an inline pre-hide of these same elements: it 3388 starts at the first byte of the page, where this library cannot, because it has 3389 to download first. Ours is up now, so theirs comes out. 3390 3391 Absent on an installation whose code predates it, which simply means the hiding 3392 starts when the library does, exactly as it always did. */ 3393 var prehide = document.getElementById('pz-prehide'); 3394 if (prehide && prehide.parentNode) 3395 { prehide.parentNode.removeChild(prehide); 3396 } 3397 } 3398 } 3399 3400 function reg_unblock() 3401 { ns.reg_onready 3402 ( () => 3403 { if (blocks) 3404 { for (var action_id in blocks) 3405 { if (blocks[action_id] != 2) 3406 { delete blocks[action_id]; 3407 } 3408 } 3409 unblock_all(); 3410 } 3411 } 3412 ); 3413 } 3414 3415 function memorize_not_executed() 3416 { var ane = ns.cookie_obj('_stat_track_s_id')._ane; 3417 ns.cookie_obj('_stat_track_s_id', {_ane: ''}); 3418 ns.add_e
3419 ( window, 3420 'onbeforeunload', 3421 function() 3422 { var q = actions_q; 3423 for (var i=0; i<q.length; i++) 3424 { q[i] = q[i].join(' ').replace(' ', ':'); 3425 } 3426 var ane = q.join(';'); 3427 actions_q = []; 3428 if (ane != ' ') 3429 { ns.cookie_obj('_stat_track_s_id', {_ane: ane}); 3430 } 3431 }, 3432 'st ane' 3433 ); 3434 if (ane) 3435 { ane = ane.split(';'); 3436 for (var i=0; i<ane.length; i++) 3437 { var pos = ane[i].indexOf(':'); 3438 if (pos != -1) 3439 { var rid = ane[i].substr(0, pos); 3440 var aids = ane[i].substring(pos+1).split(' '); 3441 for (var j=0; j<aids.length; j++) 3442 { log_event(-11, aids[j], LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&8 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 3443 } 3444 } 3445 } 3446 } 3447 } 3448 3449 function log_visit_2() 3450 { var {new_f, factor_dirty, visit_id, actions, clear_conds, clear_actions} = ins_status; 3451 if (want_new_navigation) 3452 { want_new_navigation = false; 3453 onend(); 3454 install_focus(); 3455 memorize_not_executed(); 3456 html_input_state = {}; 3457 events_sent_times = 0; 3458 } 3459 cur_visit_id = visit_id || 0; 3460 // process received data 3461 set_f(new_f, factor_dirty, 1); 3462 // add sticky conditions 3463 if (current_conditions) 3464 { var obj = {}; 3465 for (var i in current_conditions) 3466 { if (current_conditions.hasOwnProperty(i) && current_conditions[i]) // if is sticky 3467 { obj[i.substring(1)] = {remain_sessions: current_conditions[i][0], match_again: current_conditions[i][1], hours: current_conditions[i][2]}; 3468 } 3469 } 3470 new SessCnt('sy').add(obj); 3471 } 3472 // 3473 if (clear_conds?.length) 3474 { new SessCnt('sy').clear(clear_conds); 3475 } 3476 if (clear_actions?.length) 3477 { do_clear_actions(clear_actions); 3478 } 3479 var call_balancer = is_ready ? null : new ns.CallBalancer(-1, reg_unblock); 3480 var bypass_others = exec_actions 3481 ( actions, 3482 () => 3483 { call_balancer?.dec(), 3484 reset_conts_retry(); // fresh actions and conditions: a container that kept coming up empty may be placeable now 3485 init_containers(); 3486 unblock_all(); 3487 } 3488 ); 3489 if (bypass_others && ondropcss) 3490 { var prev = ondropcss; 3491 ondropcss = null; 3492 setTimeout(prev, 3000); 3493 } 3494 exec_ondropcss(); 3495 if (!is_ready) 3496 { call_balancer.inc(); 3497 is_ready = true; 3498 } 3499 exec_onevent(); 3500 } 3501 3502 var debug_time_start = 0; 3503 function debug_time(message) 3504 { if (!debug_time_start) 3505 { debug_time_start = Date.now(); 3506 } 3507 var diff = Date.now() - debug_time_start; 3508 ns.info(message+': '+diff); 3509 } 3510 3511 function logmsg(msg) 3512 { console.log('Personyze: '+msg); 3513 } 3514 3515 function errmsg(msg) 3516 { // console.error (not .log) so that these survive a console filtered to errors, 3517 // and so that a stack trace points at what actually failed 3518 console.error('Personyze: '+msg); 3519 } 3520 3521 function te_errmsg(msg) 3522 { // Only for people in testing mode - site visitors must not see these. 3523 // Guarded, because an error can be reported before the lib body has assigned te_eff(). 3524 if (ns.te_eff && ns.te_eff()) 3525 { errmsg(msg); 3526 } 3527 } 3528 3529 function make_url_kv(k, v) 3530 { v = !v ? '' : v.trim(); 3531 return !v ? '' : encodeURIComponent(k)+'='+encodeURIComponent(v)+'&'; 3532 } 3533 3534 function submit(action_id, data, onload, onerror) 3535 { action_id |= 0; 3536 try 3537 { if (!action_id) 3538 { throw new Error('No action ID given'); 3539 } 3540 else 3541 { var post = ''; 3542 for (var p in data) 3543 { if (data.hasOwnProperty(p)) 3544 { var v = data[p]; 3545 p = p.trim(); 3546 if (v && typeof(v)!='function' && RE_GOOD_URL_PARAM.test(p)) 3547 { if (typeof(v) != 'object') 3548 { post += make_url_kv(p, v); 3549 } 3550 else 3551 { for (var sub_p in v) 3552 { if (v.hasOwnProperty(sub_p) && RE_GOOD_URL_PARAM.test(sub_p)) 3553 { post += make_url_kv(p+'['+sub_p+']', v[sub_p]); 3554 } 3555 } 3556 } 3557 } 3558 } 3559 } 3560 if (!post) 3561 { throw new Error('The form was empty'); 3562 } 3563 else 3564 { var id = make_id(0, 1); 3565 var delim = '?'; 3566 var url = get_protocol()+'\/\/qacounter.personyze.com\/submit.php'+delim+`sr=${ns.server_id|0}&bh=${id.bh}&id=${id.id}&si=${id.si}&ss=${id.ss}&tm=${ns.secs()}&vi=${cur_visit_id}&ai=${action_id}&ps=${id.ps}`; 3567 var xhr = new XMLHttpRequest; 3568 xhr.open('POST', url); 3569 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 3570 function report_error(error) 3571 { if (!error) error = 'Request failed'; 3572 ns.act_params[action_id].fire_event('onerror', error); 3573 var onerror_or_onload = onerror || onload; 3574 if (onerror_or_onload) 3575 { onerror_or_onload(new Error(error)); 3576 } 3577 } 3578 xhr.onerror = function() 3579 { report_error(); 3580 }; 3581 xhr.onload = function() 3582 { var result = this.responseText || ''; 3583 var error = ''; 3584 for (let sub of result.match(/\/\/[^\r\n]*[\r\n]*/g)) 3585 { let sub_t = sub.substring(2).trim(); 3586 if (sub_t=='OK' || sub_t=='Error') 3587 { error = sub_t=='OK' ? null : result.substring(result.indexOf(sub) + sub.length).trim(); 3588 break; 3589 } 3590 } 3591 if (error == null) 3592 { ns.act_params[action_id].fire_event('onextra'); 3593 if (onload) onload(); 3594 eval(result); 3595 } 3596 else 3597 { report_error(error); 3598 } 3599 }; 3600 xhr.send(post); 3601 } 3602 } 3603 } 3604 catch (e) 3605 { if (onerror || onload) (onerror || onload)(e); 3606 } 3607 } 3608 3609 function exec_commands() 3610 { let system_is_on = ns.system.is_on(); 3611 // get q 3612 let q = commands_queue; 3613 let q_override_action_id_for = commands_override_action_id_for; 3614 commands_queue = []; 3615 commands_override_action_id_for = null; 3616 // do q 3617 for (let i=0, i_end=q.length; i<i_end; i++) 3618 { let command = q[i]; 3619 if (command) 3620 { let override_action_id = q_override_action_id_for?.[i]|0;
3621 let command_name = ' '+(command[0]+'').trim().replace(RE_SPACES_G, ' ').toLowerCase()+' '; 3622 let command_args_length = command.length; 3623 let onload = null; 3624 let onerror = null; 3625 if (typeof(command[command_args_length-1]) == 'function') 3626 { onerror = onload; 3627 onload = command[--command_args_length]; 3628 } 3629 if (typeof(command[command_args_length-1]) == 'function') 3630 { onerror = onload; 3631 onload = command[--command_args_length]; 3632 } 3633 let tokens = {}; 3634 let n_tokens = 0; 3635 function has(token) 3636 { if (!tokens.hasOwnProperty(token)) 3637 { let i = command_name.indexOf(token); 3638 let yes = i!=-1 && command_name.charAt(i-1)==' ' && command_name.charAt(i+token.length)==' '; 3639 if (yes) 3640 { command_name = command_name.substr(0, i)+command_name.substring(i+token.length+1); 3641 n_tokens++; 3642 } 3643 tokens[token] = yes; 3644 } 3645 return tokens[token]; 3646 } 3647 function only(n) 3648 { return n_tokens==n && command_name==' '; 3649 } 3650 let args = []; 3651 function get_scalar_arg(n, err_msg) 3652 { var arg = command[1 + n]; 3653 if (!(typeof(arg)=='number' || typeof(arg)=='string' && (arg = (arg+'').trim()))) 3654 { throw new Error('Command '+command[0]+': '+err_msg); 3655 } 3656 args[n] = arg; 3657 } 3658 function get_object_arg(n, err_msg) 3659 { let arg = command[1 + n]; 3660 if (!(arg && typeof(arg)=='object')) 3661 { throw new Error('Command '+command[0]+': '+err_msg); 3662 } 3663 args[n] = arg; 3664 } 3665 function get_key_value_arg(n, lc_key) 3666 { let kv = {}; 3667 for (let i=n+1; i<command_args_length;) 3668 { let k = (command[i++]+'').trim(); 3669 if (!k) 3670 { throw new Error('Command '+command[0]+': Parameter name expected'); 3671 } 3672 else 3673 { k = k.replace(RE_SPACES_G, ' '); 3674 if (lc_key) 3675 { k = k.toLowerCase(); 3676 } 3677 kv[k] = command[i++]; 3678 } 3679 } 3680 args[n] = kv; 3681 } 3682 function get_flat_array_of_scalar_arg(n, err_if_empty) 3683 { let array = []; 3684 for (let i=n+1; i<command_args_length;) 3685 { get_scalar_arg(i-1, 'Must be nonempty scalar value'); 3686 array[array.length] = args[i++-1]; 3687 } 3688 if (err_if_empty && array.length==0) 3689 { throw new Error(err_if_empty); 3690 } 3691 args[n] = array; 3692 } 3693 try 3694 { let interest_class = null; 3695 if (has('on') && has('ready') && only(2)) 3696 { // command: On Ready 3697 // do nothing 3698 } 3699 else if (system_is_on) 3700 { if (has('product') && (has('viewed') || has('liked') || has('added to cart') || has('removed from cart') || has('unliked') || has('purchased')) && only(2)) 3701 { // command: Product Viewed 3702 // command: Product Liked 3703 // command: Product Added to cart 3704 // command: Product Purchased 3705 // command: Product Unliked 3706 // command: Product Removed from cart 3707 get_scalar_arg(0, 'Please, provide Product ID (number or string)'); 3708 get_key_value_arg(1, true); 3709 let c_id = has('viewed') ? -22 : has('liked') ? -24 : has('added to cart') ? -23 : has('removed from cart') ? -26 : has('unliked') ? -27 : -25; 3710 let action_id = override_action_id || (args[1].action_id>0 ? parseInt(args[1].action_id) : 0); 3711 let quantity = args[1].quantity>0 ? parseInt(args[1].quantity) : 0; // 0 when purchasing means use what was set when added to cart 3712 let key = String(args[1].key || '').toLowerCase()=='sku' ? ' sku' : ''; 3713 log_event(c_id, encodeURIComponent(action_id+' '+quantity+key+':'+args[0]), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&2097152 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2097152 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror); 3714 onload = null; 3715 } 3716 else if (has('products') && (has('purchased') || has('removed from cart') || has('unliked')) && only(2)) 3717 { // command: Products Purchased 3718 // command: Products Unliked 3719 // command: Products Removed from cart 3720 let c_id = has('unliked') ? -30 : has('purchased') ? -28 : -29; 3721 log_event(c_id, '1', (event_send_with_delay&2097152 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2097152 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror); 3722 onload = null; 3723 } 3724 else if (has('article') && (has('viewed') || has('liked') || has('commented') || has('unliked') || has('goal')) && only(2)) 3725 { // command: Article Viewed 3726 // command: Article Liked 3727 // command: Article Commented 3728 // command: Article Unliked 3729 // command: Article Goal 3730 get_scalar_arg(0, 'Please, provide Article ID (number or string)'); 3731 get_key_value_arg(1, true); 3732 let is_viewed = has('viewed');
3733 let c_id = is_viewed ? -31 : has('liked') ? -33 : has('commented') ? -32 : has('unliked') ? -35 : -34; 3734 let action_id = override_action_id || (args[1].action_id>0 ? parseInt(args[1].action_id) : 0); 3735 log_event(c_id, encodeURIComponent(action_id+' 1:'+args[0]), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&2097152 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&2097152 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror); 3736 if (is_viewed) 3737 { wait_for_article_delivery(args[0]); 3738 } 3739 onload = null; 3740 } 3741 else if 3742 ( ( has 3743 ( (interest_class='upsale category') && 'upsell category' 3744 ) 3745 || has 3746 ( 'upsell categories' 3747 ) 3748 || has 3749 ( (interest_class='cross category') && 'cross-sell category' 3750 ) 3751 || has 3752 ( 'cross-sell categories' 3753 ) 3754 || has 3755 ( interest_class= 'family' 3756 ) 3757 || has 3758 ( 'families' 3759 ) 3760 || has 3761 ( interest_class= 'tag' 3762 ) 3763 || has 3764 ( 'tags' 3765 ) 3766 || has 3767 ( interest_class= 'category' 3768 ) 3769 || has 3770 ( 'categories' 3771 ) 3772 || has 3773 ( interest_class= 'interest' 3774 ) 3775 || has 3776 ( 'interests' 3777 ) 3778 ) && only(1) 3779 ) 3780 { // command: Interest 3781 get_flat_array_of_scalar_arg(0); 3782 interest_class = (interest_class || '').replace('cross-sale', 'cross').replace('-', ''); 3783 let interests = args[0]; 3784 for (var j=0; j<interests.length; j++) 3785 { interests[j] = encodeURIComponent('0 '+interest_class+':'+interests[j]); // rank 0 3786 } 3787 log_event(-37, interests.join('&'), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&4194304 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&4194304 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id, onload, onerror); 3788 onload = null; 3789 } 3790 else if (has('event') && only(1)) 3791 { // command: Event 3792 get_scalar_arg(0, 'Please, provide event name'); 3793 get_scalar_arg(1, 'Please, provide event value'); 3794 ns.custom_event(args[0], args[1], onload, onerror); 3795 onload = null; 3796 } 3797 else if ((has('person') || has('user')) && has('profile') && only(2)) 3798 { // command: User profile 3799 get_key_value_arg(0); 3800 ns.social('IN', args[0], onload, onload); 3801 onload = null; 3802 } 3803 else if (has('delegate identity') && only(1)) 3804 { // command: Delegate identity 3805 get_flat_array_of_scalar_arg(0); 3806 let urls = args[0]; 3807 ns.pass_cookie(urls.length ? urls : null); 3808 } 3809 else if (has('navigate') && only(1)) 3810 { // command: Navigate 3811 click_event_page('-17', onload, onerror); 3812 onload = null; 3813 } 3814 else if (has('submit') && only(1)) 3815 { // command: Submit 3816 get_scalar_arg(0, 'Please, provide action id'); 3817 get_object_arg(1, 'No data given'); 3818 submit(args[0], args[1], onload, onerror); 3819 onload = null; 3820 } 3821 else if (has('fire') && has('action') && only(2)) 3822 { // command: Fire action 3823 get_flat_array_of_scalar_arg(0, 'Please, provide action id'); 3824 if (!(args[0] > 0)) 3825 { throw new Error('Invalid action id'); 3826 } 3827 let action_ids = args[0]; 3828 log_event(-38, action_ids.join(','), LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&8388608 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8388608 ? LOG_EVENT_SEND_NOW : 0), 0, 0, onload, onerror); 3829 onload = null; 3830 } 3831 else if (has('when') && has('action') && has('executed') && only(3)) 3832 { // command: When action executed 3833 get_scalar_arg(0, 'Please, provide action id'); 3834 if (!(args[0] > 0)) 3835 { throw new Error('Invalid action id'); 3836 } 3837 let action_id = args[0]|0; 3838 if (!when_action_executed[action_id]) 3839 { when_action_executed[action_id] = []; 3840 } 3841 when_action_executed[action_id].push({onload, onerror}); 3842 if (!(executed_actions[action_id] > 0)) 3843 { onload = null; 3844 } 3845 } 3846 else 3847 { throw new Error('unknown command: '+command[0]); 3848 } 3849 } 3850 else 3851 { onload = null; 3852 } 3853 onload?.(); 3854 } 3855 catch (e) 3856 { logmsg(e.message); 3857 onerror?.(e); 3858 } 3859 } 3860 } 3861 } 3862 3863 function get_elem_visible_prc(elem) 3864 { // viewport 3865 var vp_left = 0; 3866 var vp_top = 0; 3867 var vp_right = document.documentElement.clientWidth; 3868 var vp_bottom = document.documentElement.clientHeight; 3869 // overflow (hidden, scroll, etc.) 3870 for (var e=elem.parentNode; e && e.nodeName!='BODY'; e=e.parentNode) 3871 { if (e.offsetHeight<e.scrollHeight || e.offsetWidth<e.scrollWidth) 3872 { if (ns.new_elem(e).style().overflow != 'visible') 3873 { var rect = e.getBoundingClientRect(); 3874 vp_left = Math.max(vp_left, rect.left); 3875 vp_top = Math.max(vp_top, rect.top); 3876 vp_right = Math.min(vp_right, rect.right); 3877 vp_bottom = Math.min(vp_bottom, rect.bottom); 3878 } 3879 } 3880 } 3881 // elem coordinates 3882 if (elem.nodeType == XML_TEXT_NODE) 3883 { let range = document.createRange(); 3884 range.selectNodeContents(elem); 3885 var rects = range.getClientRects(); 3886 } 3887 else 3888 { rects = elem.getClientRects(); 3889 } 3890 var square = 0; 3891 var visible_square = 0; 3892 for (var i=rects.length-1; i>=0; i--) 3893 { var rect = rects[i]; 3894 var vis_v = 1 - Math.min(rect.height, Math.max(0, vp_top - rect.top) + Math.max(0, rect.bottom - vp_bottom)) / rect.height; 3895 var vis_h = 1 - Math.min(rect.width, Math.max(0, vp_left - rect.left) + Math.max(0, rect.right - vp_right)) / re
3895ct.width; 3896 var sq = rect.width*rect.height; 3897 square += sq; 3898 visible_square += !sq ? 0 : sq*vis_v*vis_h; 3899 } 3900 return square ? visible_square/square : 0; 3901 } 3902 3903 function is_elem_delivered(elem) 3904 { try 3905 { return get_elem_visible_prc(elem) >= 2/3; 3906 } 3907 catch (e) 3908 { ns.error(e); 3909 return true; 3910 } 3911 } 3912 3913 /** Call `callback` once `elem` counts as delivered (the 2/3 rule of 3914 `is_elem_delivered`), then stop. Returns a function that stops watching. 3915 3916 IntersectionObserver, not the scroll listener alone: an element reaches the 3917 viewport with no scroll at all when a tab or accordion opens it, when lazy 3918 content above it collapses, when an image finishes loading and shifts the 3919 layout, on a resize, or when it is appended into view. Every one of those 3920 was a confirmed view nobody reported, because the only trigger was 3921 `onscroll`. 3922 3923 The observer is only the TRIGGER; what counts as seen stays 3924 `is_elem_delivered`, which knows about text nodes, multi-rect elements and 3925 nested scroll containers. So this changes WHEN we look, never what counts — 3926 and where IntersectionObserver is missing, the old scroll listener still 3927 does the looking. 3928 **/ 3929 function reg_ondelivered(elem, callback) 3930 { let done = false; 3931 let stop = () => {}; 3932 function check() 3933 { if (!done && is_elem_delivered(elem)) 3934 { done = true; 3935 stop(); 3936 callback(); 3937 } 3938 } 3939 // IntersectionObserver takes an Element; for a text node watch its parent 3940 // and let the check itself measure the node. 3941 let target = elem?.nodeType==1 ? elem : elem?.parentNode?.nodeType==1 ? elem.parentNode : null; 3942 if (window.IntersectionObserver && target) 3943 { // Thresholds around the 2/3 rule rather than only 0: an element that is 3944 // already partly visible gets no entry callback, and without a middle 3945 // threshold a slow reveal could settle just under 2/3 and never fire again. 3946 let observer = new IntersectionObserver(() => {ns.pcall(check)}, {threshold: [0, 1/3, 2/3, 1]}); 3947 stop = () => 3948 { observer?.disconnect(); 3949 observer = null; 3950 };
3951 try 3952 { observer.observe(target); 3953 } 3954 catch (e) 3955 { ns.error(e); 3956 } 3957 } 3958 else 3959 { let h_onscroll = reg_onscroll(check); 3960 stop = () => 3961 { h_onscroll?.(); 3962 h_onscroll = null; 3963 }; 3964 } 3965 return () => {if (!done) stop()}; 3966 } 3967 3968 function wait_for_article_delivery(internal_id) 3969 { if (articles_delivered.indexOf(internal_id) == -1) 3970 { const WAIT_TIME = 10_000; 3971 const THRESHOLD = 0.75; 3972 let article_elem = document.querySelector('article'); 3973 if (!article_elem) 3974 { // Find element that includes the biggest picture, and the biggest text inside it 3975 let big_imgs = [...document.querySelectorAll('img')].map 3976 ( e => 3977 { let {width, height} = e.getBoundingClientRect(); 3978 let {opacity, visibility} = getComputedStyle(e); 3979 return [e, opacity<1 || visibility=='hidden' ? 0 : width * height]; 3980 } 3981 ).sort((a, b) => b[1] - a[1]); 3982 3983 let big_texts = [...document.querySelectorAll('p,h1,h2,h3,h4,h5,h6,b,strong,u,em,th,td,caption')].map 3984 ( e => 3985 { let {fontSize, fontWeight, opacity, visibility} = getComputedStyle(e); 3986 return [e, opacity<1 || visibility=='hidden' ? 0 : parseFloat(fontSize)*1000 + 1*fontWeight]; 3987 } 3988 ).sort((a, b) => b[1] - a[1]); 3989 3990 function find_common_elem(e1, e2) 3991 { if (e1 && e2) 3992 { for (let p1=e1.parentNode; p1 && p1!=document.body; p1=p1.parentNode) 3993 { if (ns.new_elem(e2).has_parent(p1)) 3994 { return p1; 3995 } 3996 } 3997 } 3998 } 3999 4000 article_elem = find_common_elem(big_imgs[0], big_texts[0]) || document.body; 4001 } 4002 4003 let till = Date.now() + WAIT_TIME; 4004 let h_onscroll = null; 4005 4006 clearTimeout(articles_delivered_timer); 4007 if (covered_prc() >= THRESHOLD) 4008 { iter(); 4009 } 4010 else 4011 { h_onscroll = ns.add_e(window, 'onscroll', iter, 'article delivery'); 4012 } 4013 4014 function covered_prc() 4015 { let {top, bottom} = article_elem.getBoundingClientRect(); 4016 return (innerHeight-top) / (bottom-top); 4017 } 4018 4019 function iter() 4020 { if (covered_prc() >= THRESHOLD) 4021 { let rem = Math.max(0, till - Date.now()); 4022 h_onscroll?.remove(); 4023 clearTimeout(articles_delivered_timer); 4024 articles_delivered_timer = setTimeout 4025 ( () => 4026 { if (articles_delivered.length >= 10) 4027 { articles_delivered.shift(); 4028 } 4029 articles_delivered.push(internal_id); 4030 log_event(-41, encodeURIComponent('0 1:'+internal_id), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&134217728 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&134217728 ? LOG_EVENT_SEND_NOW : 0), 0, cur_visit_id); 4031 }, 4032 rem 4033 ); 4034 } 4035 } 4036 } 4037 } 4038 4039 function reg_onscroll(func) 4040 { if (!onscroll_funcs) 4041 { onscroll_funcs = []; 4042 h_onscroll = ns.add_e 4043 ( window, 4044 'onscroll', 4045 infrequent_func 4046 ( () => 4047 { if (onscroll_funcs) 4048 { for (var i=0, i_end=onscroll_funcs.length; i<i_end; i++) 4049 { ns.pcall(onscroll_funcs[i]); 4050 } 4051 } 4052 }, 4053 300 4054 ), 4055 'reg_onscroll' 4056 ); 4057 } 4058 onscroll_funcs.push(func); 4059 return () => 4060 { if (onscroll_funcs) 4061 { var i = onscroll_funcs.indexOf(func); 4062 if (i != -1) 4063 { onscroll_funcs.splice(i, 1); 4064 if (!onscroll_funcs.length) 4065 { onscroll_funcs = null; 4066 h_onscroll.remove(); 4067 h_onscroll = null; 4068 } 4069 } 4070 } 4071 }; 4072 } 4073 4074 function rollback(including) 4075 { onend(); 4076 clearTimeout(send_timer); 4077 send_timer = null; 4078 // block 4079 clearTimeout(unblock_all_timer); 4080 force_unblock_all = true; 4081 unblock_all(); 4082 // actions 4083 for (let a in timers_ac) 4084 { clearTimeout(timers_ac[a]); 4085 } 4086 timers_ac = {}; 4087 // event handlers bound to elements 4088 function cleanup_iter(subelem) 4089 { let evts = subelem._S_T_EVT; 4090 if (evts) 4091 { for (let html_id in evts) 4092 { let h = evts[html_id]; 4093 try {h.remove()} catch (e) {ns.error(e)} // "h" may not have remove(), if it's inserted through Object.prototype 4094 } 4095 delete subelem._S_T_EVT; 4096 } 4097 } 4098 cleanup_iter(window); 4099 cleanup_iter(document.body); 4100 // containers 4101 let {containers} = ns;
4102 for (let c_id in containers) 4103 { if (containers.hasOwnProperty(c_id)) 4104 { let c = containers[c_id]; 4105 clearInterval(c.h_cycle); 4106 c.h_cycle = null; 4107 } 4108 } 4109 clearInterval(conts_timer); 4110 ns.new_elem(document.body, cleanup_iter); 4111 // lno 4112 ns.lno?.(); 4113 // uacss 4114 ns.uacss.rollback(); 4115 // 4116 h_onscroll?.remove(); 4117 h_onscroll = null; 4118 clearTimeout(articles_delivered_timer); 4119 } 4120 4121 function add_action_to_history(action_id, rules) 4122 { if (window.localStorage) 4123 { let cur_time = Math.floor(Date.now() / 1000); 4124 4125 // 1. Read actions_history from storage 4126 if (!actions_history) 4127 { try 4128 { let obj = JSON.parse(localStorage.stat_track_actions); 4129 actions_history = {}; 4130 // convert from storage format 4131 // i'll use 10-second presicion to save 1 digit, and to ignore too shortly presented actions 4132 for (let a in obj) 4133 { if (a > 0) 4134 { let history = obj[a]; 4135 let at = history[0]; 4136 let i, i_end=history.length; 4137 for (i=1; i<i_end; i++) 4138 { at += history[i]*10; 4139 history[i] = at; 4140 } 4141 // delete old 4142 let threshold = cur_time - 2678400; 4143 for (i=0; i<i_end && history[i]<threshold; i++); 4144 history.splice(0, i); 4145 if (history.length) 4146 { actions_history[a] = history; 4147 } 4148 } 4149 } 4150 } 4151 catch (e) 4152 { actions_history = {}; 4153 } 4154 } 4155 4156 // 2. Can execute this action now? 4157 if (rules) 4158 { function show_then_break({show, brk}) 4159 { let history = actions_history[action_id]; 4160 if (history) 4161 { let i = history.length - 1; 4162 let showing_till = history[i]; 4163 if (cur_time-showing_till < brk) // if not passed brk seconds 4164 { while (i>0 && showing_till-history[i-1]<=show) i--; 4165 let showing_since = history[i]; 4166 if (cur_time-showing_since >= show) 4167 { return false; 4168 } 4169 } 4170 } 4171 return true; 4172 } 4173 4174 function time_since_action_gt({action_id, time, is_first}) 4175 { let history = actions_history[action_id]; 4176 if (!history) 4177 { return true; 4178 } 4179 let at = history[is_first ? 0 : history.length-1]; 4180 return cur_time-at > time; 4181 } 4182 4183 function time_since_action_lt({action_id, time, is_first}) 4184 { let history = actions_history[action_id]; 4185 if (!history) 4186 { return false; 4187 } 4188 let at = history[is_first ? 0 : history.length-1]; 4189 return cur_time-at <= time; 4190 } 4191 4192 let yes = 4193 ( (!rules.show_then_break || show_then_break(rules.show_then_break)) 4194 && 4195 (!rules.time_since_action_gt || time_since_action_gt(rules.time_since_action_gt)) 4196 && 4197 (!rules.time_since_action_lt || time_since_action_lt(rules.time_since_action_lt)) 4198 ); 4199 4200 if (!yes) 4201 { return false; // can not execute 4202 } 4203 } 4204 4205 // 3. Add to history 4206 actions_history[action_id] = (actions_history[action_id] || []).concat([cur_time]); 4207 4208 // 4. Store actions_history 4209 let obj = {}; 4210 for (let a in actions_history) 4211 { if (a > 0) 4212 { let history = actions_history[a]; 4213 let history_2 = [history[0]]; 4214 for (let i=1; i<history.length; i++) 4215 { let offset = Math.floor((history[i] - history[i-1]) / 10); // 10-second presicion 4216 if (offset > 0) 4217 { history_2[i] = offset; 4218 } 4219 else 4220 { history.splice(i--, 1); 4221 } 4222 } 4223 obj[a] = history_2; 4224 } 4225 } 4226 localStorage.stat_track_actions = JSON.stringify(obj); 4227 } 4228 return true; // can execute 4229 } 4230 4231 /** Campaign-level frequency caps. `ns.camp_caps` is emitted into the account's 4232 compiled index.js from each condition's `freq_cap_def` column: 4233 {caps: {<cond_id>: {v, k, c, e, ws, wt, hs, ht, n: [action ids]}}, acts: {<action_id>: {d: [cond ids], f}}} 4234 - caps: v/k/c = views/clicks/closes thresholds (0 = off); e = 1 counts views 4235 at delivery rather than actual display; the counting window is ws sessions 4236 (1 = this session) or wt seconds; on threshold the campaign is hidden for 4237 hs sessions or ht seconds, and the counters start over — a fixed window 4238 that resets, not a sliding one. 4239 - acts: d = capped campaigns delivering the action, f = 1 when an uncapped 4240 campaign also delivers it (then the action is never blocked, because that 4241 campaign is entitled to show it). 4242 Counts live in localStorage (per-origin, like actions_history); the 4243 suppression entry goes to the 'nocmps' sessions-countdown cookie, so once 4244 tripped the hide holds domain-wide. 4245 **/
4246 function camp_cap_event(action_id, kind, at_exec) 4247 { var cc = ns.camp_caps; 4248 if (cc && window.localStorage) 4249 { var st = ns.cookie_obj('stat_track_u_id').st - 0 || 1; 4250 var cur_min = Math.floor(ns.secs() / 60); 4251 var state; 4252 try 4253 { state = JSON.parse(localStorage.stat_track_camp_caps) || {}; 4254 } 4255 catch (e) 4256 { state = {}; 4257 } 4258 var suppressed = new SessCnt('nocmps').get(); 4259 var mod = false; 4260 for (var c_id in cc.caps) 4261 { var cap = cc.caps[c_id]; 4262 var threshold = kind=='v' ? cap.v : kind=='k' ? cap.k : cap.c; 4263 // views honour the per-cap "count at delivery vs at display" choice; a click is a click and a close is a close either way 4264 if (threshold > 0 && (kind!='v' || !cap.e == !at_exec) && cap.n.indexOf(action_id|0) != -1 && !(c_id in suppressed)) 4265 { var cnt = (state[c_id] = state[c_id] || {})[kind]; 4266 // start a fresh window on first count, or when the old window expired 4267 if (!cnt || !cnt[0] || (cap.wt > 0 ? cur_min > cnt[1] : st > cnt[1])) 4268 { cnt = [0, cap.wt > 0 ? cur_min + Math.ceil(cap.wt/60) : st + (cap.ws > 1 ? cap.ws : 1) - 1]; 4269 } 4270 cnt[0]++; 4271 state[c_id][kind] = cnt; 4272 mod = true; 4273 if (cnt[0] >= threshold) 4274 { var set_cookie = {}; 4275 set_cookie[c_id] = cap.ht > 0 ? {till_min: Math.ceil((ns.secs()+cap.ht)/60), match_again: 1} : {remain_sessions: cap.hs > 1 ? cap.hs : 1, match_again: 1}; 4276 new SessCnt('nocmps').add(set_cookie, 1); 4277 delete state[c_id]; // both counters start over once the hide expires 4278 } 4279 } 4280 } 4281 if (mod) 4282 { try 4283 { localStorage.stat_track_camp_caps = JSON.stringify(state); 4284 } 4285 catch (e) 4286 { 4287 } 4288 } 4289 } 4290 } 4291 4292 /** Whether every capped campaign that delivers this action is currently hidden 4293 by its frequency cap (and no uncapped campaign delivers it too). 4294 **/ 4295 function camp_cap_blocked(action_id) 4296 { var cc = ns.camp_caps; 4297 var e = cc && cc.acts && cc.acts[action_id]; 4298 if (e && !e.f) 4299 { var suppressed = new SessCnt('nocmps').get(); 4300 var i = e.d.length - 1; 4301 while (i>=0 && e.d[i] in suppressed) i--; 4302 return i < 0; 4303 } 4304 return false; 4305 } 4306 4307 function add_to_cart_or_to_wishlist(what, products, action_id, arg) 4308 { function done() 4309 { clearTimeout(h_done); 4310 commands_cur_override_action_id = 0; 4311 } 4312 try 4313 { if (products!=null && !Array.isArray(products)) 4314 { products = [products]; 4315 } 4316 // convert products 4317 if (products) 4318 { let products_valid = []; 4319 for (let i=0, i_end=products.length; i<i_end; i++) 4320 { let obj = products[i]; 4321 if (obj) 4322 { if (typeof(obj) != 'object') 4323 { obj = {internal_id: obj}; 4324 } 4325 if (!(obj.quantity >= 1)) 4326 { obj.quantity = 1; 4327 } 4328 obj.internal_id += ''; 4329 obj.quantity |= 0; 4330 products_valid.push(obj); 4331 } 4332 } 4333 products = products_valid; 4334 } 4335 // 4336 if (!products || !products.length) 4337 { throw new Error('No products provided'); 4338 } 4339 var add_func = what=='add_to_cart' ? add_to_cart_func : what=='add_to_wishlist' ? add_to_wishlist_func : remove_from_cart_func; 4340 if (!add_func) 4341 { if (what == 'add_to_cart') 4342 { throw new Error('No add to cart function defined in account'); 4343 } 4344 else if (what == 'remove_from_cart') 4345 { throw new Error('No remove from cart function defined in account'); 4346 } 4347 } 4348 var success_html = what=='add_to_cart' ? add_to_cart_success_html : what=='add_to_wishlist' ? add_to_wishlist_success_html : ''; 4349 var error_html = what=='add_to_cart' ? add_to_cart_error_html : what=='add_to_wishlist' ? add_to_wishlist_error_html : ''; 4350 commands_cur_override_action_id = action_id; 4351 commands_cur_manually_reported = false; 4352 var res = add_func ? add_func(ns.clone_obj(products), arg) : Promise.resolve(); 4353 if (!(res instanceof Promise)) 4354 { throw new Error(`${what}() must return Promise object`); 4355 } 4356 var h_done = setTimeout(done, 10000); 4357 return res.then 4358 ( result => 4359 { done(); 4360 if (success_html) 4361 { ns.new_elem(success_html, document.body); 4362 } 4363 if (!commands_cur_manually_reported) 4364 { return new Promise 4365 ( resolve => 4366 { for (var i=0, i_end=products.length; i<i_end; i++) 4367 { let args = [what=='add_to_cart' ? 'Product added to cart' : what=='add_to_wishlist' ? 'Product Liked' : 'Product removed from cart', products[i].internal_id]; 4368 if (action_id > 0) 4369 { args.push('action_id'); 4370 args.push(action_id); 4371 } 4372 if (i == i_end-1) 4373 { args.push(() => {resolve(result)}); 4374 } 4375 ns.push(args); 4376 } 4377 } 4378 ); 4379 } 4380 else 4381 { return result; 4382 } 4383 }, 4384 error => 4385 { done(); 4386 if (error_html) 4387 { ns.new_elem(error_html, document.body); 4388 } 4389 throw error; 4390 } 4391 ); 4392 } 4393 catch (e) 4394 { done(); 4395 return Promise.reject(e.message); 4396 } 4397 } 4398 4399 function has_container_type(container_mask)
4400 { for (let c_id in ns.containers) 4401 { let c = ns.containers[c_id]; 4402 if (c.tp & container_mask) 4403 { return true; 4404 } 4405 } 4406 } 4407 4408 function ga_flush_queue() 4409 { try 4410 { for (let rec; rec = ga_queue.shift();) 4411 { let [category, action, label, value, noninteraction] = rec; 4412 gtag_event(action, label); 4413 } 4414 } 4415 catch (e) 4416 { console.error(e); 4417 } 4418 } 4419 4420 function gtag_event(action, status) 4421 { let parts = action.split(' - '); 4422 let event = parts.shift(); 4423 let name = parts.join(' - '); 4424 if (ga_is_gtm) 4425 { gtag('event', 'personyze_'+event.toLowerCase(), !status ? {name} : {name, status}); 4426 } 4427 else 4428 { // This is old and invalid code, but some clients rely on it 4429 let data = {event, action: name}; 4430 if (status) 4431 { data.status = status; 4432 } 4433 let dl = window[gtm_data_layer_name || 'dataLayer']; 4434 dl.push(data); 4435 } 4436 } 4437 4438 function gtag() 4439 { let dl = window[gtm_data_layer_name || 'dataLayer']; 4440 dl.push(arguments); 4441 } 4442 4443 function gtm_hook() 4444 { if (!gtm_inst && gtm_data_layer_name && (gtm_grab.length || has_container_type(69206016))) 4445 { let data_layer = window[gtm_data_layer_name]; 4446 if (data_layer && typeof(data_layer.push)=='function' && data_layer.push!=Array.prototype.push) 4447 { gtm_inst = data_layer.push; 4448 data_layer.push = function() 4449 { // 1. Sniff 4450 let args = []; 4451 for (let i=0, i_end=arguments.length|0; i<i_end; i++) 4452 { args[i] = arguments[i]; 4453 sniff(args[i]); 4454 } 4455 // 2. Forward 4456 gtm_inst.apply(window[gtm_data_layer_name], args); 4457 }; 4458 for (let i=0, i_end=data_layer.length|0; i<i_end; i++) 4459 { sniff(data_layer[i]); 4460 } 4461 function sniff(arg) 4462 { let n_added = 0; 4463 if (arg) 4464 { // The dataLayer half of _S_T.trigger(): a pushed {event: 'x'} 4465 // fires the custom trigger 'x' under the site's own name. Rides 4466 // this hook rather than wrapping push() a second time — note 4467 // that means it only runs where gtm_hook() installs at all, 4468 // which today is accounts with GTM grabbing configured. 4469 if (typeof(arg.event) == 'string') 4470 { ns.trigger(arg.event); 4471 } 4472 gtm_history.unshift(arg); 4473 if (gtm_history.length > 5) 4474 { gtm_history.pop(); 4475 } 4476 if ('gtm.uniqueEventId' in arg) 4477 { ga_history.unshift(arg); 4478 if (ga_history.length > 5) 4479 { ga_history.pop(); 4480 } 4481 } 4482 try 4483 { // 1. E-commerce 4484 let arg_conv = conv_gtag_event(arg); 4485 let {event} = arg_conv; 4486 let {impressions, items, detail, click, add, remove, checkout, purchase} = arg_conv.ecommerce || {}; 4487 add_products(impressions, 'Product Viewed', 'viewed'); 4488 add_products(detail?.products, 'Product Viewed', 'viewed'); 4489 add_products(click?.products, 'Product Viewed', 'viewed'); 4490 add_products(add?.products, 'Product Added to cart', 'extra'); 4491 add_products(remove?.products, 'Product Removed from cart', 'extra'); 4492 add_products(checkout?.products, 'Product Liked', 'favorite'); 4493 add_products(purchase?.products, 'Product Purchased', 'goal'); 4494 if (RE_LOOKS_LIKE_ADD_TO_CART.test(event)) 4495 { add_products(items, 'Product Added to cart', 'extra'); 4496 } 4497 else if (RE_LOOKS_LIKE_REMOVE_FROM_CART.test(event)) 4498 { add_products(items, 'Product Removed from cart', 'extra'); 4499 } 4500 else if (RE_LOOKS_LIKE_PURCHASE.test(event)) 4501 { add_products(items, 'Product Purchased', 'goal'); 4502 } 4503 else 4504 { add_products(items, 'Product Viewed', 'viewed'); 4505 } 4506 if (n_added == 0) 4507 { zen_add_products(arg_conv, RE_LOOKS_LIKE_VIEWED, 'Product Viewed', 'viewed'); 4508 zen_add_products(arg_conv, RE_LOOKS_LIKE_ADD_TO_CART, 'Product Added to cart', 'extra'); 4509 zen_add_products(arg_conv, RE_LOOKS_LIKE_REMOVE_FROM_CART, 'Product Removed from cart', 'extra'); 4510 zen_add_products(arg_conv, RE_LOOKS_LIKE_PURCHASE, 'Product Purchased', 'goal'); 4511 } 4512 // 2. Containers 4513 for (let c_id in ns.containers) 4514 { let c = ns.containers[c_id]; 4515 if (((c.tp) & 69206016))
4516 { if (!('hi_fn' in c)) 4517 { try 4518 { c.hi_fn = new Function('history', 'return '+c.hi); 4519 } 4520 catch (e) 4521 { c.hi_fn = null; 4522 ns.error(e); 4523 } 4524 } 4525 if (c.hi_fn) 4526 { try 4527 { let value; 4528 if (((c.tp) & 67108864) || ('gtm.uniqueEventId' in arg)) 4529 { value = ns.apply_ghm(c.hi_fn.call(arg, ((c.tp) & 67108864) ? gtm_history : ga_history), c.ghm); 4530 } 4531 if (value) 4532 { function send() 4533 { log_event(c_id, value, LOG_EVENT_VALUE_OVERRIDE|(event_send_with_delay&33554432 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&33554432 ? LOG_EVENT_SEND_NOW : 0), ((c.tp) & 69206016)); 4534 } 4535 // c.tr_clicked - null: !clicked && !data; function: !clicked && data; true: clicked && !data; false: clicked && data (sent) 4536 if (!c.tr || c.tr_clicked===true) 4537 { send(); 4538 c.tr_clicked = false; 4539 } 4540 else if (c.tr_clicked == null) 4541 { c.tr_clicked = send; 4542 } 4543 } 4544 } 4545 catch (e) 4546 { ns.error(e); 4547 } 4548 } 4549 } 4550 } 4551 } 4552 catch (e) 4553 { ns.error(e); 4554 } 4555 } 4556 function conv_gtag_event(arg) 4557 { if (arg.length===3 && arg[0]==='event' && typeof(arg[1])=='string' && typeof(arg[2])=='object') 4558 { return {event: arg[1], ...arg[2]}; 4559 } 4560 else 4561 { return arg; 4562 } 4563 } 4564 function add_products(products, event_name, status) 4565 { let to_add = []; 4566 if (products && gtm_grab.indexOf(status)!=-1) 4567 { for (let i=0, i_end=products.length; i<i_end; i++) 4568 { let product = products[i]; 4569 if (product) 4570 { let id_column = gtm_product_id_column || get_id_field(product); 4571 let product_id = product[id_column]; 4572 if (product_id && (typeof(product_id)=='string' || typeof(product_id)=='number' || typeof(product_id)=='bigint')) 4573 { let data = [event_name, product_id]; 4574 if (product.quantity > 0) 4575 { data.push('Quantity', product.quantity); 4576 } 4577 if (gtm_key) 4578 { data.push('key', gtm_key); 4579 } 4580 to_add.push(data); 4581 n_added++; 4582 } 4583 } 4584 } 4585 } 4586 if (event_name!='Product Viewed' || to_add.length<=1) 4587 { for (let i=0, i_end=to_add.length; i<i_end; i++) 4588 { ns.push(to_add[i]); 4589 } 4590 } 4591 } 4592 function zen_add_products(arg, re, event_name, status) 4593 { let rec = looks_like(looks_like(arg, re), RE_PRODUCT_REC, true); 4594 if (rec) 4595 { add_products([typeof(rec.cloneNode)!='function' ? rec : {[gtm_product_id_column || 'id']: rec.value}], event_name, status); 4596 } 4597 } 4598 } 4599 function looks_like(arg, re, deep, is_get_p) 4600 { if (arg) 4601 { for (let p in arg) 4602 { let v = arg[p]; 4603 if (typeof(v) == 'string') 4604 { if (re.test(deep ? p : v)) 4605 { return is_get_p ? p : arg; 4606 } 4607 } 4608 else if (deep && v && typeof(v)=='object') 4609 { if (typeof(v.cloneNode) != 'function') 4610 { let result = looks_like(v, re, true); 4611 if (result) 4612 { return result; 4613 } 4614 } 4615 else 4616 { let elements = v.form?.elements || [v]; 4617 let found; 4618 let quality = 0; 4619 for (let i=0, i_end=elements.length; i<i_end; i++) 4620 { let {id, name, className, dataset} = elements[i]; 4621 let rec = {id, name, className, ...dataset}; 4622 p = looks_like(rec, RE_PRODUCT_ID, false, true); 4623 if (p && p.length>quality) 4624 { quality = p.length; 4625 found = elements[i]; 4626 } 4627 } 4628 if (found) 4629 { return found; 4630 } 4631 } 4632 } 4633 } 4634 } 4635 } 4636 function get_id_field(rec) 4637 { let found = []; 4638 for (let p in rec) 4639 { if (RE_PRODUCT_ID.test(p)) 4640 { found[found.length] = p; 4641 } 4642 } 4643 found.sort((a, b) => b.length - a.length); 4644 return found[0]; 4645 } 4646 } 4647 } 4648 } 4649 4650 function notification(message, props) 4651 { ns.load_libs 4652 ( 'notifications', 4653 () => 4654 { if (!notifications) 4655 { notifications = _S_T.new_elem('<input class="$notifications">'); 4656 } 4657 notifications._show(message, props); 4658 } 4659 ); 4660 } 4661 4662 function action_notification(a, message, props) 4663 { notification(message, props); 4664 cur_notification_a = a; 4665 } 4666 4667 function set_executed_action_state(a, state) 4668 { executed_actions[a] = state; 4669 if (a==cur_notification_a && state===2) 4670 { notifications._hide(); 4671 } 4672 } 4673 4674 function do_exec_action(a, rid, ondone, override_te, ignore_noacts, is_action_override) 4675 { var bypass_others = false;
4676 var executed = false; 4677 function call_ondone() 4678 { actions_q_done(a, rid); 4679 if (ondone) 4680 { ondone(); 4681 ondone = null; 4682 } 4683 } 4684 function cut_err_msg(message) 4685 { return !message ? '(error)' : (message+'').slice(-100); 4686 } 4687 function onerror(message, remain_filtered, is_expected) 4688 { if (message) 4689 { // Expected outcomes (already shown, no placeholder yet, presenting rules) stay 4690 // at log level. A genuine failure goes to console.error, so it survives a 4691 // console filtered to errors instead of hiding in the site's own log noise. 4692 (is_expected ? logmsg : errmsg)(`Couldn't execute action ${a}: ${message}`); 4693 exec_onerr([a, message]); 4694 if (is_action_override) 4695 { action_notification(a, `Couldn't execute action: ${message}`, {design: 'warning'}); 4696 } 4697 } 4698 call_ondone(); 4699 if (!executed) 4700 { log_event(-11, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&8 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4701 executed = true; 4702 if (ns.act_params[a]) 4703 { ns.act_params[a].no_send_ane = true; // just sent 4704 } 4705 if (!remain_filtered) 4706 { set_executed_action_state(a, (message || 'Error') + ''); 4707 do_whens('onerror', message ? new Error(message) : null); 4708 } 4709 } 4710 log_event(-14, a+' '+cut_err_msg(message), (event_send_with_delay&8 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&8 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4711 } 4712 function do_whens(what, error) 4713 { let whens = when_action_executed[a] || []; 4714 for (let when of whens) 4715 { try 4716 { if (what == 'onload') 4717 { when.onload?.(); 4718 } 4719 else 4720 { when.onerror?.(error); 4721 } 4722 } 4723 catch (e) 4724 { console.error(e); 4725 } 4726 } 4727 } 4728 function action_event_cnt(n, event_name) 4729 { let hit = true; 4730 if (n > 1) 4731 { hit = false; 4732 let name = ({onextra: '_nae', onclose: '_nac'})[event_name] || '_nay'; 4733 let cnts = ns.cookie_obj('_stat_track_s_id')[name] || ''; 4734 let cnt; 4735 cnts = cnts.replace(new RegExp(`((?:^|,)${a}=)(\\d+)`), (a, e, n) => e+(cnt = +n + 1)); 4736 if (!cnt) 4737 { cnts += (cnts ? ',' : '') + `${a}=1`; 4738 } 4739 else 4740 { hit = cnt >= n; 4741 } 4742 ns.cookie_obj('_stat_track_s_id', {[name]: cnts}); 4743 } 4744 return hit; 4745 } 4746 if (executed_actions.hasOwnProperty(a) && typeof(executed_actions[a])!='string') 4747 { if (!is_action_override && executed_actions[a]===2) 4748 { set_executed_action_state(a, 3); 4749 onerror(`Action ${a} already executed on this page`, true, true); 4750 } 4751 return false; 4752 } 4753 if (is_action_override) 4754 { for (let c_id in ns.containers) 4755 { if (ns.containers[c_id].a?.indexOf(a) >= 0) 4756 { setTimeout 4757 ( () => 4758 { if (executed_actions[a]===0 || executed_actions[a]===1) 4759 { onerror(`No placeholder found for the action`, true, true); 4760 } 4761 }, 4762 2000 4763 ); 4764 break; 4765 } 4766 } 4767 } 4768 else if (!ignore_noacts) 4769 { if (a in new SessCnt('noacts').get()) 4770 { onerror(`Action ${a} is asked not to be shown again`, false, true); 4771 return false; 4772 } 4773 if (camp_cap_blocked(a)) 4774 { onerror(`Action ${a} is hidden by a campaign frequency cap`, false, true); 4775 return false; 4776 } 4777 } 4778 set_executed_action_state(a, 0); 4779 function stage_3() 4780 { var params = ns.act_params[a]; 4781 if (params) 4782 { // presenting_rules?
4783 if (!add_action_to_history(a, params.presenting_rules)) 4784 { onerror(`Action ${a} will not be shown this time, according to presenting rules`, false, true); 4785 return; 4786 } 4787 camp_cap_event(a, 'v', 1); // campaign caps that count views at delivery 4788 // 4789 var act_proto = ns.act[params.action_type]; 4790 for (var i in act_proto) 4791 { if (act_proto.hasOwnProperty(i)) 4792 { params[i] = act_proto[i]; 4793 } 4794 } 4795 params.action_id = a; 4796 params.visit_id = rid; 4797 var ev_done = {}; 4798 var ev_done_something = false; 4799 // One confirmed view per execution for the campaign caps: a popup now 4800 // reports both `onshown` and `ondelivered`, and either may legitimately 4801 // be the first to arrive depending on the action type. 4802 var cap_view_counted = false; 4803 params.fire_event = function(event_name, arg, flag) 4804 { if (event_name == 'onerror') 4805 { onerror(arg); 4806 } 4807 executed = true; 4808 params.no_send_ane = true; // executed, or onerror just sent ane 4809 if (!ev_done[event_name] && (event_name=='ongoal' || !ev_done.onclose)) 4810 { let event_name_for_auto_actions = event_name; 4811 switch (event_name) 4812 { case 'ondelivered': 4813 log_event(-21, a+(arg ? ' '+arg : ''), (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4814 if (flag == 'immed-delivered') 4815 { event_name_for_auto_actions = 'onshown'; 4816 } 4817 break; 4818 case 'onproductsdelivered': 4819 // Batched per-item impressions of one widget render, sent by 4820 // install_report_items below; `arg` is the ready &-joined field list. 4821 log_event(-42, arg, (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4822 break; 4823 case 'onarticlesdelivered': 4824 log_event(-43, arg, (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4825 break; 4826 case 'onextra': 4827 ev_done.ondelivered = true; 4828 log_event(-12, a+(arg ? ' '+arg : ''), (event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4829 break; 4830 case 'onclose': 4831 log_event(-13, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&512 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&512 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4832 break; 4833 case 'ongoal': 4834 log_event(-15, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&262144 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&262144 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4835 break; 4836 case 'onshown': 4837 if (params.send_executed_yes && !ev_done_something) 4838 { log_event(-18, a, LOG_EVENT_VALUE_LIST_COMMA|(event_send_with_delay&256 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&256 ? LOG_EVENT_SEND_NOW : 0), 0, rid); 4839 if (!(executed_actions[a] > 0)) 4840 { set_executed_action_state(a, 2); 4841 do_whens('onload'); 4842 } 4843 } 4844 break; 4845 } 4846 if (params.is_blocking) 4847 { unblock_action(a); 4848 } 4849 call_ondone(); 4850 ev_done[event_name] = true; 4851 ev_done[event_name_for_auto_actions] = true; 4852 ev_done_something = true; 4853 /* Campaign frequency caps. A "view" here is a CONFIRMED view, which is 4854 not the same event for every action type: 4855 - in a container: `ondelivered` — fired when 2/3 of the element is in 4856 the viewport, immediately or on the scroll that brings it there. 4857 Its sibling `onshown` (flag 'not-delivered') means "rendered below 4858 the fold", which is precisely not a view, so it must not count. 4859 - popup/bar/anything painted over the page: `onshown` IS the view; 4860 those never fire `ondelivered` at all. 4861 Counting `event_name_for_auto_actions=='onshown'` (what this did first) 4862 got both container cases backwards: it counted renders nobody saw and 4863 skipped the scroll-into-view confirmations. */ 4864 if (!cap_view_counted && (event_name=='ondelivered' || event_name=='onshown' && flag!='not-delivered')) 4865 { cap_view_counted = true;
4866 camp_cap_event(a, 'v', 0); 4867 } 4868 else if (event_name == 'onextra') 4869 { camp_cap_event(a, 'k'); 4870 } 4871 else if (event_name == 'onclose') 4872 { camp_cap_event(a, 'c'); 4873 } 4874 if (params.ga_action_name) 4875 { ns.ga_send('action', params.ga_action_name, event_name.substring(2)); 4876 } 4877 let event_aa = params[event_name_for_auto_actions+'_auto_actions']; 4878 if (event_aa) 4879 { let {dont_show_again, dont_show_again_a, user_data, fire_action, dismiss, goal, custom_event_id, ga_category, ga_action} = event_aa; 4880 // 4881 if (dont_show_again > 0) 4882 { // legacy format 4883 ns.disable_action(a, dont_show_again); 4884 } 4885 else if (dont_show_again) 4886 { let {n, sessions, secs} = dont_show_again; 4887 if (action_event_cnt(n, event_name)) 4888 { ns.disable_action(a, sessions, secs); 4889 } 4890 } 4891 if (dont_show_again_a) 4892 { let {action_ids, n, sessions, secs} = dont_show_again_a; 4893 if (action_ids && (sessions>0 || secs>0)) 4894 { if (action_event_cnt(n, event_name)) 4895 { for (let i=0; i<action_ids.length; i++) 4896 { ns.disable_action(action_ids[i], sessions, secs); 4897 } 4898 } 4899 } 4900 } 4901 if (user_data?.length > 0) 4902 { let ud = {}; 4903 for (let i=user_data.length-1; i>=0; i--) 4904 { ud[user_data[i].name] = ns.subst_user_data_action(user_data[i].value, '', a); 4905 } 4906 ns.social('IN', ud); 4907 } 4908 if (fire_action > 0) 4909 { do_exec_action(fire_action, null, null, null, true); // TODO: ns.push(['Fire action', fire_action]); 4910 } 4911 if (dismiss) 4912 { for (let i=dismiss.length-1; i>=0; i--) 4913 { ns.dismiss_action(dismiss[i]); 4914 } 4915 } 4916 if (goal) 4917 { params.fire_event('ongoal'); 4918 } 4919 if (custom_event_id) 4920 { try 4921 { ns.custom_event(custom_event_id, event_aa.custom_event_value); 4922 } 4923 catch (e) 4924 { ns.error(e); 4925 } 4926 } 4927 /* Destinations for this event. Google Analytics is one of them, 4928 not a fixture: an action can report to Segment alone. 4929 An EMPTY list means the action predates the field, and those 4930 have always gone to Google Analytics -- so empty keeps meaning 4931 exactly that, and nobody's tracking changes under them. */ 4932 /* '-' means the editor was used and every box was cleared: report 4933 nowhere. Distinct from '' -- which is an action saved before this 4934 field existed, and those have always gone to Google Analytics. 4935 Without the distinction, unticking everything would silently keep 4936 sending to GA. */ 4937 let dests = event_aa.ga_dests=='-' ? [] : (event_aa.ga_dests || '').split(',').filter(function(d) {return !!d;}); 4938 let want_ga = event_aa.ga_dests ? dests.indexOf('ga')!=-1 : true; 4939 if (want_ga && ga_category && ga_action) 4940 { ns.do_ga_send(ga_category, ga_action, event_aa.ga_label, event_aa.ga_value, event_aa.ga_noninteraction); 4941 } 4942 let others = dests.filter(function(d) {return d!='ga';}); 4943 /* The other tools have no category/label/value shape, so the 4944 event is named for the action and the GA-ish fields ride along 4945 as properties. They also do not need a category to be usable, 4946 which is why this is not gated on ga_category. */ 4947 if (others.length && ga_action) 4948 { ns.send_event_to(others, ga_action, {category: ga_category || '', label: event_aa.ga_label || '', value: event_aa.ga_value || ''}); 4949 } 4950 } 4951 let event_code = event_aa?.js || params[event_name_for_auto_actions]; 4952 if (event_code) 4953 { event_obj.vid = rid; 4954 event_obj.action_id = a; 4955 event_code = ns.subst_user_data_action(event_code, '', a); 4956 try 4957 { ns.inject_code.call(event_obj, event_code); 4958 } 4959 catch (e) 4960 { ns.error(`Problem during execution of event ${event_name_for_auto_actions} of action ${a} (rid=${rid}). Details: ${e.message}`, e); 4961 } 4962 } 4963 } 4964 }; 4965 function exec() 4966 { try 4967 { bypass_others = params.onexec?.(a, rid, params); 4968 } 4969 catch (e) 4970 { params.fire_event('onerror', e.message); 4971 } 4972 if (!params.reports_onerror) 4973 { /* Each action is encouraged to call this.fire_event('onerror', message) 4974 if it fails to execute. When i receive such signal i report to the server 4975 about action not being executed. Having this.reports_onerror is the promise 4976 to send fire_event (either positive or negative) even in worst case, so i will 4977 feel free to block the page till i receive a feedback. 4978 */ 4979 if (executed_actions[a] === 0) 4980 { set_executed_action_state(a, 1); 4981 do_whens('onload'); 4982 } 4983 call_ondone(); 4984 } 4985 }
4986 var do_after = params.do_after|0; 4987 if (do_after <= 0) 4988 { if (params.is_blocking) 4989 { block_action(a, !!params.reports_onerror); 4990 } 4991 ns.reg_onready(exec); 4992 } 4993 else 4994 { ns.reg_onready 4995 ( () => 4996 { clearTimeout(timers_ac[a]); 4997 timers_ac[a] = setTimeout(exec, do_after*1000); 4998 } 4999 ); 5000 } 5001 } 5002 else 5003 { ns.error(`Problem during execution of action ${a} (rid=${rid}). No params received.`); 5004 } 5005 } 5006 function stage_2() 5007 { let {assets_css, assets_js, js_filenames} = ns.act_params[a]; 5008 let head = document.getElementsByTagName('head')[0]; 5009 let links = document.getElementsByTagName('link'); 5010 let cb = new ns.CallBalancer 5011 ( -1, 5012 () => {ns.load_libs(js_filenames, stage_3, onerror)} 5013 ); 5014 if (assets_css?.length) 5015 { for (let i=0, i_end=assets_css.length; i<i_end; i++) 5016 { let f = assets_css[i]; 5017 if (f.href) 5018 { for (var j=links.length-1; j>=0; j--) 5019 { let link = links[j]; 5020 if (link.href==f.href && (link.media || '')==(f.media || '') && (link.id || '')==(f.id || '') && (link.className || '')==(f.class_name || '')) 5021 { break; 5022 } 5023 } 5024 if (j == -1) 5025 { ns.new_elem 5026 ( 'link', 5027 head, 5028 { rel: 'stylesheet', 5029 type: 'text/css', 5030 href: f.href, 5031 media: f.media || undefined, 5032 id: f.id || undefined, 5033 className: f.class_name || undefined 5034 } 5035 ); 5036 } 5037 } 5038 } 5039 } 5040 if (assets_js?.length) 5041 { js_filenames = ns.clone_obj(js_filenames, 1) || []; 5042 for (let i=0, i_end=assets_js.length; i<i_end; i++) 5043 { js_filenames.push(assets_js[i]); 5044 } 5045 } 5046 cb.inc(); 5047 } 5048 try 5049 { ns.load_libs(a, stage_2, onerror, override_te); 5050 } 5051 catch (e) 5052 { onerror(e.message); 5053 return false; 5054 } 5055 if (bypass_others) 5056 { if (!window.Promise || !(bypass_others instanceof Promise)) 5057 { return true; 5058 } 5059 bypass_others.then(null, err => {onerror(err.message)}); 5060 } 5061 } 5062 5063 function init_debugger(buffer_key, try_debuggers, ondone) 5064 { let win = window._S_T_OPENER || opener || parent; 5065 /* `try_debuggers` is the server's answer, matched on the debugger's IP - and behind the 5066 live proxy that match cannot be relied on: the injected script replaces 5067 XMLHttpRequest with one that routes through proxy.php, so the visit that computes 5068 `try_debuggers` reaches the tracker from OUR server rather than from the browser, 5069 and the IP it is matched on is only as good as X-Forwarded-For end to end. When it 5070 comes back empty the handshake never starts and the page loads with no editor on it. 5071 In proxy mode we do not need that match: `_S_T_PROXY_BUFFER_KEY` was put here by 5072 proxy.php, which now resolves it against a live `st_debuggers_<sr>` row before it 5073 will fetch anything at all - so the key is server-authenticated by the time it gets 5074 here, and it is folded into `try_debuggers` just below. Seamless mode is untouched: 5075 the condition still requires the server's answer there. */ 5076 if (!win || win==self || ns.DEBUGGER_INITED || !try_debuggers && !window['_S_T_PROXY_BUFFER_KEY']) 5077 { ondone(); 5078 } 5079 else 5080 { ns.DEBUGGER_INITED = true; 5081 if (buffer_key) 5082 { try_debuggers = !try_debuggers ? buffer_key : try_debuggers+','+buffer_key; 5083 } 5084 try 5085 { let message_type = 'auth-'+ns.random_id(12); 5086 let listeners = {}; 5087 ns.new_elem(window).add_props 5088 ( { onmessage: 5089 { func(event) 5090 { if (event.data?.message_type == message_type) 5091 { let {debugger_tool, buffer_key, auth} = event.data; 5092 /* The panel's REAL origin, taken from the handshake rather than from 5093 CONF__GUI_HOST. That constant is baked when the templates are compiled, 5094 and stat-track-lib is served from the counter host for every white 5095 label at once - so it always held the DEFAULT gui host, and a session 5096 opened from a white-labelled panel (plugin.personyze.com, 5097 platform.e-marketer.io, ...) was handed an origin that was not the 5098 panel's. Both directions then failed silently: the page posted its 5099 status to a window that is not there, and refused the panel's reply 5100 because the origin did not match - so the editor never received its 5101 value and never started picking. 5102 This is the window we are already taking `auth` from, so trusting its 5103 origin adds no reach; and it still narrows every later message to that 5104 one origin, which '*' would not. 5105 */ 5106 let gui_origin = event.origin && event.origin!='null' ? event.origin : 'https://'+CONF__GUI_HOST; 5107 if (!buffer_key) 5108 { ns.cookie_obj('stat_track_u_id', {dg: ''}); 5109 } 5110 else 5111 { ns.cookie_obj('stat_track_u_id', {dg: buffer_key}); 5112 do_init_debugger(debugger_tool, buffer_key, auth, gui_origin); 5113 listeners.onmessage.remove(); 5114 } 5115 ondone?.(); 5116 ondone = null; 5117 } 5118 }, 5119 listeners 5120 } 5121 } 5122 ); 5123 setTimeout 5124 ( () => 5125 { listeners.onmessage.remove(); 5126 ondone?.(); 5127 }, 5128 10000 5129 ); 5130 win.postMessage({message_type, try_debuggers, server_id: ns.server_id}, '*'); 5131 } 5132 catch (e) 5133 { ns.error(e); 5134 ondone(); 5135 } 5136 } 5137 5138 /* Simulator tools that have been rewritten as Preact bundles (fresh-service/webdriver-apps/src/apps). 5139 Each entry maps a `debugger_tool` to the single global its bundle defines plus the two 5140 build variants of that bundle; a tool that isn't listed here falls through to the legacy 5141 _S_T_DEBUGGER template library below. 5142 Keep in sync with StatTrackApi::setup_filesystem, which is what actually serves the file. 5143 */ 5144 const WEBDRIVER_TOOLS = 5145 { /* The "Find on pages" picker of the new panel's Content Placement screen. 5146 `with_jsapi` for its "Describe this placement" box, which asks the AI service 5147 through the same endpoint the visual editor's assistant uses. 5148 */ 5149 select_placeholder: 5150 { global_name: 'personyzeSimulatorPlaceholder', 5151 with_jsapi: true, 5152 src: "\/\/qacounter.personyze.com\/webdriver\/simulator-placeholder.js?v=1788040102", 5153 src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-placeholder-debug.js?v=1788040102" }, 5154 /* The goal-element picker of the new panel's Conversion Tracking screen. A tool of its 5155 own, not a replacement of `select_container`: that one is still what the old-UI 5156 container editor opens, for containers that are not goals. 5157 `with_jsapi` for its "Describe the conversion" box, which asks the AI service 5158 through the same endpoint the visual editor's assistant uses. 5159 */ 5160 select_goal: 5161 { global_name: 'personyzeSimulatorGoal', 5162 with_jsapi: true, 5163 src: "\/\/qacounter.personyze.com\/webdriver\/simulator-goal.js?v=1788040101", 5164 src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-goal-debug.js?v=1788040101" }, 5165 /* The data-source picker of the new panel's Visitor Attributes screen. Like select_goal, 5166 a tool of its own rather than a replacement of `select_container_grab`, which the 5167 old-UI container editor still opens, and `with_jsapi` for the same reason - its 5168 "Describe what to grab" box. 5169 */ 5170 select_attribute: 5171 { global_name: 'personyzeSimulatorAttribute', 5172 with_jsapi: true, 5173 src: "\/\/qacounter.personyze.com\/webdriver\/simulator-attribute.js?v=1788040101", 5174 src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-attribute-debug.js?v=1788040100" }, 5175 /* The QA Simulator of the new panel's campaign editor. A tool of its own rather than a 5176 replacement of `test_condition`, which the old-UI campaign screen still opens. 5177 `with_jsapi` is what hands a tool the session's SQL endpoint, and hence the auth 5178 token; only the tools that talk to Personyze while they run get it. 5179 */ 5180 qa_simulator: 5181 { global_name: 'personyzeSimulatorQa', 5182 with_jsapi: true, 5183 src: "\/\/qacounter.personyze.com\/webdriver\/simulator-qa.js?v=1788040102", 5184 src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-qa-debug.js?v=1788040102" }, 5185 /* The WYSIWYG Visual editor of the new panel's action editor. Like the others it is a 5186 tool of its own rather than a replacement of `live`, which the old-UI action editor 5187 still opens. `with_jsapi` because its on-click editor lists the account's other 5188 actions and its custom-event containers. 5189 */ 5190 live_editor: 5191 { global_name: 'personyzeSimulatorLive', 5192 with_jsapi: true, 5193 src: "\/\/qacounter.personyze.com\/webdriver\/simulator-live.js?v=1788040102", 5194 src_debug: "\/\/qacounter.personyze.com\/webdriver\/simulator-live-debug.js?v=1788040101" } 5195 }; 5196 5197 function do_init_debugger(debugger_tool, buffer_key, auth, gui_origin) 5198 { if (debugger_tool && (["sim","test_condition","qa_simulator"]).indexOf(debugger_tool)==-1) 5199 { window['_S_T_ACTIONS_DISABLED'] = true; 5200 } 5201 let webdriver_tool = Object.prototype.hasOwnProperty.call(WEBDRIVER_TOOLS, debugger_tool) && WEBDRIVER_TOOLS[debugger_tool]; 5202 if (webdriver_tool) 5203 { do_init_webdriver_debugger(webdriver_tool, buffer_key, auth, gui_origin); 5204 return; 5205 } 5206 let _S_T_DEBUGGER = window['_S_T_DEBUGGER'] = window['_S_T_DEBUGGER'] || {}; 5207 _S_T_DEBUGGER.JSAPI_URL = get_protocol()+"//"+"qacounter.personyze.com"+`/debugger_jsapi.tsx?auth=${auth}&sr=${ns.server_id}`; 5208 _S_T_DEBUGGER.host_id = host_id; 5209 ns.new_elem_bootstrap_to(_S_T_DEBUGGER); 5210 ns.new_elem 5211 ( 'link', 5212 document.getElementsByTagName('head')[0], 5213 { rel: 'stylesheet', 5214 media: 'all', 5215 href: get_protocol()+"\/\/qacounter.personyze.com\/templates\/_S_T_DEBUGGER.css" } 5216 ); 5217 let js = NEW_ELEM_NDEBUG ? "\/\/qacounter.personyze.com\/templates\/_S_T_DEBUGGER.js" : "\/\/qacounter.personyze.com\/templates\/_S_T_DEBUGGER-debug.js"; 5218 ns.load_scripts 5219 ( { [get_protocol()+js]: () => _S_T_DEBUGGER.initialization 5220 }, 5221 e => 5222 { if (e instanceof Error) 5223 { ns.error(e); 5224 } 5225 _S_T_DEBUGGER.new_elem(document).add_props 5226 ( { ontplready() 5227 { _S_T_DEBUGGER.initialization(debugger_tool, buffer_key); 5228 } 5229 } 5230 ); 5231 } 5232 ); 5233 } 5234 5235 /* Load one of the WEBDRIVER_TOOLS bundles and hand it the session. Unl
5235ike _S_T_DEBUGGER 5236 these are self-contained classic scripts with no new_elem bootstrap and no stylesheet 5237 of their own, so there is nothing to load besides the one file. 5238 */ 5239 function do_init_webdriver_debugger(webdriver_tool, buffer_key, auth, gui_origin) 5240 { let {global_name} = webdriver_tool; 5241 /* The "?v=" baked into both URLs is the bundle's mtime at the time the templates were 5242 compiled. That versions releases correctly, but it doesn't move for `make 5243 compile-webdriver`, which writes the served file without recompiling anything - so 5244 on a debug build, where that is the whole dev loop, add a timestamp as well. 5245 */ 5246 let src = get_protocol() + (NEW_ELEM_NDEBUG ? webdriver_tool.src : webdriver_tool.src_debug) + '&t='+Date.now(); 5247 ns.load_scripts 5248 ( { [src]: [window, global_name] 5249 }, 5250 e => 5251 { if (e instanceof Error) 5252 { ns.error(e); 5253 return; 5254 } 5255 let options = 5256 { buffer_key, 5257 gui_origin: gui_origin || 'https://'+CONF__GUI_HOST, 5258 counter_url: get_protocol()+'//'+CONF__COUNTER_WWW_HOSTNAME 5259 }; 5260 if (webdriver_tool.with_jsapi) 5261 { /* The account-cached counter host serves a stale copy of anything per-account, 5262 which is why both the SQL endpoint and the action screenshots go to the 5263 uncached one - the same two hosts the legacy _S_T_DEBUGGER used for these. 5264 */ 5265 let no_cache = get_protocol()+'//'+"qacounter.personyze.com"; 5266 options.jsapi_url = `${no_cache}/debugger_jsapi.tsx?auth=${auth}&sr=${ns.server_id}`; 5267 options.images_url = `${no_cache}/js/${ns.server_id}/images/actions/`; 5268 options.host_id = host_id; 5269 options.server_id = ns.server_id; 5270 } 5271 window[global_name].start(options); 5272 } 5273 ); 5274 } 5275 } 5276 5277 function log_new_tealium_audiences(send_mode=0) 5278 { try 5279 { clearTimeout(tealium_init_timer); 5280 if (tealium_storage_name && window.localStorage) 5281 { let str = localStorage[tealium_storage_name]; 5282 if (str) 5283 { /* A standalone events-only request has nowhere to land until log_visit() has answered: 5284 `cur_visit_id` is 0 until then, and st_log_visit_* refuses events for a session it 5285 holds no visit row for ("this session has no page view on record"). The 600ms poll 5286 below fires SEND_NOW the moment Tealium fills its localStorage, which on a fast tag 5287 is before the page view has round-tripped - that raced request was ~3% of all event 5288 requests on a Tealium-driven site, and the 5-second cookie retry never rescued it, 5289 because every retry repeated the same too-early request. So while the visit is 5290 unknown, keep polling rather than sending; the audiences then ride a request that 5291 has somewhere to land. Past the 20-second budget send anyway, which is what this 5292 did before. Same reason the 'tp' containers wait for `cur_visit_id`. */ 5293 if ((send_mode & LOG_EVENT_SEND_NOW) && !cur_visit_id && Date.now() < tealium_try_init_till) 5294 { tealium_init_timer = setTimeout(() => log_new_tealium_audiences(LOG_EVENT_SEND_NOW), 600); 5295 return; 5296 } 5297 tealium_try_init_till = 0; 5298 let obj = JSON.parse(str); 5299 let audiences = []; 5300 for (let id of Object.keys(obj.audiences || {}).sort()) 5301 { audiences.push(JSON.stringify(id)+':1'); 5302 } 5303 let badges = []; 5304 for (let id of Object.keys(obj.badges || {}).sort()) 5305 { badges.push(JSON.stringify(id)+':1'); 5306 } 5307 let new_tealium_audiences = `[{${audiences.join(',')}},{${badges.join(',')}}]`; 5308 if (new_tealium_audiences != tealium_audiences) 5309 { tealium_audiences = new_tealium_audiences; 5310 log_event(-39, tealium_audiences, send_mode|LOG_EVENT_VALUE_OVERRIDE); 5311 } 5312 } 5313 else if (Date.now() < tealium_try_init_till) 5314 { tealium_init_timer = setTimeout(() => log_new_tealium_audiences(LOG_EVENT_SEND_NOW), 600); 5315 } 5316 } 5317 } 5318 catch (e) 5319 { ns.error(e); 5320 } 5321 } 5322 5323 function cookie_object(name, new_value, set_new_value, attrs) 5324 { name = encodeURIComponent(name); 5325 let obj = {}; 5326 if (!set_new_value) 5327 { if (name=='stat_track_u_id' && stat_track_u_id) 5328 { Object.assign(obj, stat_track_u_id); 5329 } 5330 else if (name=='_stat_track_s_id' && _stat_track_s_id) 5331 { Object.assign(obj, _stat_track_s_id); 5332 } 5333 else 5334 { let str = document.cookie.match(new RegExp('\\b'+name+'=([^;]+)'))?.[1] || window.localStorage?.getItem(name) || ''; 5335 decodeURIComponent(str).replace 5336 ( /([^&=]+)=([^&]*)/g, 5337 (a, k, v) => 5338 { obj[decodeURIComponent(k)] = decodeURIComponent(v); 5339 } 5340 ); 5341 } 5342 } 5343 if (new_value || set_new_value) 5344 { try 5345 { add_props_shallow(obj, new_value); 5346 if (name == 'stat_track_u_id') 5347 { stat_track_u_id = Object.assign({}, obj); 5348 } 5349 else if (name == '_stat_track_s_id') 5350 { _stat_track_s_id = Object.assign({}, obj); 5351 } 5352 let value = ''; 5353 for (let i in obj) 5354 { if (obj[i]!=null && typeof(obj[i])!='function') 5355 { value += (value ? '&' : '') + encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); 5356 } 5357 } 5358 let exp = !new_value ? new Date(1970, 1, 1) : name.charAt(0)=='_' ? null : new Date(Date.now()+63072000000); 5359 exp = exp==null ? '' : ';expires='+exp.toGMTString(); 5360 if (!attrs) 5361 { attrs = {}; 5362 }
5363 let c = name+'='+encodeURIComponent(value)+exp+';path='+(attrs.path || '/')+';domain='+(attrs.domain || location.hostname)+(attrs.secure && ';secure' || ''); 5364 if (c.length <= 4096) 5365 { document.cookie = c; 5366 } 5367 window.localStorage?.setItem(name, value); 5368 } 5369 catch (e) 5370 { ns.error(e); 5371 } 5372 } 5373 return obj; 5374 } 5375 5376 function reapply_html_changes() 5377 { const action_ids = [...not_found_html_changes.keys()]; 5378 for (let action_id of action_ids) 5379 { let hc = not_found_html_changes.get(action_id); 5380 not_found_html_changes.delete(action_id); 5381 ns.apply_html_changes(hc, action_id); 5382 } 5383 } 5384 5385 /** How long this container's element search must still wait before repeating it can tell us 5386 anything new, or 0 for "run it now". See CONT_RETRY_FREE for what the two ceilings mean. 5387 `c.rn` counts the passes that left the container un-inited, `c.rt` is when the last search ran, 5388 and `c.rg` is the DOM generation it saw. 5389 **/ 5390 function conts_retry_wait(c, now) 5391 { let pending = c.tp & ~c.inited; 5392 if (!(pending & CONT_SELECTOR_TYPES) || (pending & ~CONT_SELECTOR_TYPES)) 5393 { return 0; // nothing to search for, or something cheap is still owed alongside it 5394 } 5395 if (c.found_elems || c.found_elems_tr) 5396 { // This one is not searching, it is re-reading: 'grab html', 'grab volatile html' and the 5397 // text-input types are deliberately never marked inited (see the grab_html() call in 5398 // init_container) so that a value which changes on the page is picked up on the next 5399 // pass. Delaying them would lose data, and they are not the expensive case - the 5400 // pathological one is a selector whose element is nowhere on the page. 5401 return 0; 5402 } 5403 if (c.rg !== conts_dom_gen) 5404 { return 0; // the element tree changed since this container looked - the answer can differ, so look now 5405 } 5406 let n = c.rn|0; 5407 if (n <= CONT_RETRY_FREE) 5408 { return 0; 5409 } 5410 let wait = Math.min(CONT_RETRY_STEP * Math.pow(2, Math.min(n-CONT_RETRY_FREE, 10)), CONT_RETRY_IDLE_MAX) - (now - (c.rt || 0)); 5411 return wait>0 ? wait : 0; 5412 } 5413 5414 /** Forget every container's back-off, because the world just changed enough that a selector which 5415 came up empty a moment ago can match now: a new page in an SPA, a new set of actions or 5416 conditions from the server, the testing switch. Callers follow this with init_containers(). 5417 **/ 5418 function reset_conts_retry() 5419 { let {containers} = ns; 5420 for (let c_id in containers) 5421 { if (containers.hasOwnProperty(c_id)) 5422 { containers[c_id].rn = 0; 5423 } 5424 } 5425 } 5426 5427 /** Every DOM change is a reason to look again for the containers we have not placed yet - but not 5428 every DOM change can change the answer. A batch that only rewrote text leaves the element tree 5429 exactly as it was, so no selector can start or stop matching because of it, and re-walking the 5430 document for every container that matches nothing here would buy nothing. On a page whose UI 5431 keeps ticking (a clock, a counter, our own panel) those batches are most of them. 5432 Two things do want a pass even for text alone, and both are asked about rather than assumed: a 5433 container that already found its element and re-reads it on every pass (`conts_read_text`) - 5434 a grabbed price rewritten in place is exactly a characterData change, and dropping it would 5435 lose data - and an html change we have not managed to place, since only a pass re-applies it. 5436 What is left out is the rare selector that reads text without owning an element yet (`:empty`, 5437 or a custom function); that one is retried from the timer pass, within CONT_RETRY_IDLE_MAX. 5438 **/ 5439 function on_conts_mutation(records) 5440 { for (let i=0; i<records.length; i++) 5441 { if (records[i].type != 'characterData') 5442 { conts_dom_gen++; 5443 init_containers(); 5444 return; 5445 } 5446 } 5447 if (conts_read_text || not_found_html_changes.size) 5448 { init_containers(); 5449 } 5450 } 5451 5452 let init_containers = infrequent_func 5453 ( () => 5454 { gtm_hook(); 5455 // containers 5456 clearInterval(conts_timer); 5457 let {containers} = ns; 5458 let want_monitor_containers = false; 5459 let next_pass = CONT_RETRY_IDLE_MAX;
5459 // when the retry timer must come back: the soonest any pending container is due 5460 let now = Date.now(); 5461 let read_text = false; // does any pending container hold an element it re-reads? then text alone matters 5462 for (let c_id in containers) 5463 { let c = containers[c_id]; 5464 if (containers.hasOwnProperty(c_id) && (c.tp & ~c.inited)) 5465 { // c is {hi:html_id, ha:html_attr, hc:html_changes, tp:container_types, mm:match_many, tr_mm:match_many_trigger, ac:action_ids, em:style_override_empty, oc:style_override_occupied, ud:units_direction, uc:units_count_max, cc:cycle_actions_period, wv:watch_variable, udf:user_data_format, ghm:grab_html_mask_regexp, cn:[1,2,-3], a:placed_action_id, rid:record_id, html:html, inited:0, rn:failed_passes, rt:last_pass_time, rg:last_pass_dom_gen} // a, rid and html are assigned when action is put; rn, rt and rg are the back-off (see conts_retry_wait) 5466 let wait = conts_retry_wait(c, now); 5467 if (wait > 0) 5468 { want_monitor_containers = true; // too soon to ask the document the same question again 5469 } 5470 else 5471 { try 5472 { c.rt = now; 5473 c.rg = conts_dom_gen; 5474 want_monitor_containers = init_container(c_id, c, false) || want_monitor_containers; 5475 c.rn = !(c.tp & ~c.inited) ? 0 : (c.rn|0) + 1; 5476 } 5477 catch (e) 5478 { let html_id = c.hi; 5479 if (!ns.cont_errors) ns.cont_errors = {}; 5480 if (!ns.cont_errors[html_id]) ns.cont_errors[html_id] = 0; 5481 if (ns.cont_errors[html_id]++ > 12) 5482 { c.inited = c.tp; 5483 } 5484 ns.error(e); 5485 } 5486 wait = conts_retry_wait(c, now); 5487 } 5488 if (wait < next_pass) 5489 { next_pass = wait; 5490 } 5491 if (c.found_elems || c.found_elems_tr) 5492 { read_text = true; 5493 } 5494 } 5495 } 5496 conts_read_text = read_text; 5497 // html_changes 5498 reapply_html_changes(); 5499 // restart if something remaining undone 5500 if (!conts_observer) 5501 { conts_observer = new MutationObserver(on_conts_mutation); 5502 conts_observer.observe 5503 ( document.body, 5504 { childList: true, 5505 subtree: true, 5506 characterData: true 5507 } 5508 ); 5509 } 5510 if (want_monitor_containers) 5511 { // try to init not inited containers later - as soon as the earliest of them is worth trying again 5512 conts_timer = setInterval(init_containers, Math.max(1500, next_pass)); 5513 } 5514 }, 5515 150 5516 ); 5517 5518 /* Every "the world changed" caller reaches for immediate_call(): the first pass after log_visit 5519 brings the account's actions, the parallel/async bootstrap, the debugger. Those are exactly the 5520 moments when a selector that came up empty can suddenly match, so an immediate pass also drops 5521 the back-off. */ 5522 const conts_immediate_call = init_containers.immediate_call; 5523 init_containers.immediate_call = function() 5524 { reset_conts_retry(); 5525 return conts_immediate_call.apply(this, arguments); 5526 }; 5527 5528 ns.server_id = 0; 5529 ns.uid = null; 5530 ns.si = 0; // session_start_time (server time) 5531 ns.user_data = {}; 5532 ns.act = {}; 5533 ns.act_params = {}; 5534 ns.is_te_ip = 0; // switch 2, loaded from '_te_ip' by init_testing_mode() - see ns.te_ip()
5535 ns.SessCnt = SessCnt; 5536 ns.obj_follow_path = obj_follow_path; 5537 ns.rollback = rollback; 5538 ns.init_containers = init_containers; 5539 5540 // Old actions use these: 5541 ns.json_encode = function(value) 5542 { return JSON.stringify(value); 5543 }; 5544 ns.json_decode = function(str) 5545 { if (str==null || str==='') return str; 5546 return JSON.parse(str); 5547 }; 5548 5549 // DEPRECATED: instead use: ns.push(['Fire action', action_id]) 5550 ns.fire_action = function(action_id, ondone) 5551 { do_exec_action(action_id, null, ondone, null, true); 5552 }; 5553 5554 ns.get_ps = function(add_cur_sid) 5555 { var sessions = {}; 5556 if (add_cur_sid) 5557 { sessions[add_cur_sid] = 1; 5558 } 5559 sessions = ns.storage_obj('stat_track_sessions', sessions); 5560 var sessions_arr = []; 5561 for (var prop in sessions) 5562 { var s = prop|0; 5563 if (s) sessions_arr.push(s); 5564 } 5565 sessions_arr.sort((a, b) => b-a); // descending order 5566 // delete records beyond WConf::$n_remember_past_sessions_time 5567 var new_sessions = {}; 5568 for (var s=0; s<12 && s<sessions_arr.length; s++) 5569 { new_sessions[sessions_arr[s]] = 1; 5570 } 5571 ns.storage_obj('stat_track_sessions', new_sessions, true); 5572 // done 5573 return sessions_arr; 5574 }; 5575 5576 /* The refusal lives in its OWN cookie, not inside `stat_track_u_id`. 5577 5578 A record of "this visitor said no" is the one thing you may store without consent -- you 5579 cannot honour a refusal you are not allowed to remember, so it is strictly necessary for a 5580 service the visitor asked for. The tracking identifier is not. Keeping the flag inside the 5581 tracking cookie meant declining still left one behind, which is why the privacy docs had to 5582 admit "the cookie is set even for visitors who opt out". Now an opted-out visitor carries 5583 nothing but this flag. 5584 5585 Deliberately no identifier in it, and no other field: the moment it holds anything else it 5586 stops being exempt. */ 5587 const OPT_OUT_COOKIE = 'stat_track_off'; 5588 5589 ns.system = 5590 { is_on() 5591 { /* The legacy flag is still READ, forever. Every visitor who opted out before this 5592 change has their refusal in `stat_track_u_id.off` and nowhere else; ignoring it 5593 would silently opt them all back IN, which is the worst possible regression here -- 5594 invisible, and precisely against the wishes they expressed. It is never WRITTEN 5595 again, so the old location drains away as those visitors return. */ 5596 return ns.cookie_obj(OPT_OUT_COOKIE).off!='1' && ns.cookie_obj('stat_track_u_id').off!='1'; 5597 }, 5598 5599 _set(on, ondone) 5600 { ns.load_libs 5601 ( 'index', 5602 () => 5603 { ns.cookie_obj(OPT_OUT_COOKIE, {off: on ? '' : 1}); 5604 /* Opting back IN must also clear the legacy flag, or the read above keeps 5605 returning false and the switch appears not to work. Only ever cleared, 5606 never set. */ 5607 if (on) 5608 { ns.cookie_obj('stat_track_u_id', {off: ''}); 5609 } 5610 ondone?.(); 5611 } 5612 ); 5613 }, 5614 5615 on(ondone) 5616 { this._set(1, ondone); 5617 }, 5618 5619 off(ondone) 5620 { this._set(0, ondone); 5621 rollback(); 5622 } 5623 }; 5624 5625 ns.push = function() 5626 { for (var i=0; i<arguments.length; i++) 5627 { var command = arguments[i]; 5628 if (command) 5629 { if (commands_cur_override_action_id > 0) 5630 { if (!commands_override_action_id_for) 5631 { commands_override_action_id_for = {}; 5632 } 5633 commands_override_action_id_for[commands_queue.length] = commands_cur_override_action_id; 5634 if (typeof(command[0])=='string' && command[0].toLowerCase().indexOf('product')!=-1)
5635 { commands_cur_manually_reported = true; 5636 } 5637 } 5638 if (commands_queue.length == 0) 5639 { if (window.queueMicrotask) 5640 { queueMicrotask(exec_commands); 5641 } 5642 else 5643 { setTimeout(exec_commands, 0); 5644 } 5645 } 5646 commands_queue.push(command); 5647 } 5648 } 5649 }; 5650 5651 ns.end_session = function() 5652 { want_end_session = true; 5653 }; 5654 5655 ns.log_navigation = function() 5656 { /* Sent NOW, not on the account's 'click event page' send mode. Those modes exist to batch 5657 events - a click, a grab - where arriving a few seconds later costs nothing. This is not 5658 one of those: it is the SPA's page view, and until the server has answered it the new 5659 page has no actions at all, so the delay is not a delay in reporting but in showing. 5660 5219 has 'click event page' set to send-with-delay, which is event_load_timeout = 6s: on 5661 half the navigations the form appeared 4.7-6.7s after the visitor arrived (measured, 8 5662 trials), and on the other half something else happened to flush the batch early and it 5663 came in ~230ms. That is exactly the "shows about half the time" the customer reported - 5664 the campaign was never missing, it was late, and a visitor does not wait 6s. 5665 A full page load never had this problem because its log-visit goes out with the load 5666 itself. An SPA navigation is the same event and now travels the same way. 5667 LOG_EVENT_SEND_NOW rather than SEND_IMMED: it still lets events raised in this same tick 5668 ride along, and it clears any pending send_timer instead of queueing behind it. */ 5669 want_new_navigation = true; 5670 log_event('-17', 1, LOG_EVENT_SEND_NOW, 16); 5671 }; 5672 5673 ns.add_style = function(elem, css) 5674 { ns.new_elem(elem).add_style(css); 5675 }; 5676 5677 ns.id = function(id, find_all, ctx_elem) 5678 { if (id && typeof(id)=='object') 5679 { return ns.qelem(id, find_all); 5680 } 5681 id = !id ? '' : (id+'').trim(); 5682 if (id.charAt(0) == '[') 5683 { return ns.qelem(JSON.parse(id), find_all); 5684 } 5685 if (!id) return find_all ? [] : null; 5686 if (RE_CSS_ID.test(id)) 5687 { id = `${id},#${id},*[name="${id}"]`; 5688 } 5689 return ns.joyquery(id, ctx_elem).get(find_all ? null : 0); 5690 }; 5691 5692 ns.qelem = function(qelem_id, find_all, root_node) 5693 { const P_CLASS=0, P_ATTRIBUTES=1, P_STYLE=2; 5694 if (typeof(qelem_id) == 'string') 5695 { return ns.id(qelem_id, find_all); 5696 } 5697 if (qelem_id.length == 0) 5698 { return find_all ? [root_node] : root_node; 5699 } 5700 function subiter(elem, parent_score, qelem_id, step, return_found) 5701 { var node_name = qelem_id[step][0]; 5702 var node_index = qelem_id[step][1]; 5703 var diff = qelem_id[step][2]; 5704 for (var e=elem?.firstChild, i=0; e; e=e.nextSibling) 5705 { var cur_node_name = e.nodeName.toLowerCase(); 5706 if (cur_node_name == 'tbody') 5707 { subiter(e, parent_score, qelem_id, step, return_found); 5708 } 5709 else if (cur_node_name == node_name) 5710 { var score = 0.1; // node_name matched 5711 var max_score = 0.6; // +0.1 point for node_name matched, +0.5 point for node_index match 5712 if (diff) 5713 { for (var j=0; j<diff.length; j++) 5714 { var prop_name = diff[j][0]; 5715 var prop_subname = diff[j][1]; 5716 var value = diff[j][2]; 5717 var my_score = 0; 5718 var match = false;
5719 if (prop_name == P_CLASS) 5720 { my_score = value ? 1 : 0.5; 5721 match = ((' '+e.className+' ').toLowerCase().indexOf(' '+prop_subname+' ')!=-1) ^ !value; 5722 } 5723 else if (prop_name == P_ATTRIBUTES) 5724 { my_score = value!=null ? 1 : 0.5; 5725 match = e.getAttribute(prop_subname) == value; 5726 } 5727 else if (prop_name == P_STYLE) 5728 { my_score = 1; 5729 match = e.style[prop_subname] == value; 5730 } 5731 max_score += my_score; 5732 if (match) score += my_score; 5733 } 5734 } 5735 if (i++ == node_index) 5736 { score += 0.5; // node_index matched 5737 } 5738 return_found.push({elem:e, score:score/max_score*parent_score}); 5739 } 5740 } 5741 } 5742 function iter(elem, parent_score, qelem_id, step) 5743 { var found = []; 5744 subiter(elem, parent_score, qelem_id, step, found); 5745 if (step+1 >= qelem_id.length) 5746 { return found; 5747 } 5748 var final_found = []; 5749 for (var i=0; i<found.length; i++) 5750 { final_found = final_found.concat(iter(found[i].elem, found[i].score, qelem_id, step+1)); 5751 } 5752 return final_found; 5753 } 5754 var elem; 5755 if (qelem_id.length) 5756 { var first_tag = qelem_id[0][0]; 5757 var found = first_tag.charAt(0)!='#' ? 5758 iter(root_node || document.body, 1.0, qelem_id, 0) : 5759 iter(document.getElementById(first_tag.substring(1)), 1.0, qelem_id, 1); 5760 found.sort((a, b) => b.score - a.score); 5761 elem = found[0]?.elem; 5762 } 5763 return !find_all ? elem : elem ? [elem] : []; 5764 }; 5765 5766 ns.qelem_id_to_css = function(qelem_id) 5767 { const P_CLASS=0, P_ATTRIBUTES=1, P_STYLE=2; 5768 if (typeof(qelem_id) == 'string') 5769 { return qelem_id; 5770 } 5771 var css = 'body'; 5772 for (var i=0, j=qelem_id.length; i<j; i++) 5773 { var tag = qelem_id[i][0]; 5774 var index = qelem_id[i][1]; 5775 var distinct = qelem_id[i][2]; 5776 if (i==0 && tag.charAt(0)=='#') 5777 { css = tag; 5778 continue; 5779 } 5780 var classes='', attrs=''; 5781 if (distinct) 5782 { for (var m=0, n=distinct.length; m<n; m++) 5783 { var prop_name = distinct[m][0]; 5784 var prop_subname = distinct[m][1]; 5785 var value = distinct[m][2];
5786 if (prop_name == P_CLASS) 5787 { if (value) 5788 { classes += '.'+prop_subname; 5789 } 5790 } 5791 else if (prop_name == P_ATTRIBUTES) 5792 { if (value) 5793 { attrs += '['+prop_subname+'="'+value+'"]'; 5794 } 5795 } 5796 } 5797 } 5798 if (tag == 'tr') 5799 { css += ' > tbody'; 5800 } 5801 css += ' > ' + tag + classes + attrs; 5802 css += ':nth-of-type(' + (index+1) + ')'; 5803 } 5804 return css; 5805 }; 5806 5807 /** The action ran, and has nothing to show on this page. Release the page, report nothing. 5808 5809 An action that declares `reports_onerror` is trusted to signal either way, and the page is 5810 held until it does - `blocks[action_id] = 2`, which the onready sweep deliberately will NOT 5811 clear, because an attached action is exactly the one worth waiting for. Firing any event 5812 releases it (see the `is_blocking` branch in fire_event). 5813 5814 Which left one case with no way out. The WYSIWYG page editor's types stopped firing 5815 `onshown` when every change had been scoped away - correctly, since the action did not 5816 appear and reporting it as shown is how a campaign gets an impression count nobody can 5817 explain. But firing the event was ALSO the only thing that released the block, so a scoped-out 5818 action went on holding the pre-hide stylesheet for the whole page - every container, and 5819 every selector in `actions_qelem_ids`, none of which it had anything to do with - until 5820 `force_unblock_all` fired a second later. A visible hold on the header of a page whose 5821 actions were all for somewhere else. Reported from personyze.com (Jacob, 27 Aug 26). 5822 5823 So: the promise is kept, and the report is not made. Two different things that had been 5824 one line. 5825 **/ 5826 ns.action_did_not_appear = function(action_id) 5827 { unblock_action(action_id); 5828 unblock_all(); 5829 }; 5830 5831 ns.apply_html_changes = function(html_changes, action_id) 5832 { function subst_attrs(attrs, except) 5833 { if (!attrs) return null; 5834 let c_attrs = {}; 5835 for (let i in attrs) 5836 { if (attrs.hasOwnProperty(i) && i!=except) 5837 { c_attrs[i] = ns.subst_user_data_action(attrs[i], '', action_id); 5838 } 5839 } 5840 return c_attrs; 5841 } 5842 function put_elem(elem, put_elem, where) 5843 { ns.new_elem(put_elem).add_to_parent(where=='before' ? {nextSibling: elem} : where=='after' ? {previousSibling: elem} : elem); 5844 } 5845 function textToCanonical(text) 5846 { return text.trim().replace(RE_SPACES_G, ' ').toLowerCase(); 5847 } 5848 let last_add_elem = null; 5849 let set_img_src = []; 5850 let can_continue = true; 5851 // How many changes this page is not meant for. See where it is read, below. 5852 let n_scoped_out = 0; 5853 for (let i=0; i<html_changes.length; i++) 5854 { let change = html_changes[i]; 5855 // Recorded for one URL, and this is not it. Nothing to wait for, so it is skipped 5856 // outright rather than parked in not_found_html_changes to be retried forever. 5857 if (!scope_allows_page(change)) 5858 { n_scoped_out++; 5859 continue; 5860 } 5861 let elems = !can_continue ? [] : ns.qelem(change.qelem_id, true); 5862 if (change.scope=='shape' && change.scope_fp && elems.length) 5863 { // A plain loop, not filter(): what qelem() returns is array-LIKE, and has been 5864 // read as such everywhere else in this function. 5865 let same = []; 5866 for (let j=0; j<elems.length; j++) 5867 { if (scope_fp_matches(elems[j], change.scope_fp)) 5868 { same.push(elems[j]); 5869 } 5870 } 5871 if (same.length == 0) 5872 { // The selector found something, but not the thing that was edited. That is 5873 // the case this whole mechanism exists for: apply nothing, and do not retry. 5874 n_scoped_out++; 5875 continue; 5876 } 5877 elems = same; 5878 } 5879 if (elems.length == 0) 5880 { change.action_id = action_id; 5881 let hc = not_found_html_changes.get(action_id); 5882 if (!hc) 5883 { not_found_html_changes.set(action_id, (hc=[])); 5884 } 5885 hc.push(change); 5886 } 5887 else if (change.delay) 5888 { let {delay} = change; 5889 if (i+1 < html_changes.length) 5890 { let ms = delay.ms|0; 5891 if (ms>0 && !delay._till) 5892 { delay._till = Date.now() + ms; 5893 setTimeout(reapply_html_changes, ms); 5894 } 5895 can_continue = elems.length>0 && (!delay._till || Date.now()>=delay._till); 5896 if (can_continue && delay.req_text!=null) 5897 { can_continue = false;
5898 for (let j=0; j<elems.length; j++) 5899 { if (!delay.req_text || textToCanonical(elems[j].textContent).indexOf(textToCanonical(delay.req_text)) != -1) 5900 { can_continue = true; 5901 break; 5902 } 5903 } 5904 } 5905 if (!can_continue) 5906 { if (elems.length==0 && !delay._hobs) 5907 { delay._hobs = new MutationObserver 5908 ( () => 5909 { let new_elems = ns.qelem(change.qelem_id, true); 5910 if (new_elems.length > 0) 5911 { delay._hobs.disconnect(); 5912 reapply_html_changes(); 5913 } 5914 } 5915 ); 5916 delay._hobs.observe(document.body, {childList: true, subtree: true}); 5917 } 5918 i--; // also add "delay" record to `not_found_html_changes` 5919 } 5920 } 5921 } 5922 else 5923 { for (let j=0; j<elems.length; j++) 5924 { last_add_elem = apply_html_change_on_elem(elems[j], change, action_id, set_img_src) || last_add_elem; 5925 } 5926 } 5927 } 5928 /* WAS THIS PAGE ONE OF THE PAGES THIS ACTION IS FOR? 5929 5930 Read by the WYSIWYG page editor's own types, which fire `onshown` after applying - 5931 and were firing it even on a page where every change had been scoped away, so an 5932 action that did nothing at all still reported as shown. Same rule AI Chat follows: 5933 the event says the action APPEARED, so it waits until it did. 5934 5935 Only "all of them", never "some": a change held back because its element has not 5936 rendered yet is a different thing, and it is retried. 5937 **/ 5938 ns.last_apply_all_scoped_out = html_changes.length>0 && n_scoped_out==html_changes.length; 5939 5940 for (let i=set_img_src.length-1; i>=0; i--) 5941 { let {elem, src} = set_img_src[i]; 5942 if (elem.style.visibility != 'hidden') 5943 { elem.style.visibility = 'hidden'; 5944 let listeners = {}; 5945 function onload() 5946 { elem.style.visibility = 'visible'; 5947 ns.deferred 5948 ( () => 5949 { listeners.onload.remove(); 5950 listeners.onerror.remove(); 5951 } 5952 ); 5953 } 5954 ns.new_elem(elem).add_props 5955 ( { onload: 5956 { func: onload, 5957 listeners 5958 }, 5959 onerror: 5960 { func: onload, 5961 listeners 5962 } 5963 } 5964 ); 5965 setTimeout(onload, 1500); 5966 } 5967 elem.src = src; 5968 } 5969 return last_add_elem; 5970 }; 5971 5972 ns.cookie_obj = function(name, new_value, set_new_value) 5973 { let attrs = {domain: get_cookie_domain()}; 5974 if (set_new_value) 5975 { ns.delete_cookie(name); 5976 } 5977 return cookie_object(name, new_value, set_new_value, attrs); 5978 }; 5979 5980 ns.storage_obj = function(name, new_value, set_new_value) 5981 { if (!window.localStorage) 5982 { let attrs = {domain: get_cookie_domain()}; 5983 return cookie_object(name, new_value, set_new_value, attrs); 5984 } 5985 let obj = {}; 5986 if (!set_new_value) 5987 { (localStorage[name] || '').replace 5988 ( /([^&=]+)=([^&]*)/g, 5989 (a, k, v) => 5990 { obj[decodeURIComponent(k)] = decodeURIComponent(v); 5991 } 5992 ); 5993 } 5994 if (new_value || set_new_value) 5995 { if (new_value) 5996 { for (let i of Object.keys(new_value)) 5997 { obj[i] = new_value[i]; 5998 } 5999 } 6000 let value = ''; 6001 for (let i of Object.keys(obj)) 6002 { if (obj[i]!=null && typeof(obj[i])!='function') 6003 { value += (value ? '&' : '') + encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); 6004 } 6005 } 6006 if (!value) 6007 { localStorage.removeItem(name); 6008 } 6009 else 6010 { localStorage.setItem(name, value); 6011 } 6012 } 6013 return obj; 6014 }; 6015 6016 /** Simulator overrides this function. 6017 **/ 6018 ns.get_location = function() 6019 { return location; 6020 }; 6021 6022 /** Simulator overrides this function. 6023 **/ 6024 ns.set_location = function(href) 6025 { location.href = href; 6026 }; 6027 6028 /** Simulator overrides this function. 6029 **/ 6030 ns.get_referrer = function() 6031 { return document.referrer; 6032 }; 6033 6034 ns.delete_cookie = function(name) 6035 { if (!name) 6036 { ns.delete_cookie('_stat_track_s_id'); 6037 ns.delete_cookie('stat_track_u_id'); 6038 } 6039 else 6040 { var domain = location.hostname; 6041 while (true) 6042 { var c = name+'=;expires='+new Date(1970, 1, 1).toGMTString()+';path=/;domain=.'+domain; 6043 document.cookie = c; 6044 if (document.cookie.indexOf(name+'=') == -1) break; 6045 var c = name+'=;expires='+new Date(1970, 1, 1).toGMTString()+';path=/;domain='+domain; 6046 document.cookie = c; 6047 if (document.cookie.indexOf(name+'=') == -1) break; 6048 domain = domain.split('.'); 6049 domain.splice(0, 1); 6050 if (domain.length < 2) 6051 { ns.error('FAILED TO DELETE COOKIE'); 6052 break; 6053 } 6054 domain = domain.join('.'); 6055 } 6056 } 6057 }; 6058 6059 ns.get_param = function(name, href) 6060 { if (!href && !use_location) 6061 { use_location = new ns.Location(ns.get_location().href); 6062 } 6063 var loc = !href ? use_location : new ns.Location(href); 6064 var i; 6065 if (name == '//') 6066 { return loc.hostname; 6067 } 6068 else if (name == '/') 6069 { return loc.pathname; 6070 } 6071 else if (name.charAt(0)=='/' && !isNaN(i = name.substring(1)-0) && i>0) 6072 { return loc.pathname.split('/')[i]; 6073 } 6074 else if (name == '#') 6075 { var hash = !href ? location.hash : loc.hash; 6076 return hash ? hash.substring(1) : undefined; 6077 } 6078 else if (name == '///?') 6079 { return '//'+loc.hostname+loc.pathname+loc.search; 6080 } 6081 else 6082 { return loc.params()[name]; 6083 } 6084 }; 6085 6086 ns.url_replace_param = function(url, name, value) 6087 { var loc = new ns.Location(url); 6088 var set_params = {}; 6089 set_params[name] = value; 6090 loc.params(set_params); 6091 return loc.href; 6092 }; 6093 6094 ns.add_e = function(elem, onwhat, listener, event_name) 6095 { var obj = {}; 6096 var listeners = {}; 6097 obj[onwhat] = {func: listener, listeners, capture: true}; 6098 ns.new_elem(elem).add_props(obj);
6099 var event = listeners[onwhat]; 6100 if (event_name) 6101 { event_name = onwhat+'\t'+event_name; 6102 var cur; 6103 if (!elem._S_T_EVT) 6104 { elem._S_T_EVT = {}; 6105 } 6106 else if ((cur = elem._S_T_EVT[event_name])) 6107 { cur.remove(); 6108 } 6109 elem._S_T_EVT[event_name] = event; 6110 } 6111 return event; 6112 }; 6113 6114 ns.reg_onready = function(func) 6115 { ns.new_elem(document).add_props({ontplready: func}); 6116 }; 6117 6118 // DEPRECATED 6119 // TODO: remove 6120 ns.cut_scripts = function(html, scripts) 6121 { html = (html+'').replace 6122 ( RE_CUT_SCRIPTS, 6123 function($0, $1, $2) 6124 { var src; 6125 if ($1 && ((src=$1.match(/src=(["'])(.*?)\1/i)) || (src=$1.match(/src(=)(\S+)/i)))) 6126 { scripts.push(ns.new_elem('script', null, {src: src[2]})); 6127 } 6128 else if ($2) 6129 { scripts.push($2); 6130 } 6131 return $0; 6132 } 6133 ); 6134 return html; 6135 }; 6136 6137 // DEPRECATED 6138 // TODO: remove 6139 ns.exec_cut_scripts = function(elem, scripts) 6140 { for (var i=0; i<scripts.length; i++) 6141 { if (scripts[i].cloneNode) 6142 { elem.appendChild(scripts[i]); 6143 } 6144 else 6145 { // TODO: remove scripts as text 6146 var global_exec = window.execScript || function(js) {eval.call(window, js)}; 6147 global_exec(scripts[i]); 6148 } 6149 } 6150 }; 6151 6152 ns.dismiss_action = function(action_id) 6153 { return !!do_clear_actions([action_id])[0]; 6154 }; 6155 6156 ns.get_custom_data = function(d, ic) 6157 { if(!d) return {}; 6158 var cds = JSON.parse(d); 6159 var custom_data = {}; 6160 for(var i in cds) { 6161 var cd = cds[i]; 6162 if(!cd) continue; 6163 var _i = cd.i; 6164 if(cd['type'] == 'selector') { 6165 var val = ns.get_page_grab_val['_'+cd.from](cd.val); 6166 } else if (ic) { 6167 var val = ns.containers[cd.val] ? ns.containers[cd.val].vl : null; 6168 } 6169 if(typeof val != 'undefined') custom_data[_i] = val ? val : cd['default']; 6170 6171 } 6172 return custom_data; 6173 }; 6174 6175 ns.get_page_grab_val = 6176 { _html(v){ 6177 var obj = ns.id(v); 6178 return ns.inner_text(obj); 6179 }, 6180 _meta(v){ 6181 var name = JSON.stringify(v); 6182 var e = ns.joyquery(`meta[name=${name}], meta[property=${name}]`).get(0); 6183 return e?.content || ''; 6184 }, 6185 _cookie(v){ 6186 return ns.get_cookie(v); 6187 }, 6188 _lstorage(v){ 6189 return window.localStorage?.[v]; 6190 }, 6191 _sstorage(v){ 6192 return window.sessionStorage?.[v]; 6193 }, 6194 _var(v){ 6195 return obj_follow_path(window, v); 6196 }, 6197 _url(cd){ 6198 return ns.get_param(cd.val); 6199 } 6200 }; 6201 6202 ns.apply_ghm = function(text, regexp) // Apply "grab_html_mask_regexp" on text. Will catch first reference, e.g., if applying "\s*(.*?)\s*" on " foobar " will return "foobar". If the first char of mask is '*', it's not a part of PCRE, but it means negative assertion - will replace matched fragment(s) with empty string and return what is remained. 6203 { text = !text ? '' : (text+'').trim(); 6204 if ((regexp = (regexp || '')+'')) 6205 { if (regexp == 'double') 6206 { // TODO: parse locale number 6207 let m = text.match(RE_GHM_NUMBER); 6208 text = !m || m[1]==null ? '' : m[1]; 6209 } 6210 else if (regexp == 'datetime') 6211 { // TODO: parse locale date 6212 } 6213 else if (regexp.charAt(0) != '*') 6214 { let m = text.match(new RegExp(regexp)); 6215 text = !m || m[1]==null ? '' : m[1]; 6216 } 6217 else 6218 { text = text.replace(new RegExp(regexp.substring(1), 'g'), ''); 6219 } 6220 } 6221 text = text.trim().replace(RE_SPACES_G, ' '); 6222 return text.substr(0, 150).trim(); 6223 }; 6224 6225 ns.inner_text = function(elem) 6226 { return !elem ? '' : typeof(elem.value)=='string' ? elem.value : elem.textContent; 6227 }; 6228 6229 // DEPRECATED: use `ns.scripts_to_ready()` 6230 // TODO: remove 6231 ns.html_scripts_to_ready = function(elem, cut_scripts) 6232 { if (elem.nodeType == XML_ELEMENT_NODE) 6233 { let scripts = elem.getElementsByTagName('SCRIPT'); 6234 for (let i=0, i_end=scripts.length; i<i_end; i++) 6235 { let script = scripts[i]; 6236 if (!script.src) 6237 { script.setAttribute('ontplready', (window.execScript ? 'execScript(this.textContent);' : 'eval.call(window, this.textContent);')+(script.getAttribute('ontplready') || '')); 6238 } 6239 else if (cut_scripts) 6240 { let {src, async, onload, onerror} = script; 6241 cut_scripts[cut_scripts.length] = ns.new_elem('script', null, {src, async, onload, onerror}); 6242 } 6243 } 6244 } 6245 }; 6246 6247 ns.scripts_to_ready = function(elem, ondone) 6248 { if (elem.nodeType === XML_ELEMENT_NODE) 6249 { let html = ''; 6250 let scripts = elem.getElementsByTagName('SCRIPT'); 6251 for (let i=0, i_end=scripts.length; i<i_end; i++) 6252 { html += scripts[i].outerHTML; 6253 } 6254 if (html) 6255 { ns.inject_code(html, false, ondone); 6256 return; 6257 } 6258 } 6259 ondone?.(); 6260 }; 6261 6262 ns.do_put_html = function(elem, html, css, html_changes, action, with_uacss, n_action, reverse_order, animation) 6263 { if (html && !html.cloneNode) 6264 { html += ''; 6265 if (!html) throw new Error('Nothing to show'); 6266 } 6267 let to_back = []; 6268 let created_path = null; 6269 function select_child(elem, possible_node_names, node_name) 6270 { let created = null; 6271 let child = elem && 6272 ( ns.new_elem(elem).select_child_elems(possible_node_names)[0] || 6273 ( created = ns.new_elem(node_name, elem) 6274 ) 6275 ); 6276 created_path = created_path || created; 6277 return child; 6278 } 6279 let want_add_before = html_changes?.ae_where == 'before'; 6280 let want_add_after = html_changes?.ae_where == 'after'; 6281 let want_replace = html_changes?.ae_where == 'replace'; 6282 let tag = elem.nodeName; 6283 let is_not_node = !html.cloneNode; 6284 switch (tag) 6285 { case 'TBODY': 6286 elem = select_child(elem, 'TR', 'TR'); 6287 case 'TR': 6288 elem = select_child(elem, 'TH,TD', 'TD'); 6289 break; 6290 case 'TABLE':
6291 case 'IMG': 6292 case 'HR': 6293 case 'METER': 6294 case 'PROGRESS': 6295 case 'IFRAME': 6296 case 'EMBED': 6297 case 'VIDEO': 6298 case 'OBJECT': 6299 case 'CANVAS': 6300 case 'SVG': 6301 case 'UL': 6302 case 'OL': 6303 case 'MENU': 6304 want_replace = !want_add_before && !want_add_after; 6305 break; 6306 case 'INPUT': 6307 case 'TEXTAREA': 6308 case 'SELECT': 6309 var want_into_value = is_not_node; 6310 break; 6311 } 6312 let content_elem = elem; 6313 if (want_into_value) 6314 { elem.value = ns.new_elem('div', null, {innerHTML: html}).textContent; 6315 } 6316 else 6317 { if (is_not_node) 6318 { let tmp = document.createElement(html_changes?.add_elem || 'font'); 6319 tmp.innerHTML = html; 6320 ns.new_elem(tmp).elem_trim(); 6321 if (tmp.firstChild == tmp.lastChild) 6322 { html = tmp.firstChild; 6323 } 6324 else 6325 { html = tmp; 6326 } 6327 if (animation) 6328 { if (!html.classList.contains('$switch-elem')) 6329 { html.classList.add('$switch-elem'); 6330 let inner = ns.new_elem('div', null, {attributes: {'data-case': 'on', 'data-prepared': 1}, innerHTML: html.childNodes}); 6331 html.appendChild(inner); 6332 } 6333 else 6334 { animation = null; 6335 } 6336 } 6337 } 6338 content_elem = html; 6339 html._st__action = action || null; 6340 let placeholder_order = (action?.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - 0; 6341 let p_action_id = action?.action_id | 0; 6342 if (want_replace || want_add_before || want_add_after) 6343 { let ref_node = elem; 6344 let a, p; 6345 if (want_add_before) 6346 { while ((a = ref_node.previousSibling?._st__action) && ((p = (a.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - placeholder_order)>0 || p==0 && a.action_id>p_action_id)) 6347 { ref_node = ref_node.previousSibling; 6348 } 6349 } 6350 else if (want_add_after) 6351 { while ((a = ref_node.nextSibling?._st__action) && ((p = (a.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - placeholder_order)<0 || p==0 && a.action_id<p_action_id)) 6352 { ref_node = ref_node.nextSibling; 6353 } 6354 ref_node = ref_node.nextSibling; 6355 } 6356 elem.parentNode.insertBefore(html, ref_node); 6357 if (want_replace) 6358 { to_back[0] = elem; 6359 elem.parentNode.removeChild(elem); 6360 } 6361 } 6362 else // want inside 6363 { let ref_node = null; 6364 for (let i=0, i_end=elem.childNodes.length; i<i_end; i++) 6365 { let n = elem.childNodes[i]; 6366 let that_action = n._st__action; 6367 if (!that_action) 6368 { to_back.push(n); 6369 elem.removeChild(n); 6370 i--; 6371 i_end--; 6372 } 6373 else if (!ref_node) 6374 { let that_placeholder_order = (that_action.placeholder_order || CONF__DEFAULT_PLACEHOLDER_ORDER) - 0; 6375 if (that_placeholder_order>placeholder_order || (that_placeholder_order==placeholder_order && that_action.action_id>p_action_id)) 6376 { ref_node = n; 6377 } 6378 } 6379 } 6380 elem.insertBefore(html, ref_node); 6381 } 6382 } 6383 let to_back_style = null; 6384 if (css) 6385 { to_back_style = elem.style.cssText; 6386 ns.add_style(elem, css); 6387 } 6388 let s=elem.style, cs; 6389 if (elem.style.textAlign=='center' && !s.marginLeft && !s.marginRight && ((cs=ns.new_elem(elem).style('display'))=='block' || cs=='table')) 6390 { s.marginLeft = s.marginRight = 'auto'; 6391 } 6392 if (!want_into_value) 6393 { if (is_not_node) 6394 { // process templates: when element is linked to document (needed especially for $responsive) 6395 ns.new_elem 6396 ( html, 6397 null, 6398 { ontplready() 6399 { if (action) 6400 { this._st__action = action; 6401 } 6402 if (animation) 6403 { this._reinit?.(); 6404 this._set_case('on', null, false, animation); 6405 } 6406 }, 6407 ontplerror: !action ? null : function(event, error) 6408 { action.fire_event('onerror', error); 6409 } 6410 } 6411 ); 6412 ns.scripts_to_ready(html); 6413 } 6414 } 6415 let revert_for_elem = ns.uacss.supress_for_elem(html, action?.action_id, with_uacss); 6416 let reverted = false; 6417 function revert() 6418 { if (!reverted && !want_into_value) 6419 { if (to_back_style != null) 6420 { elem.style.cssText = to_back_style; 6421 } 6422 if (want_replace) 6423 { html.parentNode.insertBefore(to_back[0], html); 6424 html.parentNode.removeChild(html); 6425 html = to_back[0]; 6426 } 6427 else if (want_add_before || want_add_after) 6428 { html.parentNode?.removeChild(html); 6429 } 6430 else if (created_path) 6431 { created_path.parentNode?.removeChild(created_path); 6432 } 6433 else if (n_action == 0) 6434 { elem.innerHTML = ''; 6435 for (let i=0, i_end=to_back.length; i<i_end; i++) 6436 { elem.appendChild(to_back[i]); 6437 } 6438 } 6439 revert_for_elem(); 6440 reverted = true; 6441 } 6442 } 6443 let a = action.action_id; 6444 if (executed_actions[a]===0 || executed_actions[a]===1) 6445 { set_executed_action_state(a, 2); 6446 } 6447 return {elem: content_elem, revert}; 6448 }; 6449 6450 ns.put_html = function(action, animation, unused_2, html, unused_3, unused_4, css, no_report_extra, with_uacss, onattach) 6451 { let a = action.action_id; 6452 let rid = action.visit_id; 6453 let found = []; 6454 let {containers} = ns;
6455 for (let c_id in containers) 6456 { let c = containers[c_id]; 6457 if (c && ((c.tp) & 1)) 6458 { let a_ids = c.ac; 6459 if (Array.isArray(a_ids) && a_ids.indexOf(a|0)!=-1) 6460 { let n_actions = c.a?.length || 0; 6461 if (n_actions) 6462 { let to_rm = -1; 6463 for (var k=n_actions; k>=0; k--) 6464 { if (c.a[k] == a) 6465 { to_rm = k; 6466 break; 6467 } 6468 else if (to_rm==-1 && c.rid<rid) 6469 { to_rm = k; 6470 } 6471 } 6472 if (to_rm != -1) 6473 { c.html[to_rm].detach(); 6474 c.html.splice(to_rm, 1); 6475 c.a.splice(to_rm, 1); 6476 } 6477 } 6478 if (c.uc-n_actions > 0 || c.cc>0) 6479 { if (!c.html) c.html = []; 6480 if (!c.a) c.a = []; 6481 found.push(c_id); 6482 let h_ondelivered = null; 6483 let detach_called = false; 6484 c.html.push 6485 ( { h_elem: null, 6486 attach(elem, n_action) 6487 { if (!this.h_elem) 6488 { for (let i=found.length-1; i>=0; i--) 6489 { let other_c = containers[found[i]]; 6490 if (other_c!=c && Array.isArray(other_c.a)) 6491 { let other_i = other_c.a.indexOf(a|0); 6492 if (other_i != -1) 6493 { other_c.a.splice(other_i, 1); 6494 other_c.html.splice(other_i, 1); 6495 if (other_c.a.length == 0) 6496 { other_c.a = null; 6497 other_c.html = null; 6498 } 6499 } 6500 } 6501 } 6502 found = []; 6503 block_action(a, true); 6504 try 6505 { detach_called = false; 6506 this.h_elem = ns.do_put_html(elem, html, [c.oc, css], c.hc, action, with_uacss, n_action, c.ud=='bt', animation); 6507 let attached_elem = this.h_elem.elem; 6508 if (!no_report_extra) 6509 { ns.install_report_extra(action, attached_elem); 6510 } 6511 ns.install_report_items(action, attached_elem); 6512 action.init_elem?.(attached_elem); 6513 let is_delivered = is_elem_delivered(attached_elem); 6514 ns.deferred 6515 ( () => 6516 { /* The 'not-delivered' flag marks the one `onshown` that is NOT a 6517 view: the action rendered below the fold, and its confirmed 6518 view arrives later as `ondelivered` (or never). Only the 6519 campaign frequency caps read it — the legacy auto-actions 6520 still see a plain 'onshown', unchanged. */ 6521 action.fire_event(is_delivered ? 'ondelivered' : 'onshown', null, is_delivered ? 'immed-delivered' : 'not-delivered'); 6522 } 6523 ); 6524 if (!is_delivered) 6525 { h_ondelivered = reg_ondelivered 6526 ( attached_elem, 6527 () => 6528 { action.fire_event('ondelivered'); 6529 h_ondelivered = null; 6530 } 6531 ); 6532 } 6533 if (detach_called) // called from ontplready of action html 6534 { this.detach(); 6535 } 6536 } 6537 catch (e) 6538 { action.fire_event('onerror', e.message); 6539 } 6540 unblock_action(a); 6541 onattach?.(this.h_elem.elem); 6542 } 6543 }, 6544 detach() 6545 { if (this.h_elem) 6546 { this.h_elem.revert(); 6547 this.h_elem = null; 6548 h_ondelivered?.(); 6549 h_ondelivered = null; 6550 } 6551 detach_called = true; 6552 }, 6553 is_attached() 6554 { return !!this.h_elem; 6555 }, 6556 /** Cross-fade this unit in where the outgoing one was. 6557 6558 IN WHERE IT WAS is the whole difficulty. `attach` puts the new content 6559 exactly where the placeholder says - before an element, after it, or 6560 inside it - and then this cross-fade used to take it straight back out 6561 again: `new_elem(html, parent, ...)` APPENDS (see `add_to_parent`: with 6562 a real node as the parent it does `insertBefore(elem, null)`), so the 6563 switch box was created as the parent's last child, the content was 6564 moved into it, and `parent.appendChild(next)` at the end of the 6565 animation left it there. 6566 6567 So the first action of a rotation rendered in the right place and every 6568 one after it dropped to the bottom of the section. Reported on 6569 account 7549's "Below pdpd buttons": the first card sat above the Add 6570 to basket button, and the moment it rotated the next card appeared 6571 under the price, the monthly line and the T&Cs. 6572 6573 The box is therefore moved to the outgoing content's own position, and 6574 the incoming content is put back relative to the BOX rather than to the 6575 parent - so it lands where the box is, which is where the content was. 6576 **/ 6577 swap(elem, n_action, other) 6578 { if (!other.h_elem) 6579 { this.attach(elem, n_action); 6580 } 6581 else 6582 { let prev = other.h_elem.elem; 6583 other.detach(); 6584 this.attach(elem, n_action); 6585 let next = this.h_elem.elem; 6586 let parent = next.parentNode; 6587 // Read BEFORE the box moves `next` out of the document. 6588 let anchor = next.nextSibling; 6589 let by_id = {}; 6590 let sw = ns.new_elem 6591 ( '<div id="sw" class="$switch-elem" data-case="prev">' + 6592 '<div id="prev" data-case="prev"></div>' + 6593 '<div id="next" data-case="next"></div>' + 6594 '</div>', 6595 parent, 6596 { prev: {innerHTML: prev},
6597 next: {innerHTML: next}, 6598 }, 6599 by_id 6600 ); 6601 /* `anchor` is null when the content was the last child, and then the 6602 append new_elem already did is the right position. */ 6603 if (anchor && anchor.parentNode === parent) 6604 { parent.insertBefore(sw, anchor); 6605 } 6606 sw._set_case 6607 ( 'next', 6608 null, 6609 false, 6610 { template: 'animator_fade', 6611 ontransitionend() 6612 { if (next.parentNode == by_id.next) 6613 { sw.parentNode?.insertBefore(next, sw); 6614 } 6615 sw.parentNode?.removeChild(sw); 6616 } 6617 } 6618 ); 6619 } 6620 } 6621 } 6622 ); 6623 c.a.push(a|0); 6624 c.rid = rid; 6625 c.inited &= ~1; 6626 init_container(c_id, c, false); 6627 if (!found.length) return 1; // attach() called 6628 } 6629 } 6630 } 6631 } 6632 if (!found.length) 6633 { action.fire_event('onerror', 'No suitable container'); 6634 } 6635 }; 6636 6637 /* Per-item impression reporting for recommendation widgets: once the attached element's view 6638 is confirmed (the same 2/3-visible rule as `ondelivered`), collect the item ids its cards 6639 already carry in data-personyze-click-target and send them as ONE event per item kind 6640 (-42 products / -43 articles), riding the normal event batch. The server counts them into 6641 st_actions_items_daily - the denominator that turns the existing click rows into a 6642 per-item CTR. fire_event's ev_done guard makes each render report at most once. */ 6643 ns.install_report_items = function(action, elem) 6644 { function report() 6645 { let targets = elem.querySelectorAll('[data-personyze-click-target]'); 6646 let seen = {}; 6647 let by_kind = {}; 6648 for (let i=0; i<targets.length; i++) 6649 { let v = (targets[i].getAttribute('data-personyze-click-target') || '').trim(); 6650 let sp = v.indexOf(' '); 6651 if (sp > 0) 6652 { let kind = v.slice(0, sp); 6653 let id = v.slice(sp+1).trim(); 6654 if (id && !seen[v] && (kind=='products' || kind=='articles')) 6655 { seen[v] = true; 6656 (by_kind[kind] = by_kind[kind] || []).push(encodeURIComponent(action.action_id+':'+id)); 6657 } 6658 } 6659 } 6660 if (by_kind.products) 6661 { action.fire_event('onproductsdelivered', by_kind.products.join('&')); 6662 } 6663 if (by_kind.articles) 6664 { action.fire_event('onarticlesdelivered', by_kind.articles.join('&')); 6665 } 6666 } 6667 if (is_elem_delivered(elem)) 6668 { report(); 6669 } 6670 else 6671 { reg_ondelivered(elem, report); 6672 } 6673 }; 6674 6675 ns.install_report_extra = function(action, elem) 6676 { ns.add_e 6677 ( elem, 6678 'onclick', 6679 event => 6680 { var target = event.target; 6681 while (target && target!=elem) 6682 { // Click target, or interaction with product/article? 6683 var click_target = target.getAttribute('data-personyze-click-target'); 6684 if (click_target != null) 6685 { action.fire_event('onextra', click_target.trim() || undefined); 6686 return; 6687 } 6688 // Close? 6689 var value = target._get_tplname?.(); 6690 if (value=='personyze_button_dont_show_again' || value=='personyze_button_popup_close') 6691 { action.fire_event('onclose'); 6692 return; // no 6693 } 6694 target = target.parentNode; 6695 } 6696 // by default, send extra if there are no data-personyze-click-target="" in the html 6697 if (!ns.joyquery('*[data-personyze-click-target]', elem).get(0)) 6698 { action.fire_event('onextra'); 6699 } 6700 }, 6701 'put_html extra' 6702 ); 6703 }; 6704 6705 ns.get_owner_action = function(elem) 6706 { var action; 6707 for (var n=elem; (action = n._st__action)===undefined && (n=n._ne__switch_elem || n.parentNode);); 6708 return action; 6709 }; 6710 6711 ns.ga_send = function(what, name, event_name) 6712 { ns.do_ga_send(ns.get_location().host+' '+what, name, event_name, 0, true); 6713 /* The same automatic report to whatever else the account chose in 6714 Analytics settings, and the page actually has. Named for the action 6715 (`name`), with the event kind as a property -- so a Segment or 6716 Klaviyo timeline reads "Homepage banner / shown" rather than a 6717 GA-shaped category+action pair nobody else uses. */ 6718 if (default_send_dests) 6719 { ns.send_event_to(default_send_dests.split(','), name, {event: event_name, source: 'personyze'}); 6720 } 6721 }; 6722 6723 ns.do_ga_send = function(category, action, label, value, noninteraction) 6724 { if (ga_is_active) 6725 { ga_queue.push([category, action, label, value, noninteraction]); 6726 ns.reg_onready(ga_flush_queue); 6727 } 6728 }; 6729 6730 /* Push one event out to whichever analytics tools the page already has. 6731 6732 Every destination here is a library the customer loaded themselves -- 6733 we never inject one. A missing library is a silent skip, not an error: 6734 a page that has Segment but not Tealium should still send to Segment, 6735 and a visitor with an ad blocker should not break the campaign. 6736 6737 Each adapter is split into `ready` and `send` on purpose. Asking "can 6738 this page send to Segment?" must never actually send anything -- a 6739 probe event would land in the customer's own analytics and pollute the 6740 data we are supposed to be feeding. 6741 6742 The adapters share one property object, so the same event lands 6743 identically everywhere. Adding a destination is one entry in this map, 6744 which is what lets the Send-event action, the per-action report 6745 checkbox and anything later all reuse it. 6746 */ 6747 var send_event_adapters = 6748 { segment: 6749 { ready: function() { return !!(window.analytics && typeof(analytics.track)=='function'); }, 6750 send: function(name, props) { analytics.track(name, props); } 6751 }, 6752 tealium:
6753 { /* utag.link is Tealium's own "a thing happened" call, present 6754 whenever utag.js is on the page -- the same condition that makes 6755 the audiences/badges side of this integration work. */ 6756 ready: function() { return !!(window.utag && typeof(utag.link)=='function'); }, 6757 send: function(name, props) 6758 { var payload = {tealium_event: name}; 6759 for (var k in props) payload[k] = props[k]; 6760 utag.link(payload); 6761 } 6762 }, 6763 ga4: 6764 { ready: function() { return typeof(window.gtag)=='function'; }, 6765 send: function(name, props) { gtag('event', name, props); } 6766 }, 6767 datalayer: 6768 { ready: function() 6769 { var layer = window[gtm_data_layer_name || 'dataLayer']; 6770 return !!(layer && typeof(layer.push)=='function'); 6771 }, 6772 send: function(name, props) 6773 { var payload = {event: name}; 6774 for (var k in props) payload[k] = props[k]; 6775 window[gtm_data_layer_name || 'dataLayer'].push(payload); 6776 } 6777 }, 6778 klaviyo: 6779 { /* klaviyo.js exposes `klaviyo`, a command queue taking 6780 push(['track', name, props]); older installs have the same 6781 queue as `_learnq`. Both accept commands before the library 6782 finishes loading, so ready-with-queue is truly ready. */ 6783 ready: function() 6784 { var k = window.klaviyo || window._learnq; 6785 return !!(k && typeof(k.push)=='function'); 6786 }, 6787 send: function(name, props) { (window.klaviyo || window._learnq).push(['track', name, props]); } 6788 } 6789 }; 6790 6791 /* Returns the destinations that actually accepted the event, so a caller 6792 can report what happened rather than assume. */ 6793 ns.send_event_to = function(destinations, name, props) 6794 { var sent = []; 6795 if (!name) 6796 { return sent; 6797 } 6798 props = props || {}; 6799 for (var i=0; i<(destinations || []).length; i++) 6800 { var key = (destinations[i]+'').toLowerCase(); 6801 var adapter = send_event_adapters[key]; 6802 if (!adapter) 6803 { continue; 6804 } 6805 try 6806 { if (adapter.ready()) 6807 { adapter.send(name, props); 6808 sent.push(key); 6809 } 6810 } 6811 catch (e) 6812 { ns.error('Could not send event "'+name+'" to '+key+': '+e.message, e); 6813 } 6814 } 6815 return sent; 6816 }; 6817 6818 /* Which destinations this page could send to right now -- checked, never 6819 sent. The action editor cannot know what a visitor's page has loaded, 6820 so this is what a "nothing arrived" support question gets answered 6821 with. */ 6822 ns.send_event_available = function() 6823 { var out = []; 6824 for (var key in send_event_adapters) 6825 { try 6826 { if (send_event_adapters[key].ready()) 6827 { out.push(key); 6828 } 6829 } 6830 catch (e) 6831 { // a destination whose probe throws is not available 6832 } 6833 } 6834 return out; 6835 }; 6836 6837 ns.custom_event = function(id_or_html_id, value, onload, onerror) 6838 { if (onload && typeof(onload)!='function') 6839 { throw new Error("custom_event(): onload must be function"); 6840 } 6841 if (onerror && typeof(onerror)!='function') 6842 { throw new Error("custom_event(): onerror must be function"); 6843 } 6844 var c = null; 6845 var c_id = null; 6846 if (ns.containers.hasOwnProperty(id_or_html_id)) 6847 { // id 6848 c_id = id_or_html_id; 6849 c = ns.containers[c_id]; 6850 } 6851 else if (conts_map.hasOwnProperty(id_or_html_id)) 6852 { // html_id 6853 var c_ids = conts_map[id_or_html_id]; 6854 for (var i=c_ids.length-1; i>=0; i--) 6855 { c_id = c_ids[i]; 6856 c = ns.containers[c_id]; 6857 if (c.tp & 1024) break; 6858 } 6859 } 6860 if (!c || !(c.tp & 1024)) 6861 { throw new Error('No such custom event container: '+id_or_html_id); 6862 } 6863 value = (value==null ? '' : value+'').substr(0, 1024); 6864 log_event(c_id, value, (event_send_with_delay&524288 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&524288 ? LOG_EVENT_SEND_NOW : 0), c.tp & 50176, 0, onload, onerror); 6865 }; 6866 6867 ns.action_state = function(action_id, value) 6868 { var error = null; 6869 if (!ns.act_params[action_id]) 6870 { error = `action ID ${action_id} not executed`; 6871 } 6872 else if (value!='extra' && value!='closed' && value!='goal') 6873 { error = 'value must be one of: "extra", "closed", "goal"'; 6874 } 6875 if (error) 6876 { throw new Error("action_state(): "+error); 6877 } 6878 ns.act_params[action_id].fire_event('on'+value); 6879 }; 6880 6881 ns.get_action_message = function(action_id, current_status='') 6882 { let e = executed_actions[action_id]; 6883 6884 let container_found = -1; 6885 let {containers} = ns;
6886 for (let c_id in containers) 6887 { let c = containers[c_id]; 6888 if (c && ((c.tp) & 1)) 6889 { if (Array.isArray(c.ac) && c.ac.indexOf(action_id|0)!=-1) 6890 { container_found = 0; 6891 if (Array.isArray(c.a)) 6892 { let i = c.a.indexOf(action_id|0); 6893 if (i!=-1 && c.html[i].is_attached()) 6894 { container_found = 1; 6895 break; 6896 } 6897 } 6898 } 6899 } 6900 } 6901 6902 // substring match: the server-side status reads "delivered, not executed (runtime 6903 // error or suppressed)" (and just "not executed" from older backends) 6904 return typeof(e)=='string' ? e : container_found==0 && current_status=='ok' ? 'No placeholder found yet' : e===1 && String(current_status).indexOf('not executed')!=-1 ? 'Scheduled' : ''; 6905 }; 6906 6907 ns.secs = function() // client's local time offset, not unix time 6908 { var date = new Date(); 6909 return Math.floor(date.getTime() / 1000) - date.getTimezoneOffset()*60; 6910 }; 6911 6912 ns.hash = function(str) // for compatibility 6913 { return ns.strhash(str); 6914 }; 6915 6916 ns.subst_page_user_data = function(t, cd) 6917 { for (var i in cd) 6918 { t = t.replace('${cd_'+i+'}', cd[i]); 6919 } 6920 return t; 6921 }; 6922 6923 ns.subst_user_data = function(format, acc, const_data) 6924 { if (format == null) return ''; 6925 var props=null, props_all=null; 6926 var f = function(all, prop, fmt) 6927 { var v; 6928 if (const_data?.[prop]) 6929 { v = const_data[prop]; 6930 } 6931 else if (prop=='_' || !prop) 6932 { v = acc || ''; 6933 } 6934 else if (prop == 'tm') 6935 { v = Math.floor(Date.now() / 1000) + ''; 6936 } 6937 else if (prop == 'vid') 6938 { v = cur_visit_id; 6939 } 6940 else if (prop == 'id_short') 6941 { var v = (parseInt(ns.uid)+2147483647) + ''; 6942 while (v.length < 10) v = '0' + v; 6943 v = v.substr(0, 6); 6944 } 6945 else 6946 { v = ns.user_data[prop]; 6947 } 6948 if (v == null) 6949 { if (!props) props = get_request_props(1, 0, 1); 6950 v = props[prop]; 6951 } 6952 if (v == null) 6953 { if (!props_all) props_all = get_request_props(0, 0, 1); 6954 v = props_all[prop]; 6955 } 6956 if (v == null) v = ''; 6957 v = ''+v; 6958 fmt.replace 6959 ( RE_SUBST_USER_DATA_FUNC, 6960 function(all, func, args_str) 6961 { var args = []; 6962 if (args_str) 6963 { args_str.replace 6964 ( RE_SUBST_USER_DATA_FUNC_ARGS, 6965 arg => 6966 { arg = !arg ? '' : arg.trim(); 6967 if (arg.charAt(0) == "'") 6968 { arg = arg.substring(1, arg.length-1).replace(RE_SUBST_USER_DATA_AP, "'"); 6969 } 6970 else if (arg.charAt(0) == '"') 6971 { arg = arg.substring(1, arg.length-1).replace(RE_SUBST_USER_DATA_QT, '"'); 6972 } 6973 args[args.length] = arg; 6974 } 6975 ); 6976 } 6977 switch (func) 6978 { case 'json': 6979 v = JSON.stringify(v); 6980 break; 6981 case 'pas': 6982 v = "'"+v.replace(/'/g, "''")+"'"; 6983 break; 6984 case 'html': 6985 v = ns.htmlspecialchars(v); 6986 break; 6987 case 'url': 6988 v = encodeURIComponent(v); 6989 break; 6990 case 'time': 6991 case 'time_short': 6992 case 'date': 6993 case 'date_short': 6994 case 'datetime': 6995 case 'datetime_short': 6996 v = ftime.strftime(time_formats[func] || '%c', v); 6997 break; 6998 case 'strftime': 6999 v = ftime.strftime(args[0], v); 7000 break; 7001 case 'trim': 7002 v = !v ? '' : (v+'').trim(); 7003 break; 7004 case 'substr': 7005 v = v.substr(args[0]>=0 ? args[0] : v.length+parseInt(args[0]), args[1]>=0 ? args[1] : v.length+parseInt(args[1])); 7006 break; 7007 case 'substring': 7008 v = v.substring(args[0]>=0 ? args[0] : v.length+parseInt(args[0]), args[1]>=0 ? args[1] : v.length+parseInt(args[1])); 7009 break; 7010 case 'replace': 7011 var arg = args[0]; 7012 var is_re = false; 7013 if (arg.charAt(0) == '/') 7014 { try 7015 { arg = eval(arg); 7016 is_re = true; 7017 } 7018 catch (e) 7019 { 7020 } 7021 } 7022 if (!is_re) 7023 { arg = new RegExp(arg.replace(RE_SUBST_USER_DATA_ESC, '\\$1'), 'gi'); // default is 'i'! 7024 } 7025 v = v.replace(arg, args[1]); 7026 break; 7027 case 'uc': 7028 v = v.toUpperCase(); 7029 break; 7030 case 'lc': 7031 v = v.toLowerCase(); 7032 break; 7033 case 'nth-word': 7034 v = nth_word(v, args[0] || ''); 7035 break; 7036 case 'param': 7037 v = ns.get_param(args[0] || ''); 7038 break; 7039 case 'var': 7040 v = window[args[0]]; 7041 break; 7042 case 'func': 7043 v = window[args.shift()]; 7044 try 7045 { v = typeof(v)=='function' ? v.apply(window, args) : ''; 7046 } 7047 catch (e) 7048 { v = ''; 7049 } 7050 break; 7051 case 'default': 7052 v = v || args[0]; 7053 break; 7054 } 7055 if (v == null) v = ''; 7056 v = v+''; 7057 } 7058 ); 7059 return v; 7060 }; 7061 return (format+'').replace(RE_SUBST_USER_DATA, f); 7062 }; 7063 7064 ns.subst_user_data_action = function(format, acc, action_id, action_field, const_data) 7065 { if (format == null) return ''; 7066 if (!const_data) const_data = {}; 7067 const_data.action_id = action_id; 7068 if (action_field && action_id>0 && format.join && format.length<=2) 7069 { var data = (actions_subst_user_data[action_id] || {})[action_field] || ''; 7070 return !data ? '' : // no need to wrap if no data 7071 ( (format[0] ? ns.subst_user_data(format[0], acc, const_data) : '') + 7072 ns.subst_user_data(data, acc, const_data) + // server side substitution leaves ${...} where it can't substitute, so complete this on client side 7073 (format[1] ? ns.subst_user_data(format[1], acc, const_data) : '') 7074 ); 7075 } 7076 else 7077 { return ns.subst_user_data(format, acc, const_data); 7078 } 7079 }; 7080 7081 ns.get_html_ids_of_action = function(action_id) 7082 { var html_ids = []; 7083 var {containers} = ns;
7084 for (var c_id in containers) 7085 { if (containers.hasOwnProperty(c_id)) 7086 { var c = containers[c_id]; 7087 for (var i=0; i<c.ac.length; i++) 7088 { if (c.ac[i] == action_id) 7089 { html_ids.push(c.hi); 7090 break; 7091 } 7092 } 7093 } 7094 } 7095 return html_ids; 7096 }; 7097 7098 ns.get_elems_of_action = function(action_id, default_to_body) 7099 { var ids = ns.get_html_ids_of_action(action_id); 7100 if (ids.length == 0) 7101 { return default_to_body ? [document.body || document.documentElement] : []; 7102 } 7103 return ns.id(ids.join(','), true); 7104 }; 7105 7106 ns.compose_url = function(url_parts) 7107 { var {href} = url_parts; 7108 if (!href) 7109 { function getParam(param, ensure_prefix, no_stray) 7110 { var part = url_parts[param]; 7111 if (!part) 7112 { return ns.get_location()[param]; 7113 } 7114 if (ensure_prefix && part.charAt(0)!=ensure_prefix) 7115 { part = ensure_prefix + part; 7116 } 7117 if (param=='search' && url_parts.edit_query) 7118 { var subj = ns.get_location()[param]; 7119 if (subj.charAt(0) == '?') subj = subj.substring(1); 7120 var params = part.substring(1).split('&'); 7121 for (var i=0; i<params.length; i++) 7122 { var eq = params[i].indexOf('='); 7123 var name = eq==-1 ? params[i] : params[i].substr(0, eq); 7124 if (name == '') continue; 7125 var value = eq==-1 ? null : params[i].substr(eq+1); 7126 var from = subj.indexOf(name+'='); 7127 var to = from==-1 ? -1 : subj.indexOf('&', from+1); 7128 if (to == -1) to = subj.length; 7129 if (value == null) // remove 7130 { if (from != -1) 7131 { subj = subj.substr(0, from) + subj.substring(to+1); 7132 } 7133 } 7134 else // edit 7135 { if (from != -1) 7136 { subj = subj.substr(0, from+name.length+1) + value + subj.substring(to); 7137 } 7138 else 7139 { if (subj.length>0 && subj.charAt(subj.length-1)!='&') subj += '&'; 7140 subj += name+'='+value; 7141 } 7142 } 7143 } 7144 if (subj.length>0 && subj.charAt(subj.length-1)=='&') 7145 { subj = subj.substr(0, subj.length-1); 7146 } 7147 part = '?'+subj; 7148 } 7149 if (no_stray && part==ensure_prefix) 7150 { part = ''; 7151 } 7152 return part; 7153 } 7154 7155 href = getParam('protocol') + '//' + getParam('host') + getParam('pathname', '/') + getParam('search', '?', true) + getParam('hash', '#', true); 7156 } 7157 return href; 7158 }; 7159 7160 ns.redirect = function(action, url_parts, throw_error) 7161 { var href = ns.compose_url(url_parts); 7162 if (decodeURI(ns.get_location().href) != decodeURI(href)) 7163 { ns.set_location(href); 7164 return true; 7165 } 7166 if (throw_error) throw new Error("Tried to redirect to same URL: "+href); 7167 return false; 7168 }; 7169 7170 ns.get_visit_id = function() 7171 { return cur_visit_id; 7172 }; 7173 7174 /** Function that other scripts on page can call; returns occured condition IDs - not all, but only that for which there is a container that depends on them, or a page refresh interval, or for which send_to_client is set to true 7175 **/ 7176 ns.get_conditions = function() 7177 { var cur_conds = []; 7178 for (var i in current_conditions) 7179 { if (current_conditions.hasOwnProperty(i)) 7180 { cur_conds.push(parseInt(i.substring(1))); 7181 } 7182 } 7183 return cur_conds; 7184 }; 7185 7186 /** function that other scripts on page can call 7187 **/ 7188 ns.get_pagegroups = function() 7189 { return current_pagegroups.concat([]); 7190 }; 7191 7192 /** Public API: suppress an action for the visitor without closing/removing its 7193 currently shown content (unlike $personyze_button_dont_show_again, which does 7194 both). Alias of disable_action(); the optional for_n_sessions (default 1) 7195 keeps the action suppressed for that many sessions, or pass for_secs to 7196 suppress for wall-clock time instead. 7197 **/ 7198 ns.session_disable_action = function(action_id, for_n_sessions, for_secs) 7199 { ns.disable_action(action_id, for_n_sessions, for_secs); 7200 }; 7201 7202 /** Public API: suppress action_id for the visitor for for_n_sessions sessions 7203 (default/minimum 1), or when for_secs > 0 for that many seconds of wall-clock 7204 time instead (rounded up to whole minutes — the cookie stores a minutes-since- 7205 epoch expiry stamp). The action's conditions are re-evaluated after that. 7206 **/ 7207 ns.disable_action = function(action_id, for_n_sessions, for_secs) 7208 { action_id = parseInt(action_id); 7209 for_n_sessions |= 0; 7210 for_secs |= 0; 7211 if (action_id > 0) 7212 { var set_cookie = {}; 7213 if (for_secs > 0) 7214 { set_cookie[action_id] = {till_min: Math.ceil((ns.secs()+for_secs)/60), match_again: 1}; 7215 } 7216 else 7217 { if (for_n_sessions <= 1) // assert(true <= 1); assert(!(undefined <= 1)); 7218 { for_n_sessions = 1; 7219 } 7220 set_cookie[action_id] = {remain_sessions: for_n_sessions, match_again: 1}; 7221 } 7222 new SessCnt('noacts').add(set_cookie, 1); 7223 } 7224 }; 7225 7226 ns.need_query_social = function(login_id_type) 7227 { return (','+ns.cookie_obj('_stat_track_s_id')._so+',').indexOf(','+login_id_type+',') == -1; 7228 }; 7229 7230 ns.social = function(login_id_type, data, onload, onerror) 7231 { function no_need_query_social(login_id_type) 7232 { var sid = ns.cookie_obj('_stat_track_s_id'); 7233 var so_arr=[login_id_type], so_obj={}; 7234 so_obj[login_id_type] = null; 7235 (sid._so || '').replace 7236 ( /\b\w\w\b/g, 7237 $0 => 7238 { if (!so_obj.hasOwnProperty($0)) 7239 { so_arr.push($0); 7240 so_obj[$0] = null; 7241 } 7242 } 7243 ); 7244 if (so_arr.length < 20) 7245 { ns.cookie_obj('_stat_track_s_id', {_so: so_arr.join(',')}); 7246 } 7247 } 7248 if (login_id_type=='IN' || login_id_type=='SA' || login_id_type=='DO' || ns.need_query_social(login_id_type)) 7249 { if (login_id_type!='IN' && login_id_type!='SA') 7250 { login_id_type = (login_id_type+'').substr(0, 2); 7251 no_need_query_social(login_id_type); 7252 } 7253 let data_arr = ['TP='+login_id_type]; 7254 let data_len = 5; 7255 ns.user_data = ns.user_data || {}; 7256 for (let i in data) 7257 { if (typeof(data[i])=='number' || typeof(data[i])=='string') 7258 { let item = (data[i]+'').substr(0, 200); 7259 if (item) 7260 { ns.user_data[i] = item; 7261 item = i+'='+encodeURIComponent(item); 7262 data_len += item.length; 7263 if (data_len > 2500) break; 7264 data_arr.push(item); 7265 } 7266 } 7267 } 7268 if (data_arr.length > 1) 7269 { log_event('-4', data_arr.join('&'), LOG_EVENT_VALUE_LIST_AMP|(event_send_with_delay&1048576 ? LOG_EVENT_SEND_WITH_DELAY : event_send_now&1048576 ? LOG_EVENT_SEND_NOW : 0), 0, onload, onerror); 7270 } 7271 } 7272 }; 7273 7274 /** DEPRECATED 7275 **/ 7276 ns.send_user_data = function(data, onload, onerror) 7277 { ns.social('IN', data, onload, onerror); 7278 }; 7279 7280 /** The escape hatch for "open it when THIS happens", where THIS is something 7281 only the site itself knows: a cart update, a step of a wizard, a video 7282 finishing, a price recalculated. The site calls _S_T.trigger('name') and 7283 any action whose `when.event` names it opens. 7284 7285 Two decisions worth knowing: 7286 7287 **A fired name stays fired for the rest of the page.** Actions arrive 7288 asynchronously and a dataLayer push near the top of <head> beats them 7289 every time, so a trigger that only notified whoever was already listening 7290 would silently never work for the earliest — and most common — events. 7291 A late subscriber therefore fires immediately on a name already seen. 7292 The semantic is "did this happen on this page", not "happens next". 7293 7294 **Names are the site's, unprefixed.** A dataLayer `{event: 'add_to_cart'}` 7295 is triggerable as `add_to_cart`, because the author types the name they 7296 can already see in their own tag manager. 7297 **/ 7298 var custom_trigger_fired = {}; 7299 var custom_trigger_subs = {}; 7300 7301 ns.trigger = function(name) 7302 { name = (name==null ? '' : name+'').trim(); 7303 if (name) 7304 { custom_trigger_fired[name] = true; 7305 var subs = custom_trigger_subs[name]; 7306 if (subs) 7307 { // Copy: a subscriber that removes itself while showing would 7308 // otherwise reindex the array mid-loop and skip its neighbour. 7309 subs = subs.slice(); 7310 for (var i=0; i<subs.length; i++)
7311 { ns.pcall(subs[i]); 7312 } 7313 } 7314 } 7315 }; 7316 7317 /** Returns a handle with `remove()`, so it can go straight into an action's 7318 `attached_events` like every other trigger registration. **/ 7319 ns.on_custom_trigger = function(name, callback) 7320 { name = (name==null ? '' : name+'').trim(); 7321 var subs = custom_trigger_subs[name] || (custom_trigger_subs[name] = []); 7322 subs.push(callback); 7323 if (custom_trigger_fired[name]) 7324 { // Deferred, not called straight out: a modal popup's do_show() runs 7325 // synchronously, so replaying inline would show the action BEFORE this 7326 // function has returned the handle its caller pushes into 7327 // `attached_events` — and anything that tore the action down in between 7328 // would drain that list before the handle was in it, leaking a listener 7329 // nothing can remove. The re-check covers a teardown during the wait. 7330 setTimeout 7331 ( function() 7332 { if (subs.indexOf(callback) != -1) 7333 { ns.pcall(callback); 7334 } 7335 }, 7336 0 7337 ); 7338 } 7339 return { 7340 remove() 7341 { var i = subs.indexOf(callback); 7342 if (i != -1) 7343 { subs.splice(i, 1); 7344 } 7345 } 7346 }; 7347 }; 7348 7349 /** Switch 1 - the explicit testing-mode switch, remembered until switched back. Set by the 7350 "_S_T=testing|production" URL parameter, or by 7351 _S_T.te(1) / _S_T.te(0) from the console. 7352 7353 _te (test environment) 7354 0 - no (production) 7355 1 - yes (testing) 7356 2 - no, but show controls (production) 7357 7358 Nothing may write this on behalf of switch 2 (the account's testing-mode IP). That switch 7359 is not the visitor's choice and has to be re-decided on every log visit, so it lives in 7360 '_te_ip' and is owned by the server. Conflating the two is what used to strand ordinary 7361 visitors in testing mode for good: testing IPs auto-fill with whoever last saved a campaign, 7362 so anyone sharing that egress IP got '_te=1' written once - and nothing ever cleared it, 7363 not even changing IP, closing the browser or the account leaving testing mode. 7364 **/ 7365 ns.te = function(value) 7366 { return ns.cookie_obj('_stat_track_s_id', value==null ? null : {_te: value-0 ? 1 : 2, _tev: TE_VERSION})._te-0 == 1 ? 1 : 0; 7367 }; 7368 7369 /** The mode actually in force - either switch turns testing on, and switch 2 is checked first. 7370 Mirrors what st_log_visit_N does with the same two inputs: 7371 "IF p_is_testing_mode_ip THEN SET p_version_tag_used = 'testing'". 7372 **/ 7373 ns.te_eff = function() 7374 { return ns.is_te_ip || ns.te(); 7375 }; 7376 7377 /** '_te' used to be written on behalf of the testing-mode IP as well as by the explicit switch, 7378 so a '_te' left by an older lib cannot be told apart from a real choice - and the IP-derived 7379 ones are exactly what must go. Any '_te' not carrying the current TE_VERSION is therefore 7380 dropped. The cost is that testers who switched on explicitly add the URL parameter once 7381 more; the alternative is leaving site visitors stranded in testing mode, which is the whole 7382 point of the split. 7383 7384 Also loads switch 2 from the previous page load, because get_js_url() has to choose between 7385 the testing and the production build of a JS file before this page's first response arrives. 7386 **/ 7387 function init_testing_mode() 7388 { var sid_obj = ns.cookie_obj('_stat_track_s_id'); 7389 if (sid_obj._te && sid_obj._tev-0 != TE_VERSION) 7390 { ns.cookie_obj('_stat_track_s_id', {_te: '', _tev: TE_VERSION}); 7391 } 7392 ns.is_te_ip = sid_obj._te_ip-0 ? 1 : 0; 7393 } 7394 7395 /** Start a fresh session from the testing-mode console, the same way the Simulator's 7396 "Start new session" button does. 7397 7398 Two things this must get right that clearing cookies by hand does not: 7399 - `cookie_obj(..., true)` is used rather than `delete_cookie()`, because every cookie 7400 is mirrored into localStorage under the same key and is read back from there when 7401 the cookie is gone. Deleting the cookie alone leaves the mirror, so the old id 7402 simply returns. Only the write path rewrites both. 7403 - emptying the cookie also drops `_te` and `_te_ip`, which would silently kick the 7404 tester out of testing mode, so both are put back afterwards. 7405 **/ 7406 ns.te_new_session = function() 7407 { var was_te = ns.te(), was_te_ip = ns.is_te_ip; 7408 ns.cookie_obj('_stat_track_s_id', {}, true); 7409 tracker_events.set(null); // buffered events belong to the session that just ended 7410 if (was_te) 7411 { ns.te(1); 7412 } 7413 ns.te_ip(was_te_ip); 7414 location.reload(); 7415 }; 7416 7417 /** Become a brand new visitor. Emptying `uid` makes make_id() take its new-user branch, 7418 which starts a new session too. `dg` is preserved so an attached Simulator survives 7419 the reload. `stat_track_actions` holds the per-action display history behind 7420 frequency capping - a "new" visitor that still carries it would keep having campaigns 7421 suppressed, so it goes as well. 7422 **/ 7423 ns.te_new_user = function() 7424 { var was_te = ns.te(), was_te_ip = ns.is_te_ip; 7425 ns.cookie_obj('stat_track_u_id', {dg: ns.cookie_obj('stat_track_u_id').dg}, true); 7426 ns.cookie_obj('_stat_track_s_id', {}, true); 7427 tracker_events.set(null); 7428 try 7429 { window.localStorage?.removeItem('stat_track_actions'); 7430 } 7431 catch (e) 7432 { 7433 } 7434 ns.storage_obj('stat_track_sessions', {}, true); 7435 if (was_te) 7436 { ns.te(1); 7437 } 7438 ns.te_ip(was_te_ip); 7439 location.reload(); 7440 }; 7441 7442 /** Switch 2 - is this visitor's IP currently one of the account's testing-mode IPs? Not the 7443 visitor's choice, so it is never merged into '_te'. Every log-visit response calls this with 7444 the value the server just computed from the connecting IP, including 0, so leaving the 7445 testing IP takes effect on the very next request. 7446 7447 It is remembered between page loads only because get_js_url() must pick the testing or the 7448 production build of a JS file before the first response comes back. A stale value therefore 7449 costs at most one page load and is corrected by that same load's response - it cannot 7450 outlive the IP the way '_te' used to. 7451 7452 A missing argument means 1: responses generated before the split emit a bare te_ip() and 7453 only ever emitted it to say "yes". 7454 **/ 7455 ns.te_ip = function(value) 7456 { ns.is_te_ip = value==null || value-0 ? 1 : 0; 7457 ns.cookie_obj('_stat_track_s_id', {_te_ip: ns.is_te_ip}); 7458 return ns.is_te_ip; 7459 }; 7460 7461 ns.get_js_url = function(js_filename, override_te, is_action) 7462 { const FACTORIZE_JS_URLS = 0; 7463 const HOST = "qacounter.personyze.com";
7464 const HOST_T = "qacounter.personyze.com"; 7465 var factor = 4773; 7466 7467 var te = override_te!=null ? override_te : ns.te_eff(); 7468 var host = !NEW_ELEM_NDEBUG || te ? HOST_T : HOST; 7469 var is_action_params = js_filename > 0; 7470 if (is_action || js_filename=='index') 7471 { js_filename = 'default/'+js_filename; 7472 } 7473 if (!is_action && te) 7474 { js_filename += '-t'; 7475 } 7476 if (!NEW_ELEM_NDEBUG && !is_action_params) 7477 { js_filename += '-debug'; 7478 } 7479 var url = get_protocol()+'//'+host; 7480 if (!is_action) 7481 { url += "\/js\/"+ns.server_id+'/'; 7482 factor = get_f(); 7483 if (!factor) 7484 { // No factor yet (first ever page view). A random number is right: it cannot collide with a real 7485 // version's cache key, so whatever comes back is never mistaken for a published build. 7486 factor = 1000000+Math.floor(Math.random()*1000000); 7487 } 7488 else if (get_fb()) 7489 { /* A build newer than this factor exists that not every app host serves yet, so right now the 7490 hosts can disagree about what index.js holds. Ask for a DIFFERENT url than the clean one. 7491 7492 These files are served with `expires 30d` and the versioned name is only an alias for one 7493 mutable file per host (nginx `try_files $1$2.js $1.js`), so whichever build answers gets 7494 pinned under the name it was asked by for a month. Sending the unsettled window to its own 7495 'b' key means the clean key is only ever filled during windows when every host serves 7496 exactly the build it names -- and the 'b' key is simply abandoned once the build goes out. 7497 */ 7498 factor += 'b'; 7499 } 7500 } 7501 else 7502 { url += "\/actions\/"; 7503 } 7504 if (FACTORIZE_JS_URLS && NEW_ELEM_NDEBUG) 7505 { return url+js_filename+'-'+factor+'.js'; 7506 } 7507 else 7508 { return url+js_filename+'.js?fr='+factor; 7509 } 7510 }; 7511 7512 ns.load_libs = function(js_filenames, onload, onerror, override_te) 7513 { var url, checker; 7514 if (!js_filenames) 7515 { return onload?.(); 7516 } 7517 if (!js_filenames.join) 7518 { js_filenames = [js_filenames]; 7519 } 7520 var cur_ld = {}; 7521 var ld = [cur_ld]; 7522 for (var i=0; i<js_filenames.length; i++) 7523 { let filename = js_filenames[i]; 7524 if (typeof(filename) == 'object') 7525 { url = filename.src; 7526 checker = null; 7527 if (!url) 7528 { cur_ld = {}; 7529 ld.push(cur_ld); 7530 return; 7531 } 7532 var checker_var = filename.check; 7533 if (checker_var) 7534 { checker_var = checker_var.split(/\s*\.\s*/); 7535 checker = function() 7536 { var obj = window; 7537 for (var i=0; i<checker_var.length && obj; i++) 7538 { obj = obj[checker_var[i]]; 7539 } 7540 return obj; 7541 }; 7542 } 7543 } 7544 else if (filename == 'index') 7545 { url = ns.get_js_url(filename); 7546 checker = () => ins_status_install; 7547 } 7548 else if (filename > 0) 7549 { url = ns.get_js_url(filename, override_te); 7550 if (override_te != null) 7551 { ns.act_params[filename] = null; 7552 } 7553 checker = () => ns.act_params[filename]; 7554 } 7555 else 7556 { url = ns.get_js_url(filename, override_te, true); 7557 checker = () => ns[filename] || ns.act[filename] || ns.templates[filename]; // lib || action_js 7558 } 7559 cur_ld[url] = checker; 7560 } 7561 ns.load_scripts 7562 ( ld, 7563 err_msg => 7564 { if (!err_msg) 7565 { onload?.(); 7566 } 7567 else 7568 { onerror?.(err_msg); 7569 } 7570 } 7571 ); 7572 }; 7573 7574 ns.pass_cookie_url = function(url, no_count) 7575 { var u = ns.cookie_obj('stat_track_u_id'); 7576 var s = ns.cookie_obj('_stat_track_s_id'); 7577 if (url.indexOf('/') == -1) 7578 { url = '//'+url; 7579 } 7580 return ns.url_replace_param(url, '_S_T', 'u'+u.uid+(no_count ? 'n' : '')+(parseInt(u.off) ? 'o' : '')+(s._inew&2 ? 'u' : '')+'_'+u.st+'_'+u.ls+'_'+(u.sy||'').replace(/,/g, ';')+'_'+s._sid+'_'+(s._bh||'')); 7581 }; 7582 7583 ns.pass_cookie = function(urls) 7584 { if (!urls) 7585 { urls = []; 7586 var {domains} = ns; 7587 for (var d in domains) 7588 { if (d!=ns.get_location().host && domains.hasOwnProperty(d)) 7589 { if (d.substr(0, 2) == '*.') 7590 { d = d.substring(2); 7591 if (d in domains) continue; 7592 } 7593 urls.push('//'+d+'/'); 7594 } 7595 } 7596 } 7597 var div = ns.new_elem('div', null, {style: 'position:absolute; display:none'}); 7598 for (var i=0; i<urls.length; i++) 7599 { var iframe = ns.new_elem 7600 ( 'iframe', 7601 div, 7602 { src: ns.pass_cookie_url(urls[i], true), 7603 width: 1, 7604 height: 1 7605 } 7606 ); 7607 } 7608 document.body.appendChild(div); 7609 setTimeout(() => {try {document.body.removeChild(div)} catch (e) {}}, 10000); 7610 return div; 7611 }; 7612 7613 /* `consent` is the OPT-IN third argument, present only in snippets generated for accounts that 7614 turned consent mode on. Absent (every existing installation) this whole block is skipped and 7615 setup behaves exactly as it always has. 7616 7617 It must be an OBJECT, and that check is load-bearing rather than defensive. Snippets in the 7618 wild pass the domain list as separate arguments instead of one space-separated string -- 7619 `setup(6980, "clientes.example.com", "example.com", "*.example.com")` -- so a third argument 7620 is not evidence of consent mode at all; it is somebody's second domain. Testing this for 7621 truthiness took dkv.es (account 6980) dark: the hold engaged, `consent.timeout` was 7622 undefined on a string so it waited the default 3 seconds, nothing called grant() because 7623 nothing knew it should, and the tracker switched itself off. Silently -- the library loads, 7624 `is_on()` is true, and no request is ever made. 7625 7626 So: only a real object opts in. Extra positional arguments stay what they always were, 7627 which is ignored. 7628 7629 Truthy means: do nothing at all until the site tells us the visitor agreed. Nothing means 7630 nothing -- no cookie read, no sessionStorage, no request. Note `ns.system.is_on()` below 7631 reads `stat_track_u_id`, so the hold has to sit ABOVE that call rather than
7631inside it; 7632 reading a cookie is itself access to the visitor's device. 7633 7634 Deliberately NOT hiding the page while we wait. The tracker's usual trick for avoiding 7635 flicker is `body {visibility:hidden}` until it is ready, and reusing it here would hide the 7636 consent banner the visitor is supposed to be reading -- a blank page until timeout, on every 7637 first visit. So the page renders unpersonalized and personalization appears after consent. 7638 7639 `consent.timeout` is a safety net for a site with a broken or missing CMP, not a delay: a 7640 returning visitor's consent manager answers from its own storage within a frame or two. On 7641 expiry we take the OFF path, because an account that enabled this did so to be able to say 7642 nothing happens without consent. */ 7643 /* Default, so that `_S_T.consent.status()` answers on EVERY site rather than only on one that 7644 engaged the hold -- the site you most need to ask is the one where you are not sure whether 7645 consent mode is involved at all, and there `ns.consent` would otherwise be undefined and the 7646 question would throw instead of answering "off". Replaced wholesale by the real object below 7647 when a hold actually starts; grant/deny are no-ops until then, because with no hold in 7648 progress there is nothing to release. */ 7649 ns.consent = 7650 { status() 7651 { return consent_denied ? 'denied' : consent_granted ? 'granted' : consent_pending ? 'waiting' : 'off'; 7652 }, 7653 grant() {}, 7654 deny() {} 7655 }; 7656 7657 ns.setup = function(server_id, domains, consent) 7658 { is_sync_blocking = !document.body; // assume: sync tracker code is in <head> 7659 if (consent && typeof consent == 'object' && !consent_pending && !consent_denied && !ns.server_id) 7660 { consent_pending = [server_id, domains]; 7661 let settled = false; 7662 let wait_msec = consent.timeout > 0 ? consent.timeout : 3000; 7663 /* Expiry means nothing on the page ever answered -- not that the visitor declined. That 7664 is a broken installation rather than a visitor choice, and it is otherwise perfectly 7665 silent: the library loads, `is_on()` is true, no request is made and n
7665o error appears. 7666 dkv.es sat in exactly this state and it took a customer noticing, so it says so. 7667 7668 Unconditional, NOT gated on testing mode, because `ns.te_eff()` reads the 7669 `_stat_track_s_id` cookie -- the very access the hold exists to avoid. Volume is not a 7670 concern: it can only fire on a site that turned consent mode ON and then never wired a 7671 callback, where every visitor is already being lost. A correctly wired site never 7672 reaches this line, and an explicit deny() below prints nothing, because a visitor 7673 saying no is the feature working. */ 7674 let timer = setTimeout 7675 ( () => 7676 { errmsg 7677 ( 'consent mode is on for this account, but nothing called '+ 7678 CONF__PERSONYZE_COMMANDS_JS_VAR_NAME+'.consent.grant() or .deny() within '+wait_msec+ 7679 'ms, so tracking and personalization stayed OFF. Wire your consent manager to those calls, '+ 7680 'or turn consent mode off in the panel.' 7681 ); 7682 ns.consent.deny(); 7683 }, 7684 wait_msec 7685 ); 7686 /* Public API for the CMP adapter. Idempotent on purpose: consent managers fire their 7687 listener more than once (initial state, then again on a change), and a second grant 7688 must not re-run setup and log a second page view. */ 7689 ns.consent = 7690 { /* Readable from the console, because the failure mode this feature has is a SILENT 7691 one: the library loads, `is_on()` is true, and nothing ever happens. There is no 7692 error to find and no request to inspect, so without somewhere to look, "no 7693 personalization" and "waiting for consent" are indistinguishable from outside. 7694 That is what made the dkv.es breakage take a customer report rather than a 7695 console glance. */ 7696 status() 7697 { return consent_denied ? 'denied' : consent_granted ? 'granted' : consent_pending ? 'waiting' : 'off'; 7698 }, 7699 grant() 7700 { if (!settled) 7701 { settled = true; 7702 clearTimeout(timer); 7703 let args = consent_pending; 7704 consent_pending = null; 7705 consent_granted = true; 7706 /* Re-entering setup with no `consent` argument runs the ordinary path. 7707 `is_sync_blocking` is recomputed from `!document.body` on the way in, and 7708 by now the body exists -- so the resumed run cannot hide a page the 7709 visitor is already looking at. */ 7710 ns.setup(args[0], args[1]); 7711 } 7712 }, 7713 deny() 7714 { if (!settled) 7715 { settled = true; 7716 clearTimeout(timer); 7717 let args = consent_pending; 7718 consent_pending = null; 7719 consent_denied = true; 7720 ns.setup(args[0], args[1]); 7721 } 7722 } 7723 }; 7724 return; 7725 } 7726 if (!ns.server_id) 7727 { ns.server_id = server_id; 7728 if (!consent_denied && ns.system.is_on()) 7729 { unblock_all_timer = setTimeout 7730 ( () => 7731 { force_unblock_all = true; 7732 unblock_all(); 7733 }, 7734 1000 ); 7735 if (is_sync_blocking) 7736 { var head = document.getElementsByTagName('head')[0]; 7737 var style = ns.new_elem('style', head); 7738 var css = 'body {visibility:hidden}'; 7739 if (style.styleSheet) 7740 { style.styleSheet.cssText = css; 7741 } 7742 else 7743 { style.appendChild(document.createTextNode(css)); 7744 } 7745 ns.reg_ondropcss 7746 ( () => 7747 { if (style.parentNode) 7748 { head.removeChild(style); 7749 } 7750 } 7751 ); 7752 } 7753 if (window.sessionStorage) 7754 { try 7755 { var containers = JSON.parse(sessionStorage['_S_T.c'] || 'null'); 7756 domains = domains || JSON.parse(sessionStorage['_S_T.d'] || 'null'); 7757 let gtm = JSON.parse(sessionStorage['_S_T.gtm'] || 'null'); 7758 //JSON.stringify([new_gtm_data_layer_name, new_gtm_grab, new_gtm_product_id_column]); 7759 tealium_storage_name = sessionStorage['_S_T.tealium'] || tealium_storage_name; 7760 if (gtm) 7761 { gtm_data_layer_name = gtm[0]; 7762 gtm_grab = gtm[1]; 7763 gtm_product_id_column = gtm[2]; 7764 gtm_key = gtm[3]; 7765 }
7766 if (containers) 7767 { set_conts(containers); 7768 } 7769 } 7770 catch (e) 7771 { ns.error(e); 7772 } 7773 } 7774 init_testing_mode(); // before any get_js_url(), which picks the testing or production build 7775 if (!domains || !containers) 7776 { ns.load_libs('index'); 7777 } 7778 else 7779 { set_domains(domains); 7780 if (get_personyze_override().t != null) 7781 { ns.te(get_personyze_override().t); 7782 } 7783 is_parallel = true; 7784 ns.load_libs 7785 ( 'index', 7786 null, 7787 exec_ondropcss // onerror 7788 ); 7789 var timer = setTimeout(exec_ondropcss, 1000); 7790 ns.reg_onready 7791 ( () => 7792 { init_containers.immediate_call(); 7793 send_request 7794 ( LOG_EVENT_SEND_IMMED, 7795 1, 7796 null, 7797 null, 7798 () => // onload 7799 { clearTimeout(timer); 7800 }, 7801 exec_ondropcss, // onerror 7802 () => ins_status_log_visit // checker_func 7803 ); 7804 } 7805 ); 7806 } 7807 } 7808 else 7809 { exec_ondropcss(); 7810 exec_commands(); 7811 } 7812 } 7813 else 7814 { ns.su2 = server_id; 7815 } 7816 // The whole page is hidden until the decision lands; this is the floor under a decision 7817 // that never arrives at all. See WConf::$hide_body_max for what the number is trading. 7818 setTimeout(exec_ondropcss, 1500); 7819 }; 7820 7821 ns.install = function(new_f, hide_body_till_actions, containers, domains, embedded_act_params, actions_qelem_ids, is_async_blocking, new_add_to_cart, new_send_after_unload, new_add_to_wishlist, new_ga_account, new_gtm_data_layer_name, new_gtm_grab, new_gtm_product_id_column, new_remove_from_cart, new_tealium_storage_name, new_gtm_key, new_ga_is_active, new_default_send_dests, new_event_send_with_delay, new_event_send_now, new_ga_is_gtm, new_add_to_cart_success_html, new_add_to_wishlist_success_html, new_add_to_cart_error_html, new_add_to_wishlist_error_html) 7822 { try 7823 { if (new_add_to_cart) 7824 { add_to_cart_func = typeof(new_add_to_cart)=='string' ? eval(new_add_to_cart) : new_add_to_cart; 7825 } 7826 if (new_add_to_wishlist) 7827 { add_to_wishlist_func = typeof(new_add_to_wishlist)=='string' ? eval(new_add_to_wishlist) : new_add_to_wishlist; 7828 } 7829 if (new_remove_from_cart) 7830 { remove_from_cart_func = typeof(new_remove_from_cart)=='string' ? eval(new_remove_from_cart) : new_remove_from_cart; 7831 } 7832 } 7833 catch (e) 7834 { ns.error(e); 7835 } 7836 event_send_now = +new_event_send_now; 7837 event_send_with_delay = +new_event_send_with_delay; 7838 send_after_unload = new_send_after_unload; 7839 ga_is_active = new_ga_is_active; 7840 default_send_dests = new_default_send_dests || ''; 7841 ga_is_gtm = new_ga_is_gtm; 7842 gtm_data_layer_name = new_gtm_data_layer_name; 7843 gtm_grab = new_gtm_grab; 7844 gtm_product_id_column = new_gtm_product_id_column; 7845 gtm_key = new_gtm_key; 7846 tealium_storage_name = new_tealium_storage_name; 7847 add_to_cart_success_html = new_add_to_cart_success_html; 7848 add_to_wishlist_success_html = new_add_to_wishlist_success_html; 7849 add_to_cart_error_html = new_add_to_cart_error_html; 7850 add_to_wishlist_error_html = new_add_to_wishlist_error_html; 7851 7852 set_domains(domains); 7853 if (window.sessionStorage) 7854 { sessionStorage['_S_T.d'] = JSON.stringify(domains); 7855 sessionStorage['_S_T.c'] = JSON.stringify(containers); 7856 sessionStorage['_S_T.gtm'] = JSON.stringify([gtm_data_layer_name, gtm_grab, gtm_product_id_column, gtm_key]); 7857 sessionStorage['_S_T.tealium'] = tealium_storage_name || ''; 7858 } 7859 /* The factor baked into this file is the BUILT version -- the file was written by whichever app host 7860 rebuilt first, so it can be ahead of what has been published to browsers. It must never become our 7861 factor on its own. Adopting it is what used to make the browser re-request index.js under a version 7862 name that a still-lagging host answered with the PREVIOUS build, which `expires 30d` then pinned 7863 under that name for a month; and since install() would then write the older number back into the 7864 cookie, every following page view repeated the same request and got the same poisoned cache entry. 7865 The factor belongs to log_visit now. 7866 7867 Bootstrapping from it when there is no factor at all is still worth doing -- a visitor whose 7868 log_visit never completes would otherwise refetch index.js under a fresh random url on every page 7869 view -- but it goes in marked dirty, so urls stay on the quarantined 'b' key until log_visit 7870 confirms the version is published. This sets a cookie, so it must come after domains are set. 7871 */ 7872 if (!get_f() && new_f) 7873 { set_f(new_f, 1); 7874 }
7875 set_conts(containers); 7876 for (var i in embedded_act_params) 7877 { if (embedded_act_params.hasOwnProperty(i) && !ns.act_params[i]) 7878 { ns.act_params[i] = embedded_act_params[i]; 7879 } 7880 } 7881 if (!is_ready) 7882 { install_focus(); 7883 memorize_not_executed(); 7884 /* THE PAINT GATE. `is_async_blocking` is the per-account opt-in that runs the 7885 pre-hide even when the library loaded asynchronously - which is the one path 7886 that can arrive AFTER the page is on screen. When it does, the "pre"-hide is 7887 nothing of the sort: it blanks content the visitor is reading and hands it 7888 back a moment later. Measured on personyze.com (Jacob, 28 Aug 26): tracker at 7889 4136ms, first paint at 4180ms - a photo-finish that the tracker loses on any 7890 slow connection, and every loss was a manufactured flash. The flag was ON for 7891 exactly one account in the fleet, because this failure made it 7892 unrecommendable. 7893 7894 So the async opt-in now defers to reality: hide only while the content is 7895 still unseen (which includes elements covered by the snippet's inline 7896 pre-hide since the first byte - see content_already_seen). Once the visitor 7897 has the page, this view behaves as if the flag were off - the change pops in 7898 late rather than the page blinking. 7899 7900 The sync paths are NOT gated, deliberately: `is_sync_blocking` means the 7901 library ran before <body> existed, and `wants_to_block` is set by that same 7902 path - both are pre-paint by construction, and gating them would only add a 7903 way for a support of paint-timing quirks to break the genuine anti-flicker. */ 7904 if (wants_to_block || is_sync_blocking || (is_async_blocking && !content_already_seen())) 7905 { pre_init_containers(actions_qelem_ids); 7906 } 7907 ins_status_done(); 7908 if (!is_parallel) 7909 { ns.reg_onready 7910 ( () => 7911 { init_containers.immediate_call(); 7912 send_request(LOG_EVENT_SEND_IMMED, 1); 7913 } 7914 ); 7915 } 7916 } 7917 if (!hide_body_till_actions || !is_sync_blocking) 7918 { exec_ondropcss(); 7919 } 7920 if (tracker_events.buffered_mode) 7921 { log_event(0, '', tracker_events.buffered_mode); 7922 } 7923 }; 7924 7925 ns.log_visit = function(new_f, visit_id, user_data, log_navigation_on, conds, actions, client_cookie, clear_conds, clear_actions, bh, ga_cond_names, pagegroups, asud, session_start_time, hs, interests, new_host_id, try_debuggers, article_viewed_internal_id, factor_dirty) 7926 { var f = get_f(); 7927 if (f && f<new_f && !is_ready) 7928 { set_f(new_f, factor_dirty); 7929 } 7930 ins_status = {new_f, factor_dirty, visit_id, actions, clear_conds, clear_actions}; 7931 ns.si = session_start_time; 7932 actions_q_add(actions); 7933 /* THE WYSIWYG BACKSTOP IS ARMED HERE, and deliberately not where the sheet goes up. 7934 7935 Measured from the moment the actions are KNOWN, not from the moment the library 7936 loaded. Those are the same instant on a warm connection and very far apart on a 7937 cold one, and starting the clock at load meant a page whose decision took longer 7938 than the backstop uncovered its elements while the change that was going to alter 7939 them had not arrived yet - the original content flashing before the personalized 7940 content, which is the whole thing this machinery exists to prevent, reintroduced on 7941 exactly the connections that can least afford it. 7942 7943 From here it is a real bound: the actions are in hand, a WYSIWYG change is applied 7944 synchronously inside its action's `onexec`, so anything still not applied is not 7945 going to be applied this pass. Before this point nothing is armed and the sheet 7946 behaves exactly as it always did - `force_unblock_all` remains its backstop, so a 7947 decision that never arrives is still covered. */ 7948 if (!unblock_qelem_timer) 7949 { unblock_qelem_timer = setTimeout(unblock_qelem, 300); 7950 } 7951 narrow_container_hide(actions); 7952 current_conditions = conds; 7953 current_pagegroups = pagegroups; 7954 current_interests = interests; 7955 host_id = new_host_id; 7956 set_user_data(user_data); 7957 if (asud) 7958 { for (var action_id in asud) 7959 { if (asud.hasOwnProperty(action_id)) 7960 { actions_subst_user_data[action_id] = asud[action_id]; 7961 } 7962 } 7963 } 7964 if (ga_cond_names) 7965 { for (var i=0; i<ga_cond_names.length; i++) 7966 { ns.ga_send('condition', ga_cond_names[i]); 7967 } 7968 } 7969 // bh, session_start_time 7970 ns.cookie_obj('_stat_track_s_id', {_bh: bh, _si: session_start_time}); 7971 /* AND READABLE BY AN ACTION. `bh` names the tracker host this session's visitor tables 7972 live on - visitor data is partitioned across an account's hosts, so a reader that 7973 does not know it is guessing. It has always been round-tripped in the cookie and the 7974 query string; an action that needs to tell a service of ours which host to read had 7975 no way to see it. The AI Chat placement passes it on to the chat endpoint. */ 7976 ns.best_hosts = bh || ''; 7977 // hs, client_cookie 7978 var obj = hs-0 ? {hs: 1} : {}; 7979 if (client_cookie) 7980 { if (client_cookie.id - 0) 7981 { obj.uid = ns.uid = client_cookie.id-0; 7982 } 7983 if (client_cookie.st - 0) 7984 { obj.st = client_cookie.st-0; 7985 } 7986 if (client_cookie.ls - 0) 7987 { obj.ls = client_cookie.ls-0; 7988 } 7989 if ((client_cookie.sy+'').match(/^(\d+:-?\d+)?(,\d+:-?\d+)*$/)) 7990 { obj.sy = client_cookie.sy+''; 7991 } 7992 } 7993 obj = ns.cookie_obj('stat_track_u_id', obj); 7994 ns.install_spa(log_navigation_on); 7995 // debuger + done 7996 init_debugger(obj.dg, try_debuggers, () => {ins_status_done(true)}); 7997 if (article_viewed_internal_id) 7998 { wait_for_article_delivery(article_viewed_internal_id); 7999 } 8000 }; 8001 8002 ns.install_spa = function(log_navigation_on) 8003 { const ON_PATHNAME = 1; 8004 const ON_SEARCH = 2;
8005 const ON_HASH = 4; 8006 if (!ns.lno) 8007 { let prev_pathname = location.pathname; 8008 let prev_search = location.search; 8009 let prev_hash = location.hash; 8010 let h_timeout = null; 8011 let onchange = infrequent_func 8012 ( () => 8013 { clearTimeout(h_timeout); 8014 let {pathname, search, hash} = location; 8015 let mask = (pathname!=prev_pathname && ON_PATHNAME) | (search!=prev_search && ON_SEARCH) | (hash!=prev_hash && ON_HASH); 8016 if (mask) 8017 { prev_pathname = pathname; 8018 prev_search = search; 8019 prev_hash = hash; 8020 // clear 'grab url' 8021 use_location = null; 8022 let {containers} = ns; 8023 for (let c_id in containers) 8024 { if (containers.hasOwnProperty(c_id)) 8025 { containers[c_id].inited &= ~8192; 8026 } 8027 } 8028 // 8029 reset_conts_retry(); // another page in this SPA - every selector deserves a fresh, undelayed try 8030 init_containers(); 8031 // 8032 if (log_navigation_on & mask) 8033 { ns.log_navigation(); 8034 } 8035 else 8036 { check_for_actions(); 8037 } 8038 } 8039 }, 8040 40 8041 ); 8042 let listeners = {}; 8043 ns.new_elem(window).add_props 8044 ( { onhashchange: 8045 { func: onchange, 8046 listeners 8047 } 8048 } 8049 ); 8050 let {pushState, replaceState} = history; 8051 if (pushState && replaceState) 8052 { try 8053 { history.pushState = function(a, b, c) 8054 { setTimeout(onchange, 0); 8055 setTimeout(onchange, 100); 8056 setTimeout(onchange, 200); 8057 setTimeout(onchange, 300); 8058 return pushState.call(history, a, b, c); 8059 }; 8060 history.replaceState = function(a, b, c) 8061 { setTimeout(onchange, 0); 8062 setTimeout(onchange, 100); 8063 setTimeout(onchange, 200); 8064 setTimeout(onchange, 300); 8065 return replaceState.call(history, a, b, c); 8066 }; 8067 } 8068 catch (e) 8069 { ns.error(e); 8070 } 8071 } 8072 let interval = setInterval(onchange.immediate_call, 500); 8073 ns.lno = function() 8074 { listeners.onhashchange?.remove(); 8075 history.pushState = pushState; 8076 history.replaceState = replaceState; 8077 clearInterval(interval); 8078 ns.lno = null; 8079 }; 8080 } 8081 }; 8082 8083 ns.reg_ondropcss = function(f) 8084 { wants_to_block = true; 8085 if (is_ready) 8086 { f(); 8087 } 8088 else 8089 { ondropcss = ns.pair_funcs(ondropcss, f); 8090 } 8091 }; 8092 8093 ns.reg_onevent = function(name, func) 8094 { onevent[name] = func; 8095 }; 8096 8097 ns.reg_onerr = function(name, func) 8098 { onerr[name] = func; 8099 }; 8100 8101 ns.add_to_cart = function(products, action_id, arg) 8102 { return add_to_cart_or_to_wishlist('add_to_cart', products, action_id, arg); 8103 }; 8104 8105 ns.add_to_wishlist = function(products, action_id, arg) 8106 { return add_to_cart_or_to_wishlist('add_to_wishlist', products, action_id, arg); 8107 }; 8108 8109 ns.remove_from_cart = function(products, action_id, arg) 8110 { return add_to_cart_or_to_wishlist('remove_from_cart', products, action_id, arg); 8111 }; 8112 8113 // public interface 8114 window[CONF__PERSONYZE_COMMANDS_JS_VAR_NAME] = ns; 8115 8116 return true; 8117 } 8118 8119// inject_code 8120function _new_elem_tmp_2(ns) 8121 { /** @param {String} code - Javascript code, or <script>, <style> or <link> HTML tags. Also supports MSIE conditional comments (only downlevel-hidden). 8122 @param {boolean} local_scope - If false, will eval javascript code in global scope, so "var" will add variables to window 8123 **/ 8124 function inject_code(code, local_scope, ondone) 8125 { html_cut_exec_code 8126 ( code, 8127 code => 8128 { try 8129 { if (code) 8130 { if (local_scope) 8131 { eval(code); // alter "this" (call()) to affect 8132 } 8133 else 8134 { var global_exec = window.execScript || function(js) {eval.call(window, js)}; 8135 global_exec(code); 8136 } 8137 } 8138 } 8139 catch (e) 8140 { console.error(e); 8141 } 8142 ondone?.(); 8143 } 8144 ); 8145 } 8146 8147 /** @param {String} code - Will cut <script>, <style> and <link> HTML tags, and execute/load assets. Also supports MSIE conditional comments (only downlevel-hidden). 8148 **/ 8149 function html_cut_exec_code(code, ondone) 8150 { var head = document.getElementsByTagName('head')[0]; 8151 var loading_sync = false; 8152 var then = ''; 8153 var found = true; 8154 while (found) 8155 { found = false;
8156 code = (code || '').replace 8157 ( /^\s*(<!--[\S\s]*?-->)\s*|^(\s*<(script|style|noscript)(?:\s+[\w\-:]+(?:\s*=\s*(?:[^\s<\/>="']+|"[^"]*"|'[^']*'))?\s*)*>\s*)([\S\s]*?)(<\/\3\s*>\s*)|^\s*(<link(?:\s+[\w\-:]+(?:\s*=\s*(?:[^\s<\/>="']+|"[^"]*"|'[^']*')))*\/?>)\s*/i, 8158 (all, comment, open, tag, contents, close, link) => 8159 { found = true; 8160 if (tag == 'script') 8161 { if (loading_sync) 8162 { then += all; 8163 } 8164 else if (contents) 8165 { contents = contents.replace(/^\s*<!\[CDATA\[|\]\]>\s*$/g, ''); 8166 var global_exec = window.execScript || function(js) {eval.call(window, js)}; 8167 ns.pcall(global_exec, window, [contents]); 8168 } 8169 else 8170 { var attributes = {}; 8171 open.replace 8172 ( /\s([\w\-:]+)(?:\s*=\s*(?:(["'])([\S\s]*?)\2|([^\s</>="']+)))?/g, 8173 (all, name, qt, value, or_value) => 8174 { attributes[name.toLowerCase()] = value || or_value || ''; 8175 } 8176 ); 8177 var script = ns.new_elem('script', head, {attributes}); 8178 if (attributes.async == null) 8179 { loading_sync = true; 8180 if ('onload' in script) 8181 { script.onload = function() 8182 { setTimeout(next, 100); 8183 }; 8184 } 8185 else if ('onreadystatechange' in script) 8186 { script.onreadystatechange = function() 8187 { if (script.readyState == 'complete') 8188 { next(); 8189 } 8190 else if (script.readyState == 'loaded') 8191 { setTimeout(next, 3000); 8192 } 8193 }; 8194 } 8195 else 8196 { setTimeout(next, 3000); 8197 } 8198 } 8199 } 8200 } 8201 else if (comment) 8202 { if (ns.IS_MSIE) 8203 { var m = comment.match(/^<!--\[\s*if\b([^\]]+)\]>([\S\s]*)<!\[\s*endif\s*\]-->$/i); 8204 if (m) 8205 { try 8206 { var ua = navigator.userAgent; 8207 var version = parseFloat(ua.substring(ua.indexOf("MSIE ")+5)) || 0; 8208 if 8209 ( eval 8210 ( m[1].replace 8211 ( /(?:\b([lg])t(e?)\s+)?\bIE(\s+[\d\.]+)?\b/gi, 8212 (all, l_g, e, v) => 8213 { return !v ? '1' : '('+version+(!l_g ? '==' : (l_g=='l' || l_g=='L' ? '<' : '>')+(e ? '=' : ''))+v+')'; 8214 } 8215 ) 8216 ) 8217 ) 8218 { return m[2]; 8219 } 8220 } 8221 catch (e) 8222 { window.console?.error(e); 8223 } 8224 } 8225 } 8226 } 8227 else if (tag != 'noscript') 8228 { ns.new_elem(link || (open+contents+close), head); 8229 } 8230 return ''; 8231 } 8232 ); 8233 } 8234 if (!loading_sync) 8235 { next(); 8236 } 8237 function next() 8238 { if (then) 8239 { html_cut_exec_code(then+code, ondone); 8240 } 8241 else 8242 { ondone(code); 8243 } 8244 then = code = null; // new call to next() must do nothing 8245 } 8246 } 8247 8248 return inject_code; 8249 } 8250 8251// joyquery 8252function _new_elem_tmp_3(ns) 8253 { var XML_ELEMENT_NODE = 1; 8254 var XML_TEXT_NODE = 3; 8255 var XML_CDATA_SECTION_NODE = 4; 8256 var XML_DOCUMENT_NODE = 9; 8257 var XML_HTML_DOCUMENT_NODE = 13; 8258 8259 var TOKENIZER = /[~|^$*!]?=|::|([+\-]?\d+n(?:\s*[+\-]?\d+|\s*[+\-]\s+[+\-]?\d+)?)|((?:[\w_\-\x80-\xFF]|\\(?:[0-9A-Fa-f]{1,6}|.))+)|("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*')|(\s+|\/\*[\S\s]*?\*\/)|./gi; 8260 var COMPLEX_NUMBER_TOKENIZER = /^\s*([+\-])\s*([+\-]?\d+)$/; 8261 var UNESCAPER = /\\(?:([0-9A-Fa-f]{1,6})|.)/g; 8262 8263 var TOKEN_TYPE_COMPLEX_NUMBER = 0; 8264 var TOKEN_TYPE_IDENT = 1; 8265 var TOKEN_TYPE_STRING = 2; 8266 var TOKEN_TYPE_SPACE = 3; 8267 var TOKEN_TYPE_OTHER = 4; 8268 8269 var SELF = 0; 8270 var CHILD = 1; 8271 var DESCENDANT = 2; 8272 var DESCENDANT_OR_SELF = 3; 8273 var PARENT = 4; 8274 var ANCESTOR = 5; 8275 var ANCESTOR_OR_SELF = 6; 8276 var FOLLOWING_SIBLING = 7; 8277 var FIRST_FOLLOWING_SIBLING = 8; 8278 var PRECEDING_SIBLING = 9; 8279 var FIRST_PRECEDING_SIBLING = 10; 8280 8281 var AXIS_NAMES = 8282 { 'self': SELF, 8283 'child': CHILD, 8284 'descendant': DESCENDANT, 8285 'descendant-or-self': DESCENDANT_OR_SELF, 8286 'parent': PARENT, 8287 'ancestor': ANCESTOR, 8288 'ancestor-or-self': ANCESTOR_OR_SELF, 8289 'following-sibling': FOLLOWING_SIBLING, 8290 'first-following-sibling': FIRST_FOLLOWING_SIBLING, 8291 'preceding-sibling': PRECEDING_SIBLING, 8292 'first-preceding-sibling': FIRST_PRECEDING_SIBLING 8293 }; 8294 8295 var FUNCTIONS = 8296 { empty() 8297 { var node = this.node; 8298 for (var e=node.firstChild; e; e=e.nextSibling) 8299 { if (e.nodeType!=XML_TEXT_NODE && e.nodeType!=XML_CDATA_SECTION_NODE || e.length) 8300 { return false;
8301 } 8302 } 8303 return true; 8304 } 8305 }; 8306 8307 var CMP_IN_LOWERCASE_ATTRS = 8308 { type: 1, 8309 method: 1 8310 }; 8311 8312 var SETTINGS = 8313 { /* One account's worth of selectors, not four. The tracker asks for every pending container's 8314 selector on every pass (init_containers in stat-track-lib), so the working set is however 8315 many containers the account has - and at 4 the cache filled up within the first few of 8316 them and was thrown away whole (see the wipe in compile()), so it never once answered a 8317 hit and every pass re-parsed every selector from scratch. Measured on our own panel, where 8318 an account with 87 containers scans a page none of them can match: parsing was about a 8319 seventh of each 77ms pass. An entry is a parsed path - plain objects plus one compiled 8320 `cond` function per step - so even a fat account costs kilobytes, and the wipe still keeps 8321 the ceiling hard. */ 8322 compiler_cache_max: 300, 8323 emulate: false 8324 }; 8325 8326 // jQuery uses compiler cache. Why don't i? 8327 var compiler_cache = {length: 0}; 8328 8329 function compile(css, compiler_cache_max, no_context_node, use_element_child, use_has_attribute, is_xml) 8330 { // cache hit? 8331 var css_for_cache = ((no_context_node ? 1 : 0) | (use_element_child ? 2 : 0) | (use_has_attribute ? 4 : 0) | (is_xml ? 8 : 0)) + css; 8332 if (compiler_cache[css_for_cache]) 8333 { return compiler_cache[css_for_cache]; 8334 } 8335 // cache miss 8336 var token_types = []; 8337 var tokens = []; 8338 var i = 0; 8339 css.replace 8340 ( TOKENIZER, 8341 function(token, is_complex_number, is_ident, is_string, is_space) 8342 { tokens.push(token); 8343 token_types.push(is_complex_number ? TOKEN_TYPE_COMPLEX_NUMBER : is_ident ? TOKEN_TYPE_IDENT : is_string ? TOKEN_TYPE_STRING : is_space ? TOKEN_TYPE_SPACE : TOKEN_TYPE_OTHER); 8344 } 8345 ); 8346 function read_ident(can_asterisk, can_empty) 8347 { var token = tokens[i]; 8348 if (can_asterisk && token=='*' || token_types[i]==TOKEN_TYPE_IDENT) 8349 { i++; 8350 return css_unescape(token); 8351 } 8352 else if (can_empty && (token=='#' || token=='.' || token=='[' || token==':')) 8353 { return '*'; 8354 } 8355 } 8356 function read_string() 8357 { var token = tokens[i]; 8358 if (token_types[i] == TOKEN_TYPE_STRING) 8359 { i++; 8360 return css_unescape(token.slice(1, -1)); 8361 } 8362 var value = parseFloat(token); 8363 if (!isNaN(value)) 8364 { i++; 8365 return value; 8366 } 8367 } 8368 function read_space() 8369 { if (token_types[i] == TOKEN_TYPE_SPACE) 8370 { return tokens[i++]; 8371 } 8372 } 8373 function zpad4(text) 8374 { return '000'.substr(0, 4-text.length) + text; 8375 } 8376 function css_unescape(text) 8377 { if (text.indexOf('\\') == -1) 8378 { return text; 8379 } 8380 else 8381 { return text.replace 8382 ( UNESCAPER, 8383 function(match, hexa) 8384 { if (!hexa) 8385 { return match.charAt(1); 8386 } 8387 else 8388 { return eval('"\\u'+zpad4(hexa.length<5 ? hexa : hexa.substr(hexa.length-4))+'"'); 8389 } 8390 } 8391 ); 8392 } 8393 } 8394 function error(message) 8395 { throw new Error(message || 'Unsupported selector'); 8396 } 8397 function parse_simple_selector(cur_path, axis) 8398 { var token = read_ident(true, true); 8399 if (!token) 8400 { return false; 8401 } 8402 if (tokens[i] == '::') 8403 { i++; 8404 axis = AXIS_NAMES[token]; 8405 if (axis == null) 8406 { error('Unsupported axis: '+token); 8407 } 8408 token = read_ident(true); 8409 if (!token) 8410 { error(); 8411 } 8412 } 8413 var simple_selector = 8414 { name: token=='*' ? '' : is_xml ? token : token.toUpperCase(), 8415 axis: axis, 8416 from: 1, 8417 limit: 0x7FFFFFFF, 8418 sub: [], 8419 cond: null 8420 }; 8421 var conditions = [[], [], []]; 8422 function add_condition(name, oper, value, func_args, first_complex_number_arg) 8423 { if (name==null || value==null) 8424 { error(); 8425 } 8426 var func_arg = func_args && func_args[0]; 8427 if (oper == ':from') 8428 { simple_selector.from = func_arg>1 ? func_arg-0 : 1; 8429 return; 8430 } 8431 if (oper == ':limit') 8432 { simple_selector.limit = func_arg>0 ? func_arg-0 : 0x7FFFFFFF; 8433 return; 8434 } 8435 if (oper.charAt(0) != ':') 8436 { name = name.toLowerCase(); 8437 var name_js = JSON.stringify(name); 8438 var get_attr = "n.getAttribute("+name_js+")"; 8439 value += ''; 8440 var use_value = oper!='~=' ? value : ' '+value+' ';
8441 var value_js = JSON.stringify(use_value); 8442 if (CMP_IN_LOWERCASE_ATTRS[name]) 8443 { get_attr = "(n["+name_js+"]?("+get_attr+"||'').toLowerCase():"+get_attr+")"; 8444 value_js = "(n["+name_js+"]?"+JSON.stringify(use_value.toLowerCase())+":"+value_js+")"; 8445 } 8446 } 8447 var func; 8448 var priority = 0; 8449 switch (oper) 8450 { case '' : 8451 func = use_has_attribute ? "n.hasAttribute("+name_js+")" : "n.getAttribute("+name_js+")!=null"; 8452 break; 8453 case '=': 8454 func = get_attr+"=="+value_js; 8455 break; 8456 case '!=': 8457 func = "("+get_attr+"||'')!="+value_js; 8458 break; 8459 case '^=': 8460 func = "(a="+get_attr+")&&a.substr(0,"+(value.length)+")=="+value_js; 8461 break; 8462 case '$=': 8463 func = "(a="+get_attr+")&&a.substr(a.length-"+(value.length)+")=="+value_js; 8464 break; 8465 case '*=': 8466 func = "(a="+get_attr+")&&a.indexOf("+value_js+")!=-1"; 8467 break; 8468 case '|=': 8469 func = "(a="+get_attr+")&&(b="+value_js+")&&(a==b||a.substr(0,"+(value.length+1)+")==b+'-')"; 8470 break; 8471 case '~=': 8472 get_attr = "(' '+("+(name!='class' ? "" : "n.className!=null?n.className:")+get_attr+")+' ')"; 8473 var try_class_list = name!='class' ? "" : "n.classList?n.classList.contains("+JSON.stringify(value)+"):"; 8474 func = "("+try_class_list+get_attr+".indexOf("+(name!='class' ? value_js : JSON.stringify(use_value))+")!=-1)"; 8475 break; 8476 case ':root': 8477 func = "n==this.document.documentElement"; 8478 break; 8479 case ':first-child': 8480 func = "!n.previousElementSibling"; 8481 if (!use_element_child) 8482 { func = "l(0,1)==0"; 8483 priority = 1; 8484 } 8485 break; 8486 case ':last-child': 8487 func = "!n.nextElementSibling"; 8488 if (!use_element_child) 8489 { func = "l(0,1)==l()-1"; 8490 priority = 1; 8491 } 8492 break; 8493 case ':only-child': 8494 func = "!n.previousElementSibling&&!n.nextElementSibling"; 8495 if (!use_element_child) 8496 { func = "l()==1"; 8497 priority = 1; 8498 } 8499 break; 8500 case ':nth-child': 8501 func = !first_complex_number_arg ? "l(0,1)=="+(func_arg-1) : "(l(0,1)-("+(first_complex_number_arg.real-1)+"))%"+first_complex_number_arg.imag+"==0"; 8502 priority = 1; 8503 break; 8504 case ':nth-last-child': 8505 func = !first_complex_number_arg ? "l()-l(0,1)=="+func_arg : "(l()-l(0,1)-("+first_complex_number_arg.real+"))%"+first_complex_number_arg.imag+"==0"; 8506 priority = 1; 8507 break; 8508 case ':first-of-type': 8509 func = "l(1,1)==0"; 8510 priority = 1; 8511 break; 8512 case ':last-of-type': 8513 func = "l(1,1)==l(1)-1"; 8514 priority = 1; 8515 break; 8516 case ':only-of-type': 8517 func = "l(1)==1"; 8518 priority = 1; 8519 break; 8520 case ':nth-of-type': 8521 func = !first_complex_number_arg ? "l(1,1)=="+(func_arg-1) : "(l(1,1)-("+(first_complex_number_arg.real-1)+"))%"+first_complex_number_arg.imag+"==0"; 8522 priority = 1; 8523 break; 8524 case ':nth-last-of-type': 8525 func = !first_complex_number_arg ? "l(1)-l(1,1)=="+func_arg : "(l(1)-l(1,1)-("+first_complex_number_arg.real+"))%"+first_complex_number_arg.imag+"==0"; 8526 priority = 1; 8527 break; 8528 case ':not': 8529 func = "!"+func_arg+".evaluate_one(n)"; 8530 priority = 2; 8531 break; 8532 case ':has': 8533 func = func_arg+".evaluate_one(n)"; 8534 priority = 2; 8535 break; 8536 case ':any': 8537 func = "("+func_args.join(".evaluate_one(n)||")+".evaluate_one(n))"; 8538 priority = 2; 8539 break; 8540 case ':focus': 8541 func = "n==this.document.activeElement"; 8542 break; 8543 case ':target': 8544 func = "n.id==this.window.location.hash.substr(1)"; 8545 break; 8546 case ':disabled': 8547 func = "(n.disabled&&n.nodeName!='STYLE'||(a=n.parentNode)&&a.disabled)"; 8548 break; 8549 case ':enabled': 8550 func = "(b=n.disabled)!=null&&!b&&(n.value!=null||n.label!=null)"; 8551 break; 8552 case ':checked': 8553 func = "(n.checked||n.selected)"; 8554 break; 8555 case ':hidden': 8556 func = "!n.offsetWidth&&!n.offsetHeight"; 8557 break; 8558 case ':link': 8559 func = "n.href&&n.nodeName=='A'"; 8560 break; 8561 case ':visited': 8562 func = "0"; 8563 break; 8564 case ':input': 8565 func = "n.value!=null&&((a=n.nodeName)=='INPUT'||a=='SELECT'||a=='TEXTAREA'||a=='BUTTON')"; 8566 break; 8567 default: 8568 var func_name = oper.substr(1).replace(/-/g, '_'); 8569 func_args.splice(0, 0, 'this'); 8570 func = '(this.functions.'+func_name+'||this.FUNCTIONS.'+func_name+').call('+func_args.join(',')+')'; 8571 priority = 1; 8572 break; 8573 } 8574 conditions[priority].push(func); 8575 } 8576 while (true) 8577 { token = tokens[i]; 8578 if (token == '#') 8579 { i++; 8580 add_condition('id', '=', read_ident()); 8581 } 8582 else if (token == '.') 8583 { i++; 8584 add_condition('class', '~=', read_ident()); 8585 } 8586 else if (token == '[') 8587 { i++; 8588 read_space(); 8589 var name = read_ident(true); 8590 read_space(); 8591 token = tokens[i]; 8592 var oper = ''; 8593 var value = ''; 8594 if (token.charAt(token.length-1) == '=') 8595 { i++; 8596 read_space(); 8597 oper = token; 8598 value = read_string(); 8599 read_space(); 8600 } 8601 if (tokens[i++] != ']') 8602 { error(); 8603 } 8604 add_condition(name, oper, value); 8605 } 8606 else if (token == ':') 8607 { i++; 8608 var name = read_ident(true); 8609 var func_args = []; 8610 var first_complex_number_arg = null; 8611 if (tokens[i] == '(') 8612 { while (true) 8613 { i++; 8614 read_space(); 8615 var func_arg; 8616 if (token_types[i] == TOKEN_TYPE_COMPLEX_NUMBER) 8617 { var complex_number = tokens[i].toLowerCase().split('n'); 8618 var real = complex_number[1]; 8619 var imag = complex_number[0]; 8620 if (!real) 8621 { real = '0'; 8622 } 8623 else if (isNaN(parseInt(real))) 8624 { var match = real.match(COMPLEX_NUMBER_TOKENIZER); 8625 real = match[1]=='-' ? -match[2] : match[2]; 8626 } 8627 func_arg = '{imag:'+imag+',real:'+real+'}'; 8628 if (!func_args.length) 8629 { first_complex_number_arg = {imag:imag, real:real}; 8630 } 8631 i++; 8632 } 8633 else 8634 { func_arg = read_string(); 8635 if (func_arg != null) 8636 { func_arg = JSON.stringify(func_arg); 8637 } 8638 else if (token_types[i]==TOKEN_TYPE_IDENT && FUNCTIONS[name+'.raw_argument']) 8639 { func_arg = JSON.stringify(tokens[i++]); 8640 } 8641 else 8642 { func_arg = 'new this.E(this,'+simple_selector.sub.length+')'; 8643 simple_selector.sub.push(parse(SELF, true)); 8644 } 8645 } 8646 func_args.push(func_arg); 8647 read_space(); 8648 if (tokens[i] != ',') 8649 { break; 8650 } 8651 } 8652 if (tokens[i++] != ')') 8653 { error(); 8654 } 8655 } 8656 add_condition('', ':'+name, '', func_args, first_complex_number_arg); 8657 } 8658 else 8659 { break; 8660 } 8661 } 8662 conditions = conditions[0].concat(conditions[1]).concat(conditions[2]); 8663 if (conditions[0]) 8664 { simple_selector.cond = new Function('var a,b,n=this.node,l=this.last;return '+(conditions.join('&&'))); 8665 } 8666 cur_path.push(simple_selector); 8667 return true; 8668 } 8669 function parse(axis, is_func_args) 8670 { var initial_axis = axis; 8671 var cur_path = []; 8672 var path = [cur_path]; 8673 read_space(); 8674 while (tokens[i]) 8675 { if (!parse_simple_selector(cur_path, axis)) 8676 { error(); 8677 } 8678 axis = DESCENDANT; 8679 read_space(); 8680 var token = tokens[i]; 8681 if (token == '>') 8682 { i++; 8683 axis = CHILD; 8684 } 8685 else if (token=='~' || token=='+') 8686 { i++; 8687 axis = token=='~' ? FOLLOWING_SIBLING : FIRST_FOLLOWING_SIBLING; 8688 } 8689 else if (token == ',') 8690 { if (is_func_args) 8691 { break; 8692 } 8693 i++; 8694 axis = initial_axis; 8695 cur_path = []; 8696 path.push(cur_path); 8697 } 8698 else if (token == ')') 8699 { break; 8700 } 8701 read_space(); 8702 } 8703 return path; 8704 } 8705 var path = parse(no_context_node ? DESCENDANT_OR_SELF : DESCENDANT); 8706 if (tokens[i]) 8707 { error(); 8708 } 8709 // 8710 if (compiler_cache.length >= compiler_cache_max) 8711 { compiler_cache = {length: 0}; 8712 } 8713 compiler_cache[css_for_cache] = path; 8714 compiler_cache.length++; 8715 return path; 8716 } 8717 8718 // class Evaluator 8719 function Evaluator(ctx, h) 8720 { this.ctx = ctx; 8721 this.h = h; 8722 } 8723 Evaluator.prototype.evaluate = function(node)
8724 { return evaluate(this.ctx.s[this.h], node, this.ctx.functions); 8725 }; 8726 Evaluator.prototype.evaluate_one = function(node) 8727 { var path = this.ctx.s[this.h]; 8728 for (var i=path.length-1; i>=0; i--) 8729 { var iter = select_matching(path[i], 0, node, null, this.ctx.functions, this.ctx.window, this.ctx.document, 0, NaN, NaN, NaN, NaN, [], [], [], [], 0, false); 8730 if (iter) 8731 { return iter.result; 8732 } 8733 } 8734 }; 8735 8736 function select_matching(cur_path, step, node, subnode, functions, win, doc, position_range, position, last, position_ot, last_ot, positions, lasts, position_ots, last_ots, n_positions, no_enter) 8737 { var simple_selector = cur_path[step]; 8738 var name = simple_selector.name; 8739 var axis = simple_selector.axis; 8740 var from = simple_selector.from; 8741 var limit = simple_selector.limit; 8742 var cond = simple_selector.cond; 8743 var sub_paths = simple_selector.sub; 8744 var is_last_step = step == cur_path.length-1; 8745 var ctx = 8746 { node: null, 8747 window: win, 8748 document: doc, 8749 FUNCTIONS, 8750 functions, 8751 E: Evaluator, 8752 s: sub_paths, 8753 position: get_position, 8754 last: get_last 8755 }; 8756 var use_element_child = node && node.firstElementChild!==undefined; 8757 if (!subnode) 8758 { subnode = axis==SELF || axis==ANCESTOR_OR_SELF ? node : axis==PARENT || axis==ANCESTOR ? node.parentNode : axis==CHILD ? (use_element_child ? node.firstElementChild : node.firstChild) : axis==FOLLOWING_SIBLING || axis==FIRST_FOLLOWING_SIBLING ? (use_element_child ? node.nextElementSibling : node.nextSibling) : axis==PRECEDING_SIBLING || axis==FIRST_PRECEDING_SIBLING ? (use_element_child ? node.previousElementSibling : node.previousSibling) : axis==DESCENDANT ? node : null; 8759 position = axis==SELF || axis==DESCENDANT || axis==DESCENDANT_OR_SELF || axis==ANCESTOR_OR_SELF ? position : axis==CHILD ? 0 : axis==FOLLOWING_SIBLING || axis==FIRST_FOLLOWING_SIBLING ? position+1 : axis==PRECEDING_SIBLING || axis==FIRST_PRECEDING_SIBLING ? position-1 : NaN; 8760 position_ot = axis==SELF || axis==DESCENDANT || axis==DESCENDANT_OR_SELF ? position_ot : axis==CHILD ? (name?0:NaN) : NaN; 8761 } 8762 var inc_position = axis==PRECEDING_SIBLING ? -1 : +1; 8763 function get_last(is_ot, is_p) 8764 { var value = is_ot ? (is_p ? position_ot : last_ot) : (is_p ? position : last); 8765 if (isNaN(value)) 8766 { if (!is_ot) 8767 { if (isNaN(position)) 8768 { position = 0; 8769 for (var n=(use_element_child ? subnode.previousElementSibling : subnode.previousSibling); n; n=(use_element_child ? n.previousElementSibling : n.previousSibling)) 8770 { if (n.nodeType == XML_ELEMENT_NODE) 8771 { position++; 8772 } 8773 } 8774 if (is_p) 8775 { return position; 8776 } 8777 } 8778 last = position + 1; 8779 for (var n=(use_element_child ? subnode.nextElementSibling : subnode.nextSibling); n; n=(use_element_child ? n.nextElementSibling : n.nextSibling)) 8780 { if (n.nodeType == XML_ELEMENT_NODE) 8781 { last++; 8782 } 8783 } 8784 value = last; 8785 } 8786 else 8787 { var node_name = subnode.nodeName; 8788 if (node_name == name) 8789 { if (isNaN(position_ot)) 8790 { position_ot = 0; 8791 for (var n=(use_element_child ? subnode.previousElementSibling : subnode.previousSibling); n; n=(use_element_child ? n.previousElementSibling : n.previousSibling)) 8792 { if (n.nodeName == node_name) 8793 { position_ot++; 8794 } 8795 } 8796 if (is_p) 8797 { return position_ot; 8798 } 8799 } 8800 last_ot = position_ot + 1; 8801 for (var n=(use_element_child ? subnode.nextElementSibling : subnode.nextSibling); n; n=(use_element_child ? n.nextElementSibling : n.nextSibling)) 8802 { if (n.nodeName == node_name) 8803 { last_ot++; 8804 } 8805 } 8806 value = last_ot; 8807 } 8808 else 8809 { var p = 0; 8810 for (var n=(use_element_child ? subnode.previousElementSibling : subnode.previousSibling); n; n=(use_element_child ? n.previousElementSibling : n.previousSibling)) 8811 { if (n.nodeName == node_name) 8812 { p++; 8813 } 8814 } 8815 if (!is_p) 8816 { p++; 8817 for (var n=(use_element_child ? subnode.nextElementSibling : subnode.nextSibling); n; n=(use_element_child ? n.nextElementSibling : n.nextSibling)) 8818 { if (n.nodeName == node_name) 8819 { p++; 8820 } 8821 } 8822 } 8823 value = p; 8824 } 8825 } 8826 } 8827 return value; 8828 } 8829 function get_position(is_ot) 8830 { return get_last(is_ot, true); 8831 } 8832 while (true) 8833 { var next; 8834 if (axis==SELF || axis==PARENT) 8835 { next = null; 8836 } 8837 else if (axis == FIRST_FOLLOWING_SIBLING) 8838 { next = subnode && subnode.nodeType!=XML_ELEMENT_NODE ? subnode.nextSibling : null; 8839 } 8840 else if (axis == FIRST_PRECEDING_SIBLING) 8841 { next = subnode && subnode.nodeType!=XML_ELEMENT_NODE ? subnode.previousSibling : null; 8842 } 8843 else if (axis==CHILD || axis==FOLLOWING_SIBLING) 8844 { next = subnode && (use_element_child ? subnode.nextElementSibling : subnode.nextSibling); 8845 } 8846 else if (axis == PRECEDING_SIBLING) 8847 { next = subnode && (use_element_child ? subnode.previousElementSibling : subnode.previousSibling); 8848 } 8849 else if (axis==ANCESTOR || axis==ANCESTOR_OR_SELF) 8850 { next = subnode && subnode.parentNode; 8851 position = NaN; 8852 last = NaN; 8853 position_ot = NaN; 8854 last_ot = NaN; 8855 } 8856 else 8857 { var n2; 8858 if (subnode == null) // DESCENDANT_OR_SELF 8859 { subnode = node; 8860 } 8861 else if ((n2 = use_element_child ? subnode.firstElementChild : subnode.firstChild) && !no_enter) 8862 { subnode = n2; 8863 positions[n_positions] = position; 8864 lasts[n_positions] = last; 8865 position_ots[n_positions] = position_ot; 8866 last_ots[n_positions] = last_ot; 8867 n_positions++; 8868 position = 0; 8869 last = NaN; 8870 position_ot = name ? 0 : NaN; 8871 last_ot = NaN; 8872 } 8873 else 8874 { no_enter = false;
8875 while (true) 8876 { if (subnode == node) 8877 { return; 8878 } 8879 if ((n2 = use_element_child ? subnode.nextElementSibling : subnode.nextSibling)) 8880 { subnode = n2; 8881 break; 8882 } 8883 subnode = subnode.parentNode; 8884 n_positions--; 8885 position = positions[n_positions]; 8886 last = lasts[n_positions]; 8887 position_ot = position_ots[n_positions]; 8888 last_ot = last_ots[n_positions]; 8889 } 8890 } 8891 next = subnode; 8892 } 8893 if (!subnode) 8894 { break; 8895 } 8896 var is_found = false; 8897 if (subnode.nodeType == XML_ELEMENT_NODE) 8898 { if (!name || subnode.nodeName==name) 8899 { ctx.node = subnode; 8900 if (!cond || cond.call(ctx)) 8901 { if (!is_last_step) 8902 { var next_axis = cur_path[step + 1].axis; 8903 if (next_axis==DESCENDANT || next_axis==DESCENDANT_OR_SELF) 8904 { no_enter = true; // no_enter is only used when axis==DESCENDANT || axis==DESCENDANT_OR_SELF 8905 } 8906 } 8907 position_range++; 8908 is_found = from <= position_range; 8909 var maybe_found_node = subnode; 8910 } 8911 position_ot += inc_position; 8912 } 8913 position += inc_position; 8914 } 8915 subnode = next;
8916 if (is_found) 8917 { if (!is_last_step) 8918 { var next_iter_found = select_matching(cur_path, step+1, maybe_found_node, null, functions, win, doc, 0, position, last, position_ot, last_ot, positions, lasts, position_ots, last_ots, n_positions, false); 8919 } 8920 if (is_last_step || next_iter_found) 8921 { var next_iter = !subnode || from-1+limit <= position_range ? null : function() 8922 { return select_matching(cur_path, step, node, subnode, functions, win, doc, position_range, position, last, position_ot, last_ot, positions, lasts, position_ots, last_ots, n_positions, no_enter); 8923 }; 8924 var iter = 8925 { result: is_last_step ? maybe_found_node : next_iter_found.result, 8926 next: is_last_step ? next_iter : function() 8927 { if (next_iter_found = next_iter_found.next && next_iter_found.next()) 8928 { iter.result = next_iter_found.result; 8929 return iter; 8930 } 8931 return next_iter && next_iter(); 8932 } 8933 }; 8934 return iter; 8935 } 8936 } 8937 } 8938 } 8939 8940 /** The fast path: hand the selector to the browser, and let a throw (joyquery-only syntax: 8941 axes, `:from()`/`:limit()`, custom functions) fall back to the emulator. Runs in the debug 8942 flavor too - the emulator is the fallback for what the browser cannot parse, not a debug 8943 mode; see the comment in joyquery_ext.php for the history of that distinction. 8944 **/ 8945 function try_builtin(node, query, one_elem) 8946 { try 8947 { var result = one_elem ? node.querySelector(query) : node.querySelectorAll(query); 8948 if (one_elem) 8949 { result = result ? [result] : []; 8950 } 8951 //console.log('Using builtin: '+query); 8952 return result; 8953 } 8954 catch (e) 8955 { 8956 } 8957 } 8958 8959 function evaluate(path_obj_or_str, node, functions, override_settings) 8960 { if (!functions) 8961 { functions = {}; 8962 } 8963 var path; 8964 var no_context_node = !node; 8965 if (no_context_node) 8966 { node = document.documentElement || document.body; 8967 } 8968 var doc = node.nodeType==XML_DOCUMENT_NODE || node.nodeType==XML_HTML_DOCUMENT_NODE ? node : node.ownerDocument || document; 8969 var win = doc.defaultView || doc.parentWindow || window; 8970 var iter, builtin_result=null; 8971 var i = -1; 8972 var it = function(one_elem_enuogh) 8973 { if (i == -1) 8974 { var is_string = typeof(path_obj_or_str) != 'object'; 8975 if (is_string && !(override_settings && override_settings.emulate!=null ? override_settings.emulate : SETTINGS.emulate)) 8976 { builtin_result = try_builtin(node, path_obj_or_str, one_elem_enuogh); 8977 } 8978 if (!builtin_result) 8979 { var is_xml = !doc.body || (doc.contentType || '').indexOf('/xml')!=-1; 8980 path = is_string ? compile(path_obj_or_str, override_settings && override_settings.compiler_cache_max!=null ? override_settings.compiler_cache_max : SETTINGS.compiler_cache_max, no_context_node, node.firstElementChild!==undefined, node.hasAttribute, is_xml) : path_obj_or_str; 8981 } 8982 path_obj_or_str = null; 8983 } 8984 if (builtin_result) 8985 { // using built-in 8986 return builtin_result[++i] || (node = functions = builtin_result = null); 8987 } 8988 else 8989 { // emulating 8990 while (!iter) 8991 { i++; 8992 if (!path[i]) 8993 { return (path = node = functions = null); 8994 } 8995 iter = select_matching(path[i], 0, node, null, functions, win, doc, 0, NaN, NaN, NaN, NaN, [], [], [], [], 0, false); 8996 } 8997 var elem = iter.result; 8998 iter = iter.next && iter.next(); 8999 return elem; 9000 } 9001 }; 9002 it.get = function(at, count) 9003 { var want_scalar = at!=null && count==null; 9004 count = at==null ? 0x7FFFFFFF : count>0 ? count-0 : 1; 9005 at = at>0 ? at-0 : 0; 9006 var one_elem_enuogh = at==0 && want_scalar; 9007 var get_result = []; 9008 var j = -at; 9009 var elem; 9010 while (j<count && (elem=it(one_elem_enuogh))) 9011 { if (builtin_result || get_result.indexOf(elem)==-1) 9012 { get_result[get_result.length] = elem; 9013 j++; 9014 } 9015 } 9016 if (at > 0) 9017 { get_result = get_result.slice(at); 9018 } 9019 return want_scalar ? get_result[0] : get_result; 9020 }; 9021 return it; 9022 } 9023 9024 evaluate.FUNCTIONS = FUNCTIONS; 9025 evaluate.SETTINGS = SETTINGS; 9026 9027 return evaluate; 9028 } 9029 9030// joyquery_ext 9031function _new_elem_tmp_4(ns, joyquery) 9032 { joyquery.FUNCTIONS.text_child_contains = function(str) 9033 { for (var n=this.node.firstChild; n; n=n.nextSibling) 9034 { if (n.nodeType==3 && n.data.indexOf(str) != -1) return true; 9035 } 9036 }; 9037 /* The debug flavor used to set `joyquery.SETTINGS.emulate = true` here, to suppress the 9038 (caught) exception that try_builtin() takes on joyquery-only selectors when DevTools is set 9039 to pause on caught exceptions. That made every selector run through the JS emulator - and 9040 since the dev host-nginx serves the -debug bundle for counter.personyze.com to make the 9041 tracker readable, every developer machine paid the emulator's price on every tracked page 9042 (measured: an idle panel tab at ~10% of a core, all of it selector re-walks; the release 9043 flavor answers the same selectors natively 25-40x faster). Debugging is also better served 9044 by the builtin path: it is what real visitors run, and the emulator differs from it in 9045 edge cases (uppercased names never match SVG, `:has()` is unsupported). Joyquery-only 9046 selectors are rare enough (6 containers fleet-wide) that the paused-exception noise is not 9047 worth that. Set `joyquery.SETTINGS.emulate = true` from the console if it bothers you. */ 9048 } 9049 9050// TODO: templates dependancies. load files on switch-elem 9051// TODO: emulated CSS transitions - support Bezier timing funcs 9052// TODO: remove by_id_in 9053// TODO: ontplloading and ontplunloading: compatibility 9054// TODO: pair_funcs: compatibility 9055// TODO: remove report_tplload(n) 9056 9057// External compile-time settings: 9058// const NEW_ELEM_NDEBUG = ...; 9059// const NEW_ELEM_NS_NAME = ...; 9060 9061// Consts 9062const XML_ELEMENT_NODE = 1; 9063const XML_TEXT_NODE = 3; 9064const XML_CDATA_SECTION_NODE = 4; 9065const XML_PI_NODE = 7; 9066const XML_COMMENT_NODE = 8; 9067const XML_DOCUMENT_NODE = 9; 9068const XML_DOCUMENT_FRAG_NODE = 11; 9069const IS_MSIE = 'ActiveXObject' in window; 9070const IS_SAFARI = navigator.vendor?.indexOf('Apple') >= 0; 9071const IS_CHROME = window.chrome != null; 9072const IS_OPERA = window.opera != null; 9073const IS_FIREFOX = window.InstallTrigger != null; 9074const CSS_PREFIX = IS_MSIE ? 'Ms' : IS_SAFARI || IS_CHROME ? 'Webkit' : IS_OPERA ? 'O' : IS_FIREFOX ? 'Moz' : null; 9075const CSS_PREFIX_LC = CSS_PREFIX?.toLowerCase();
9076const CSS_DEFAULT_REPLACE = {Start: 'Left', End: 'Right'}; 9077const RE_COMMA = /,/g; 9078const RE_SPACES = /\s+/g; 9079const RE_HAS_NONSPACE = /\S/; 9080const RE_NUMBER = /[+\-]?\b(?:\d+(?:\.\d+)?|\.\d+)/g; 9081const RE_THIS = /\bthis\b/; 9082const RE_NS = /\bns\b/; 9083const RE_FIRST_TAG = /^\s*<(\S+)/; 9084const RE_CSS_RULE = /([^;:\s]+)\s*:("[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'|[^;]*)/g; 9085const RE_FROM_CAMEL_1 = /(\b[A-Z])/g; 9086const RE_FROM_CAMEL_2 = /([a-z])([A-Z])/g; 9087const RE_TO_CAMEL = /-([a-z])/gi; 9088const SPECIAL_EVENTS = 9089{ ontplready: 1, 9090 ontplunloading: 1, 9091 ontplloading: 1, 9092 ontplload: 1, 9093 ontplunload: 1, 9094 ontplreload: 1 9095}; 9096const ELEMS_HAVE_OPTIONS = 9097{ SELECT: 1, 9098 OPTGROUP: 1, 9099 DATALIST: 1, 9100 MENU: 1, 9101 OL: 1, 9102 UL: 1, 9103 TABLE: 1, 9104 THEAD: 1, 9105 TBODY: 1, 9106 TFOOT: 1 9107}; 9108const EVENTS_CTORS = 9109{ click: 'MouseEvent', 9110 dblclick: 'MouseEvent', 9111 mouseup: 'MouseEvent', 9112 mousedown: 'MouseEvent', 9113 mouseover: 'MouseEvent', 9114 mouseout: 'MouseEvent', 9115 mouseenter: 'MouseEvent', 9116 mouseleave: 'MouseEvent', 9117 keydown: 'KeyboardEvent', 9118 keyup: 'KeyboardEvent', 9119 keypress: 'KeyboardEvent', 9120 focus: 'FocusEvent', 9121 blur: 'FocusEvent', 9122 focusin: 'FocusEvent', 9123 focusout: 'FocusEvent' 9124}; 9125 9126const default_display_by_tags_cache = {}; 9127var scrollbar_width; 9128 9129if (!String.prototype.replaceAll) 9130{ try 9131 { String.prototype.replaceAll = function(from, to) 9132 { if (!(from instanceof RegExp)) 9133 { from = new RegExp((from+'').replace(/[\\^$|.*+?()[\]{}]/g, '\\$&'), 'g'); 9134 } 9135 return this.replace(from, to); 9136 }; 9137 } 9138 catch (e) 9139 { error(e); 9140 } 9141} 9142 9143function do_nothing() {} 9144 9145function is_undefined(value) 9146{ return value === undefined; 9147} 9148 9149function assert(value) 9150{ if (!NEW_ELEM_NDEBUG) 9151 { if (!value) 9152 { error('Assertion failed', new Error().stack); 9153 } 9154 } 9155} 9156 9157/** Get property of props, or get one of "data-name" or "name" attributes of elem. 9158 The first attribute found in this sequence will be returned. 9159 If "take" is true, will remove found attribute (but not prop). 9160 If requested property is in props, they get priority, but attribute will be still removed. 9161 Attribute that starts with "data-json-" will be JSON-decoded. 9162 **/ 9163function get_data_attr(ns, elem, arg_name, default_value, take, props, parent_inher, for_dyn_options, options_props, options_no_keep_elems, options_by_id) 9164{ var value = props?.[arg_name]; 9165 if (value !== undefined) 9166 { if (arg_name=='options' && value && for_dyn_options?.options!==value) // for_dyn_options.options can contain already converted options from original tpl instance before _reapply_template() 9167 { value = conv_options(ns, value); 9168 } 9169 if (!take) return value; 9170 } 9171 if (elem?.nodeType == XML_ELEMENT_NODE) 9172 { let a_name, a_value=null, is_json=false; 9173 ( (a_value = elem.getAttribute((a_name = 'data-json-'+arg_name))) != null && (is_json = true) || 9174 (a_value = elem.getAttribute((a_name = 'data-'+arg_name))) != null || 9175 (a_value = elem.getAttribute((a_name = arg_name))) != null 9176 ); 9177 if (value === undefined) 9178 { if (a_value != null) 9179 { if (arg_name.slice(0, 2)=='on' && typeof(elem[arg_name])=='function') 9180 { value = elem[arg_name]; 9181 } 9182 else if (!is_json) 9183 { value = a_value; 9184 } 9185 else if (a_value !== '') 9186 { try 9187 { value = JSON.parse(a_value); 9188 } 9189 catch (e) 9190 { // Values built through :json:html substitution can arrive double-encoded 9191 // (" survives the browser's own attribute decode). Try one extra 9192 // entity-decode pass before giving up. 9193 try 9194 { value = JSON.parse(a_value.replace(/&(?:quot|#0*34|#x0*22);/gi, '"').replace(/&(?:apos|#0*39|#x0*27);/gi, "'").replace(/</gi, '<').replace(/>/gi, '>').replace(/&/gi, '&')); 9195 } 9196 catch (e2) 9197 { // Not NDEBUG-gated: a malformed data-json-* attribute is an authoring 9198 // error that otherwise fails silently (the component renders empty). 9199 window.console && console.error('Personyze: invalid JSON in "'+a_name+'" attribute', elem, e); 9200 } 9201 } 9202 } 9203 } 9204 } 9205 if (take && a_value!=null) 9206 { elem.removeAttribute(a_name); 9207 } 9208 } 9209 if (value === undefined) 9210 { value = parent_inher?.get_arg(arg_name); 9211 if (value===undefined && elem)
9212 { if (arg_name == 'options') 9213 { value = get_options(ns, elem, for_dyn_options, options_props, options_no_keep_elems, options_by_id); 9214 } 9215 else 9216 { value = elem[arg_name]; 9217 } 9218 } 9219 } 9220 return value!==undefined ? value : default_value; 9221} 9222 9223function get_options(ns, elem, for_dyn_options, options_props, options_no_keep_elems, options_by_id) 9224{ if (elem?.nodeName in ELEMS_HAVE_OPTIONS) 9225 { let options = []; 9226 if (for_dyn_options) 9227 { for_dyn_options.options = options; 9228 var dyn_options = (for_dyn_options.dyn_options = {length: 0}); 9229 var dyn_options_info = (for_dyn_options.dyn_options_info = []); 9230 } 9231 get_elem_options(ns, options, elem, dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id); 9232 return options; 9233 } 9234} 9235 9236class RepeatOption 9237{ constructor(elem, options, dyn_options, i) 9238 { this.elem = elem; 9239 this.options = options; 9240 this.dyn_options = dyn_options; 9241 this.i = i|0; 9242 } 9243 9244 get_option_index() 9245 { var {dyn_options} = this; 9246 var option_i = 0; 9247 for (let i=0, i_end=this.i; i<i_end; i++) 9248 { if (dyn_options[i]?.index_of_child) 9249 { option_i += dyn_options[i].length - 1; 9250 } 9251 option_i++; 9252 } 9253 return option_i; 9254 } 9255} 9256 9257/** If callback returns undefined, will continue. 9258 If callback returns false, will continue, but not traverse inside current element. 9259 If callback returns a DOMNode (but not the current element itself), will replace this element with the returned node, and continue. 9260 If callback returns elem itself, or a value other than undefined, false and DOMNode, will stop traversing, and return it. 9261 **/ 9262function traverse(elem, e_parent_node, callback) 9263{ var e_next_sibling = elem.nextSibling; 9264 var repl_elem = callback(elem); 9265 if (repl_elem !== undefined) 9266 { if (!repl_elem?.cloneNode || repl_elem==elem) 9267 { if (repl_elem === false) 9268 { return; // don't traverse inside 9269 } 9270 return repl_elem; 9271 } 9272 if (e_parent_node) 9273 { e_parent_node.insertBefore(repl_elem, e_next_sibling); 9274 if (elem.parentNode == e_parent_node) 9275 { e_parent_node.removeChild(elem); 9276 } 9277 } 9278 return; 9279 } 9280 for (let subelem=elem.firstElementChild; subelem;) // firstElementChild support is present since MSIE9 9281 { let next = subelem.nextElementSibling; 9282 let result = traverse(subelem, elem, callback); 9283 if (result !== undefined) 9284 { return result; 9285 } 9286 subelem = next; 9287 } 9288} 9289 9290function get_elem_options(ns, options, elem, dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id) 9291{ var node_name = elem.nodeName; 9292 if (node_name == 'TABLE') 9293 { for (let e=elem.firstElementChild; e; e=e.nextElementSibling) 9294 { let n_name = e.nodeName; 9295 if (n_name=='TBODY' || n_name=='THEAD' || n_name=='TFOOT') 9296 { get_elem_options(ns, options, e, dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id); 9297 } 9298 } 9299 } 9300 else 9301 { let i = 0; 9302 for (let e=elem.firstElementChild; e; e=e.nextElementSibling) 9303 { let for_dyn_options = dyn_options ? {} : null; 9304 let option = get_elem_option(ns, e, for_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id); 9305 if (dyn_options && e.classList.contains('$repeat-elem')) 9306 { dyn_options_info[dyn_options_info.length] = new RepeatOption(e, options, dyn_options, i); 9307 dyn_options[i] = option; 9308 dyn_options.length = i + 1; 9309 } 9310 else 9311 { options[options.length] = option; 9312 } 9313 if (dyn_options && for_dyn_options.options) 9314 { if (!dyn_options[i]) 9315 { dyn_options[i] = {}; 9316 } 9317 add_props_shallow(dyn_options[i], for_dyn_options); 9318 dyn_options.length = i + 1; 9319 } 9320 i++; 9321 } 9322 } 9323} 9324function get_elem_option(ns, elem, for_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id) 9325{ var option = get_attributes(ns, {}, elem, false, false, true); 9326 if (options_props) 9327 { if (option.id) 9328 { // id attribute was not removed yet 9329 var option_props = options_props[option.id]; 9330 } 9331 else if (options_by_id) 9332 { // look in options_by_id 9333 for (let p of Object.keys(options_props)) 9334 { if (options_by_id[p] == elem) 9335 { option_props = options_props[p]; 9336 break; 9337 } 9338 } 9339 } 9340 } 9341 if (option_props) 9342 { add_props_shallow(option, option_props); 9343 } 9344 var node_name = elem.nodeName; 9345 if (node_name == 'OPTION') 9346 { option.text = elem.text; 9347 option.value = elem.value; 9348 option.selected = elem.selected; 9349 } 9350 else if (node_name == 'OPTGROUP') 9351 { option.text = option.label; 9352 delete option.label; 9353 option.options = []; 9354 let sub_dyn_options = {}; 9355 get_elem_options(ns, option.options, elem, sub_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id); 9356 if ('length' in sub_dyn_options) 9357 { for_dyn_options.options = sub_dyn_options; 9358 } 9359 } 9360 else 9361 { let is_tr = node_name == 'TR'; 9362 let text_content = is_tr ? null : '';
9363 let child_nodes = []; 9364 let n_options_variants = 0; 9365 for (let e2=elem.firstChild, next; e2; e2=next) 9366 { next = e2.nextSibling; 9367 let node_type = e2.nodeType; 9368 if (node_type == XML_ELEMENT_NODE) 9369 { traverse 9370 ( e2, 9371 elem, 9372 subelem => 9373 { if (get_data_attr(ns, subelem, 'OPTIONS')) // capital letters, so will not be recognized as magic 'options' 9374 { let sub_options = []; 9375 let sub_dyn_options = {}; 9376 get_elem_options(ns, sub_options, subelem, sub_dyn_options, dyn_options_info, options_props, options_no_keep_elems, options_by_id); 9377 option[n_options_variants==0 ? 'options' : 'options_'+n_options_variants] = sub_options; 9378 if (!options_no_keep_elems) 9379 { option[n_options_variants==0 ? 'options_elem' : 'options_elem_'+n_options_variants] = subelem; 9380 } 9381 else 9382 { subelem.parentNode.removeChild(subelem); 9383 } 9384 if ('length' in sub_dyn_options) 9385 { for_dyn_options[n_options_variants==0 ? 'options' : 'options_'+n_options_variants] = sub_dyn_options; 9386 } 9387 n_options_variants++; 9388 return false; // no traverse inside 9389 } 9390 else 9391 { let class_list = subelem.classList; 9392 for (let i=0, i_end=class_list.length; i<i_end; i++) 9393 { let cl = class_list[i]; 9394 if (cl.charAt(0) == '$') 9395 { cl = cl.slice(1); 9396 if (typeof(ns.templates[cl]) == 'function') 9397 { let tpl_details = ns.templates[cl](); 9398 if ('options' in tpl_details.arg_names_assoc) 9399 { return false; // no traverse inside 9400 } 9401 } 9402 } 9403 } 9404 } 9405 } 9406 ); 9407 if (e2.parentNode) // if options_no_keep_elems i can unlink nodes 9408 { text_content = null; 9409 child_nodes[child_nodes.length] = e2; 9410 } 9411 } 9412 else if ((node_type==XML_TEXT_NODE || node_type==XML_CDATA_SECTION_NODE) && !is_tr) 9413 { if (text_content != null) 9414 { text_content += e2.data; 9415 } 9416 if (child_nodes.length>0 || e2.data?.trim()) 9417 { let prev_to_append = options_no_keep_elems && child_nodes[child_nodes.length - 1]; 9418 if (prev_to_append?.nodeType == node_type) 9419 { prev_to_append.data += e2.data; 9420 } 9421 else 9422 { child_nodes[child_nodes.length] = e2; 9423 } 9424 } 9425 } 9426 } 9427 if (text_content != null) 9428 { option.text = text_content.trim() || option.text || ''; 9429 } 9430 else 9431 { if (options_no_keep_elems) 9432 { let e2 = child_nodes[child_nodes.length - 1]; 9433 let node_type = e2.nodeType; 9434 if (node_type==XML_TEXT_NODE || node_type==XML_CDATA_SECTION_NODE) 9435 { e2.data = str_trim_right(e2.data); 9436 if (e2.length == 0) 9437 { child_nodes.length--; 9438 } 9439 } 9440 } 9441 option.childNodes = child_nodes; 9442 } 9443 } 9444 return option; 9445} 9446function conv_options(ns, options) 9447{ var conved = get_options(ns, options); 9448 if (!conved) 9449 { let parent_node = options[0]?.parentNode; 9450 if (parent_node) 9451 { conved = get_options(ns, parent_node); 9452 } 9453 } 9454 if (!conved) 9455 { for (let i=0, i_end=options.length; i<i_end; i++) 9456 { let option = options[i]; 9457 let no_conv = false; 9458 let is_obj=true, text, value, child_nodes=null, suboptions=null; 9459 if (typeof(option) != 'object') 9460 { is_obj = false; 9461 text = option+''; 9462 value = text; 9463 } 9464 else 9465 { text = option.text!=null ? option.text : option[0]; 9466 value = option.value!=null ? option.value : option[1]; 9467 child_nodes = option.childNodes; 9468 no_conv = (text==null || text==option.text) && (value==null || value==option.value); 9469 if (text == null) 9470 { if (!child_nodes && value!=null) 9471 { text = value+''; 9472 no_conv = false; 9473 } 9474 } 9475 else if (child_nodes != null) 9476 { child_nodes = null; 9477 no_conv = false; 9478 } 9479 if (option.options) 9480 { suboptions = conv_options(ns, option.options) 9481 if (option.options !== suboptions) 9482 { no_conv = false; 9483 } 9484 } 9485 } 9486 if (no_conv) 9487 { if (conved) 9488 { conved[i] = option; 9489 } 9490 } 9491 else 9492 { let c = {}; 9493 if (text != null) c.text = text; 9494 if (value != null) c.value = value; 9495 if (is_obj) 9496 { if (child_nodes != null) c.childNodes = child_nodes; 9497 if (suboptions != null) c.options = suboptions; 9498 for (let j in option) 9499 { if (!(j in c)) c[j] = option[j]; 9500 } 9501 } 9502 if (conved == null) 9503 { conved = []; 9504 for (let j=0; j<i; j++) 9505 { conved[j] = options[j]; 9506 } 9507 } 9508 conved[i] = c; 9509 } 9510 } 9511 } 9512 return conved || options; 9513} 9514function insert_after_or_beginning(e_parent_node, e_previous_sibling, repl_elem) 9515{ return e_parent_node.insertBefore(repl_elem, e_previous_sibling ? e_previous_sibling.nextSibling : e_parent_node.firstChild); 9516} 9517function has_playing_parent(elem) 9518{ while (elem) 9519 { let ptr = elem._ne__playing; 9520 if (ptr) return ptr; 9521 elem = elem.parentNode; 9522 } 9523} 9524function replace_elem(ns, win_doc, onready_queue, elem, e_parent_node, e_previous_sibling, repl_elem, oninited, transit, ontransitionend) 9525{ function when_complete() 9526 { if (!done) // when_complete is called through ontransitionend, so can be called twice if user makes several transitions 9527 { done = true; 9528 if (e_parent_node) 9529 { if (e_parent_node._ne__playing == ptr) 9530 { delete e_parent_node._ne__playing; 9531 } 9532 else 9533 { var playing_ptr = has_playing_parent(e_parent_node); 9534 } 9535 if (elem!=repl_elem && elem?.parentNode==e_parent_node && (!playing_ptr || playing_ptr.elem!=elem)) 9536 { e_parent_node.removeChild(elem); 9537 } 9538 a_elem?.parentNode?.removeChild(a_elem); 9539 if (repl_elem) 9540 { repl_elem = insert_after_or_beginning(e_parent_node, e_previous_sibling, repl_elem); 9541 } 9542 } 9543 if (ontransitionend) ontransitionend(); 9544 } 9545 } 9546 var a_elem = null; 9547 var done = false;
9548 var ptr = {elem, end: when_complete, transit}; 9549 var want_template = e_parent_node && transit?.template; 9550 if (want_template && typeof(ns.templates[want_template])!='function') 9551 { log('No such template in current namespace (requested as animator) - '+want_template); 9552 want_template = null; 9553 } 9554 var playing_ptr; 9555 if (!want_template || !repl_elem && !elem || (playing_ptr = has_playing_parent(e_parent_node))) 9556 { if (playing_ptr) 9557 { if (playing_ptr.transit.stop) 9558 { playing_ptr.transit.stop(); 9559 } 9560 playing_ptr.end(); 9561 } 9562 if (e_parent_node && repl_elem && elem && repl_elem!=elem) 9563 { repl_elem = insert_after_or_beginning(e_parent_node, e_previous_sibling, repl_elem); 9564 } 9565 pcall(oninited, repl_elem); 9566 when_complete(); 9567 } 9568 else 9569 { e_parent_node._ne__playing = ptr; 9570 ontransitionend = pair_funcs(transit.ontransitionend, ontransitionend); 9571 transit.ontransitionend = when_complete; 9572 a_elem = win_doc.doc.createElement('div'); 9573 a_elem.id = 'elem'; 9574 a_elem.className = '$'+transit.template; 9575 a_elem = insert_after_or_beginning(e_parent_node, e_previous_sibling, a_elem); 9576 a_elem = new_elem_3 9577 ( ns, 9578 a_elem, 9579 null, 9580 { elem: 9581 { current: elem, 9582 next: repl_elem, 9583 transit 9584 } 9585 }, 9586 {}, 9587 win_doc, 9588 onready_queue 9589 ); 9590 pcall(oninited, repl_elem); 9591 } 9592 return repl_elem; 9593} 9594function elem_get_ontpl_attrs(elem, ns, by_id, remove_ids, ontpl_attrs) 9595{ var ontpl_attrs_special = null; 9596 for (let i=elem.attributes.length-1; i>=0; i--) 9597 { let attr = elem.attributes.item(i); 9598 let name = attr.name; 9599 if 9600 ( name.slice(0, 2)=='on' || 9601 name.slice(0, 10)=='data-ontpl' && (name=name.slice(5)) || 9602 name.slice(0, 15)=='data-json-ontpl' && (name=name.slice(10)) 9603 ) 9604 { if (name.slice(0, 5) == 'ontpl') 9605 { let value = get_data_attr(ns, elem, name, null, remove_ids); 9606 if (value) 9607 { let func = str_to_func(value, ns, by_id); 9608 if (name in SPECIAL_EVENTS) 9609 { if (!ontpl_attrs_special) ontpl_attrs_special = {}; 9610 ontpl_attrs_special[name] = func; 9611 } 9612 else 9613 { ontpl_attrs[name] = func; 9614 } 9615 } 9616 } 9617 else if (name == attr.name) 9618 { try 9619 { if (typeof(elem[name]) == 'function') // in Chrome 60.0.3112.78: typeof(elem[name]) throws error if event elem[name] is incorrect code 9620 { let code = attr.value+''; 9621 if (code.indexOf('by_id')!=-1 || code.indexOf('ns')!=-1 && RE_NS.test(code)) 9622 { elem[name] = str_to_func(code, ns, by_id); 9623 } 9624 } 9625 } 9626 catch (e) 9627 { error(e); 9628 } 9629 } 9630 } 9631 } 9632 return ontpl_attrs_special; 9633} 9634function hook_ontplloading(parent_ontplloading, get_elem, props, for_parent_props_has_ontplloading, inher) 9635{ var ontplloading, ontplunloading, ontplload, ontplunload, ontplreload; 9636 function set_props(props) 9637 { ontplloading = props.ontplloading; 9638 ontplunloading = props.ontplunloading; 9639 ontplload = props.ontplload; 9640 ontplunload = props.ontplunload; 9641 ontplreload = props.ontplreload; 9642 } 9643 if (props) 9644 { set_props(props); 9645 } 9646 if (!ontplload && !ontplunload && !ontplreload && !ontplloading && !ontplunloading && !inher) 9647 { return parent_ontplloading; 9648 } 9649 var n_tpls_to_load = -1; 9650 var n_tpls_to_load_parent = 0; 9651 var parent_props_has_ontplloading = for_parent_props_has_ontplloading.value; 9652 if (ontplloading || ontplunloading) 9653 { for_parent_props_has_ontplloading.value = true; 9654 } 9655 return function(is_unload, event_obj, update_props, onready_queue) 9656 { if (update_props) 9657 { // Call with (null, null, props) to update props. Then must call with (false, null) to report initial load. 9658 // Call with (null, event_obj, props, onready_queue) to update props and report unload. Then must call with (false, event_obj) to report initial load. 9659 set_props(update_props); 9660 n_tpls_to_load--; 9661 if (event_obj) 9662 { n_tpls_to_load_parent--; 9663 if (parent_ontplloading && (parent_props_has_ontplloading || n_tpls_to_load_parent==-1)) 9664 { onready_queue.add_onready(parent_ontplloading, this, [true, event_obj]); // load 9665 } 9666 } 9667 } 9668 else 9669 { if (event_obj) 9670 { if (is_unload) 9671 { if (ontplunloading) 9672 { pcall(ontplunloading, get_elem(), [event_obj]); 9673 } 9674 } 9675 else 9676 { if (ontplloading) 9677 { pcall(ontplloading, get_elem(), [event_obj]); 9678 } 9679 } 9680 } 9681 if (is_unload) 9682 { if (n_tpls_to_load-- == 0) 9683 { if (ontplunload) 9684 { pcall(ontplunload, get_elem()); 9685 } 9686 } 9687 } 9688 else 9689 { if (++n_tpls_to_load == 0) 9690 { var func = ontplload; 9691 ontplload = ontplreload; // override before calling because call may cause unload+load 9692 pcall(func, get_elem()); 9693 } 9694 } 9695 if (event_obj) // called with event_obj==null to report that main tpl is loaded (initial n_tpls_to_load is -1) 9696 { if (inher) 9697 { inher.subst_event_target(event_obj, []); 9698 } 9699 n_tpls_to_load_parent += is_unload ? -1 : +1; 9700 if (parent_ontplloading && (parent_props_has_ontplloading || is_unload && n_tpls_to_load_parent==-1 || !is_unload && n_tpls_to_load_parent==0)) 9701 { parent_ontplloading(is_unload, event_obj); 9702 } 9703 } 9704 } 9705 }; 9706} 9707function hook_ontplevent(ns, parent_ontplevent, get_elem, props, ontpl_attrs, inher, debug_tpl_name, debug_elem_id) 9708{ function set_props(props) 9709 { if (props) 9710 { for (let p in props) 9711 { if (p.slice(0, 5)=='ontpl' && !(p in SPECIAL_EVENTS)) 9712 { let func = props[p]; 9713 if (!NEW_ELEM_NDEBUG && ns.EventsDebugger && func) 9714 { let orig_func = func; 9715 func = function() 9716 { var result = ns.EventsDebugger.log_before(debug_tpl_name || '', debug_elem_id || '', p, get_elem()); 9717 try 9718 { return orig_func.apply(this, arguments); 9719 } 9720 finally 9721 { ns.EventsDebugger.log_after(result); 9722 } 9723 }; 9724 } 9725 var func_name = p.slice(5); 9726 event_funcs[func_name] = pair_funcs 9727 ( event_funcs[func_name], 9728 func, 9729 is_undefined 9730 ); 9731 has_hooks = true; 9732 } 9733 } 9734 } 9735 } 9736 var event_funcs = {} 9737 var has_hooks = false;
9738 set_props(ontpl_attrs); 9739 set_props(props); 9740 return !has_hooks && !inher ? parent_ontplevent : function(args, update_props_and_inher) 9741 { if (update_props_and_inher) // call with (null, props) to update props 9742 { event_funcs = {}; 9743 set_props(update_props_and_inher.props); 9744 inher = update_props_and_inher.inher; 9745 } 9746 else 9747 { let event_obj = args[0]; 9748 let {type} = event_obj; 9749 if (type in event_funcs) 9750 { let stop_propagation_value = pcall(event_funcs[type], get_elem(), args); 9751 if (stop_propagation_value !== undefined) 9752 { return stop_propagation_value; 9753 } 9754 } 9755 if (inher) 9756 { let stop_propagation_value = inher.subst_event_target(event_obj, args); 9757 if (stop_propagation_value !== undefined) 9758 { return stop_propagation_value; 9759 } 9760 } 9761 if (parent_ontplevent) 9762 { return parent_ontplevent(args); 9763 } 9764 else if (type == 'error') 9765 { error(args[1]); 9766 } 9767 } 9768 }; 9769} 9770function ontplevent_filter(parent_ontplevent, get_elem, props, reports, inher) 9771{ if (!parent_ontplevent || !reports) 9772 { return parent_ontplevent; 9773 } 9774 var event_funcs = {}; 9775 if (props) 9776 { for (let p in props) 9777 { if (p.slice(0, 5)=='ontpl' && !(p in SPECIAL_EVENTS)) 9778 { var func_name = p.slice(5); 9779 event_funcs[func_name] = props[p]; 9780 } 9781 } 9782 } 9783 return function(args) 9784 { let event_obj = args[0]; 9785 if (reports.indexOf(event_obj.type) != -1) 9786 { if (!inher || inher.to_wrap_elem!=event_obj.target) 9787 { let {type} = event_obj; 9788 if (type in event_funcs) 9789 { return pcall(event_funcs[type], get_elem(), args); 9790 } 9791 return; 9792 } 9793 } 9794 return parent_ontplevent(args); 9795 }; 9796} 9797 9798class OnReadyQ 9799{ constructor() 9800 { this.onready = []; 9801 this.onload = []; 9802 this.level = 1; 9803 this.for_next = null; 9804 } 9805 9806 add_onready(func, owner, args) 9807 { if (this.level == 0) 9808 { func.apply(owner, args || []); 9809 } 9810 else 9811 { let {onready} = this; 9812 onready[onready.length] = {func, owner, args}; 9813 } 9814 } 9815 9816 add_onload(func) 9817 { if (this.level == 0) 9818 { func(); 9819 } 9820 else 9821 { let {onload} = this; 9822 onload[onload.length] = func; 9823 } 9824 } 9825 9826 enter() 9827 { this.level++; 9828 } 9829 9830 leave() 9831 { if (--this.level == 0) 9832 { let {onready, onload} = this; 9833 this.onready = []; 9834 this.onload = []; 9835 for (let i=0, i_end=onready.length; i<i_end; i++) 9836 { let obj = onready[i]; 9837 obj.func.apply(obj.owner, obj.args || []); 9838 } 9839 for (let i=0, i_end=onload.length; i<i_end; i++) 9840 { onload[i](); 9841 } 9842 } 9843 } 9844} 9845 9846function new_elem_3(ns, node_name, parent_node, props, by_id, win_doc, onready_queue, is_tpl_name, by_id_parent_use, parent_ontplloading, props_has_ontplloading, parent_ontplevent, inher, for_root_id) 9847{ var empty = {}; 9848 var ret_elem; 9849 var ontplloading = parent_ontplloading; 9850 var ontplevent = parent_ontplevent; 9851 if (!is_tpl_name) 9852 { function get_elem() 9853 { return ret_elem; 9854 } 9855 var for_props_has_ontplloading = {value: props_has_ontplloading}; 9856 ontplloading = hook_ontplloading(parent_ontplloading, get_elem, props, for_props_has_ontplloading); 9857 ontplevent = hook_ontplevent(ns, parent_ontplevent, get_elem, props); 9858 props_has_ontplloading = for_props_has_ontplloading.value; 9859 } 9860 function new_elem_traverse(elem, props, by_id, by_id_in, remove_ids, onnewids, ontplloading, props_has_ontplloading, ontplevent, for_update_elem, parent_for_dyn_options) 9861 { var e_parent_node = elem.parentNode; 9862 var e_previous_sibling = elem.previousSibling; 9863 var id = elem.id; 9864 if (id && typeof(id)!='string') // remember: a form (HTMLFormElement) can contain input named "id", so typeof(id) will be 'object' 9865 { id = elem.getAttribute('id'); 9866 } 9867 var ap = id && props && !(id in empty) ? props[id] : null; // props for this element 9868 var repl_elem = null; 9869 if (ap?.cloneNode) 9870 { repl_elem = ap; 9871 ap = null; 9872 } 9873 // 9874 var ontpl_attrs = {}; 9875 var ontpl_attrs_special = elem_get_ontpl_attrs(elem, ns, by_id, remove_ids, ontpl_attrs); 9876 // is template? 9877 var tpl_name_main = null; 9878 var tpl_names_secondary = null; 9879 var tpl_details = null; 9880 var tpl_elem = null; 9881 var new_tpl_step_2 = null; 9882 var for_dyn_options = null; 9883 var tpl_ontplloading = null; 9884 var tpl_ontplloading_2 = null; 9885 var tpl_ontplloading_3 = null; 9886 var tpl_ontplevent = null; 9887 var tpl_ontpleventchildren = null; 9888 var tpl_ontpleventinner = null; 9889 var tpl_by_id_in = by_id_in; 9890 var tpl_onnewids = onnewids; 9891 var is_repeat_elem = false;
9892 var is_switch_elem = false; 9893 var tpl_inher = inher && inher.wrap_id==id ? inher : null; 9894 var class_name = elem.className; 9895 if (class_name) 9896 { if (typeof(class_name) != 'string') 9897 { class_name = elem.getAttribute('class') || ''; // in Chrome class_name can be SVGAnimatedString 9898 } 9899 let pos = class_name.indexOf('$'); 9900 if (pos != -1) 9901 { let class_list = elem.classList; // classList support is present since MSIE10 9902 let new_class_name = ''; 9903 for (let i=0, c; c=class_list[i]; i++) 9904 { if (c.charAt(0)=='$' && c.length>1) 9905 { let c_tpl_name = c.slice(1); 9906 if (c_tpl_name == 'strip-text-nodes') 9907 { strip_text_nodes(elem); 9908 } 9909 else if (c_tpl_name == 'repeat-elem') 9910 { is_repeat_elem = true; 9911 } 9912 else if (c_tpl_name == 'switch-elem') 9913 { is_switch_elem = true; 9914 } 9915 else if (typeof(ns.templates[c_tpl_name]) == 'function') 9916 { let c_tpl_details = ns.templates[c_tpl_name](); 9917 if (c_tpl_details.html != null) 9918 { if (!tpl_name_main) 9919 { tpl_name_main = c_tpl_name; 9920 tpl_details = c_tpl_details; 9921 } 9922 else 9923 { warn(`Templates "${tpl_name_main}" and "${c_tpl_name}" cannot come together`, elem, class_name); 9924 } 9925 } 9926 else 9927 { if (!tpl_names_secondary) 9928 { tpl_names_secondary = [c_tpl_name]; 9929 } 9930 else 9931 { tpl_names_secondary[tpl_names_secondary.length] = c_tpl_name; 9932 } 9933 if (!tpl_details) 9934 { tpl_details = c_tpl_details; 9935 } 9936 } 9937 if (!ap) ap = {}; // don't use "empty" because this object can be modified in _reapply_template() 9938 } 9939 else 9940 { warn(`Template "${c_tpl_name}" not found in current namespace`, elem, class_name); 9941 if (new_class_name) 9942 { new_class_name += ' '; 9943 } 9944 new_class_name += c; 9945 } 9946 } 9947 else 9948 { if (new_class_name) 9949 { new_class_name += ' '; 9950 } 9951 new_class_name += c; 9952 } 9953 } 9954 if (!new_class_name) 9955 { elem.removeAttribute('class'); 9956 } 9957 else 9958 { elem.setAttribute('class', new_class_name); // note, that Chrome 87.0.4280.88 throws error if i try to elem.className = ..., if the elem is an SVG element 9959 } 9960 if (is_repeat_elem && is_switch_elem) 9961 { warn('$repeat-elem and $switch-elem cannot come together', elem, class_name); 9962 is_switch_elem = false; 9963 } 9964 if (is_switch_elem && tpl_name_main) 9965 { warn('$switch-elem cannot come together with another template that replaces the elem', elem, class_name); 9966 tpl_name_main = null; 9967 } 9968 } 9969 } 9970 if (!tpl_name_main && tpl_names_secondary) 9971 { tpl_name_main = tpl_names_secondary.shift(); 9972 if (tpl_names_secondary.length == 0) 9973 { tpl_names_secondary = null; 9974 } 9975 } 9976 if (id && (remove_ids || is_repeat_elem || ap && ap.id===null)) 9977 { elem.removeAttribute('id'); 9978 } 9979 if (tpl_name_main) // if is template 9980 { tpl_by_id_in = {}; 9981 for_dyn_options = 9982 { options: null, 9983 dyn_options: null, 9984 dyn_options_info: null 9985 }; 9986 tpl_elem = elem; 9987 new_tpl_step_2 = tpl_details.inst(for_dyn_options, tpl_elem, id, ap, by_id, tpl_by_id_in, by_id_parent_use, win_doc, onready_queue, tpl_inher, props); 9988 tpl_onnewids = function() 9989 { add_props_shallow(by_id_in, tpl_by_id_in); 9990 onnewids?.(); 9991 }; 9992 } 9993 function get_repl_elem() 9994 { return repl_elem || elem; 9995 } 9996 var for_props_has_ontplloading = {value: props_has_ontplloading}; 9997 tpl_ontplloading = hook_ontplloading 9998 ( ontplloading, 9999 get_repl_elem, 10000 ontpl_attrs_special, 10001 for_props_has_ontplloading 10002 ); 10003 tpl_ontplloading_2 = hook_ontplloading 10004 ( tpl_ontplloading, 10005 get_repl_elem, 10006 ap, 10007 for_props_has_ontplloading 10008 ); 10009 tpl_ontplloading_3 = !tpl_details ? tpl_ontplloading_2 : hook_ontplloading 10010 ( tpl_ontplloading_2, 10011 get_repl_elem, 10012 tpl_details.props, 10013 for_props_has_ontplloading, 10014 tpl_details.inher 10015 ); 10016 tpl_ontplevent = hook_ontplevent 10017 ( ns, 10018 ontplevent, 10019 get_repl_elem, 10020 ap, 10021 ontpl_attrs, 10022 undefined, 10023 NEW_ELEM_NDEBUG ? undefined : is_tpl_name, 10024 NEW_ELEM_NDEBUG ? undefined : id 10025 ); 10026 tpl_ontpleventchildren = !tpl_details ? tpl_ontplevent : hook_ontplevent 10027 ( ns, 10028 tpl_ontplevent, 10029 get_repl_elem, 10030 tpl_details.props, 10031 null, 10032 tpl_details.inher, 10033 NEW_ELEM_NDEBUG ? undefined : tpl_name_main, 10034 NEW_ELEM_NDEBUG ? undefined : '{}' 10035 ); 10036 tpl_ontpleventinner = !tpl_details || tpl_details.html==null ? tpl_ontpleventchildren : ontplevent_filter 10037 ( tpl_ontpleventchildren, 10038 get_repl_elem, 10039 tpl_details.props, 10040 tpl_details.reports, 10041 tpl_details.inher 10042 ); 10043 props_has_ontplloading = for_props_has_ontplloading.value; 10044 // 10045 if (is_repeat_elem) 10046 { if (!e_parent_node) 10047 { warn("$repeat-elem: element must be attached to parent"); 10048 } 10049 else 10050 { // repeat elem 10051 let case_props_decl = get_data_attr(ns, elem, 'case', null, true, ap); 10052 function splice(index, n_remove, add, case_props) 10053 { // normalize args 10054 index |= 0; 10055 if (index >= 0) 10056 { n_remove |= 0; 10057 let add_is_array = typeof(add) == 'object'; 10058 let n_add = (add_is_array ? add.length : add)|0; 10059 if (index > this.length) 10060 { index = this.length; 10061 } 10062 // find node at index 10063 let node = this._get_node_at_index(index); 10064 let {repeat_option} = this; 10065 let repeat_option_i = !repeat_option ? -1 : repeat_option.get_option_index() + index; 10066 // 10067 onready_queue.enter(); 10068 // remove 10069 if (n_remove > 0) 10070 { if (index+n_remove > this.length) 10071 { n_remove = this.length - index; 10072 } 10073 Array.prototype.splice.call(this, index, n_remove); 10074 repeat_option?.options.splice(repeat_option_i, n_remove); 10075 while (n_remove-->0 && node) 10076 { let e = node.nextSibling; 10077 this._e_parent_node.removeChild(node); 10078 node = e; 10079 while (node && node._ne__repeat_elem!==this._repeat_elem_id) 10080 { node = node.nextSibling; 10081 } 10082 } 10083 } 10084 // add 10085 for (let j=0; j<n_add; j++) 10086 { if (add_is_array) 10087 { case_props = add[j]; 10088 } 10089 let by_id_2 = {parent: by_id}; 10090 let e = this.repeat_elem.cloneNode(true); 10091 e._ne__repeat_elem = this._repeat_elem_id; 10092 if (tpl_ontplevent) 10093 { if (!case_props) case_props = {}; 10094 var event_obj = new NewElemEvent('beforecase', e, 'repeat-elem'); 10095 tpl_ontplevent([event_obj, index, case_props]); // let modify case_props 10096 } 10097 this._e_parent_node.insertBefore(e, node); 10098 Array.prototype.splice.call(this, index, 0, {elem: e, by_id: by_id_2}); 10099 if (repeat_option) 10100 { let for_dyn_options_options = {}; 10101 let sub_dyn_options_info = []; 10102 var for_dyn_options = this._repeat_option_has_suboptions ? {options: null, dyn_options: {length: 0}, dyn_options_info: sub_dyn_options_info} : null; 10103 let option = get_elem_option(ns, e, for_dyn_options_options, sub_dyn_options_info); 10104 if (for_dyn_options_options.options) 10105 { option._dyn_options = for_dyn_options.dyn_options = for_dyn_options_options.options; 10106 for (let k=1, option_options; option_options = for_dyn_options_options['options_'+k]; k++) 10107 { option['_dyn_options_'+k] = for_dyn_options['dyn_options_'+k] = option_options; 10108 } 10109 } 10110 repeat_option.options.splice(repeat_option_i++, 0, option); 10111 } 10112 let r_props = clone_obj(ap); // clone deep, so one row will not modify another's data 10113 r_props = case_to_tpls_props(case_props_decl, case_props, r_props); // modify 10114 new_elem_traverse 10115 ( e, 10116 r_props, 10117 by_id_2, 10118 by_id_in, 10119 remove_ids, 10120 !id ? onnewids : function() 10121 { by_id_export_from_repeated_child(id, by_id, by_id_2); 10122 onnewids?.(); 10123 }, 10124 ontplloading, 10125 props_has_ontplloading, 10126 ontplevent, 10127 null, 10128 for_dyn_options 10129 ); 10130 if (id) 10131 { by_id_export_from_repeated_child(id, by_id, by_id_2); 10132 onnewids?.(); 10133 } 10134 if (ap) 10135 { add_props(win_doc, ns, e, ap, 0, by_id_2, id, onready_queue); // exclude ids 10136 } 10137 index++; 10138 if (tpl_ontplevent) 10139 { event_obj.type = 'case'; 10140 onready_queue.add_onready(tpl_ontplevent, this, [[event_obj, index, case_props, true, true]]); 10141 } 10142 } 10143 // 10144 if (tpl_ontplevent) 10145 { event_obj = new NewElemEvent('resize', this, 'repeat-elem'); 10146 onready_queue.add_onready(tpl_ontplevent, this, [[event_obj]]); 10147 } 10148 onready_queue.leave(); 10149 } 10150 } 10151 let obj = new RepeatElem(elem, e_parent_node, e_previous_sibling, splice); 10152 e_parent_node.removeChild(elem); 10153 let dyn_options_info = parent_for_dyn_options?.dyn_options_info; 10154 if (dyn_options_info) 10155 { for (let i=0, i_end=dyn_options_info.length; i<i_end; i++) 10156 { let o = dyn_options_info[i]; 10157 if (o.elem == elem) 10158 { // found 10159 obj.repeat_option = o; 10160 obj._repeat_option_has_suboptions = !!o.dyn_options[o.i].options; 10161 add_props(win_doc, ns, obj, ap, 0, obj); 10162 add_props(win_doc, ns, obj, o.dyn_options[o.i], 0, obj); 10163 o.dyn_options[o.i] = obj; 10164 break; 10165 } 10166 } 10167 } 10168 elem = obj; 10169 } 10170 } 10171 else if (is_switch_elem) 10172 { // the switch
10173 let sw_is_initing = true; 10174 let sw_initial_case = get_data_attr(ns, elem, 'case', null, true, ap); 10175 let sw_cases_props_decl = get_data_attr(ns, elem, 'cases', null, true, ap); 10176 let sw_hash = get_data_attr(ns, elem, 'hash', null, true, ap); 10177 let sw_map = {}; 10178 let sw_case_names = []; 10179 let sw_cur_case_name = null; 10180 let sw_cur_case_props = {}; 10181 let sw_hash_cases = null; 10182 let sw_state_handler = ns.SwitchElemStateHandler; 10183 let sw_state = null; 10184 let want_reswitch_name = undefined; 10185 let want_reswitch_props = undefined; 10186 let want_reswitch_force_reapply = undefined; 10187 let want_reswitch_transit = undefined; 10188 let want_reswitch_ontplload = undefined; 10189 function sw_init() 10190 { var e; 10191 while ((e = elem.firstChild)) 10192 { if (e.nodeType == XML_ELEMENT_NODE) 10193 { let case_name = get_data_attr(ns, e, 'case', null, true); 10194 if (case_name == null) 10195 { error('No case in element inside $switch-elem', e); 10196 } 10197 else 10198 { let c_hash = get_data_attr(ns, e, 'hash', null, true); 10199 let c_prepared = !!get_data_attr(ns, e, 'prepared', false, true); 10200 if (sw_hash!=null && c_hash==null) 10201 { c_hash = case_name; 10202 } 10203 c_hash = (sw_hash || '') + (c_hash || ''); 10204 if (c_hash) 10205 { // add to sw_hash_cases 10206 if (sw_state_handler) 10207 { let case_props_decl = sw_cases_props_decl?.[case_name]; 10208 if (!sw_hash_cases) 10209 { sw_hash_cases = []; 10210 } 10211 sw_hash_cases[sw_hash_cases.length] = 10212 { case_name: case_name, 10213 hash: c_hash, 10214 hash_format: case_props_decl ? case_props_decl.hash : null 10215 }; 10216 } 10217 } 10218 sw_map[case_name] = {elem: e, inited: c_prepared, hash: c_hash}; 10219 if (c_prepared) 10220 { sw_map[case_name].elem = e = new_elem_traverse(e, props, by_id, by_id_in, remove_ids, onnewids, ontplloading, props_has_ontplloading, ontplevent, sw_map[case_name], parent_for_dyn_options); 10221 sw_map[case_name].elem._ne__switch_elem = elem; 10222 } 10223 sw_case_names.push(case_name); 10224 } 10225 } 10226 if (e.parentNode == elem) // new_elem_traverse could change 10227 { elem.removeChild(e); 10228 } 10229 } 10230 // state 10231 if (sw_hash_cases) 10232 { sw_state = sw_state_handler.reg 10233 ( sw_hash_cases, 10234 (new_case_name, new_case_props) => 10235 { if (sw_is_initing) 10236 { sw_initial_case = null; 10237 } 10238 onready_queue.add_onready(sw_set_case, null, [new_case_name, new_case_props, false, null, null, true]); 10239 } 10240 ); 10241 } 10242 if (sw_initial_case != null) 10243 { // set initial case (before ontplready) 10244 if (sw_set_case(sw_initial_case, ap) !== undefined) 10245 { if (sw_state) 10246 { sw_is_initing = true; // fuse 10247 sw_state.set(sw_cur_case_name, sw_cur_case_props); 10248 sw_is_initing = false; 10249 } 10250 } 10251 } 10252 // add props (including ontplready) 10253 if (ap != null) 10254 { add_props(win_doc, ns, elem, ap, 0, {'case': 1, cases: 1, hash: 1}, id, onready_queue); 10255 } 10256 sw_is_initing = false; 10257 } 10258 function sw_set_case(case_name, case_props, force_reapply, transit, ontplload, no_update_hash) 10259 { if (case_name!=null && (!sw_map[case_name] || (case_name in {}))) 10260 { throw new Error('No such case in $switch-elem: '+case_name); 10261 } 10262 var need_init = case_name!=null && !sw_map[case_name].inited; 10263 if (!sw_is_initing && (tpl_ontplevent || elem.ontplbeforecase || elem.ontplcase)) // tplbeforecase will not fire for initial case (but tplcase will) 10264 { if (!case_props) case_props = {}; 10265 if (!force_reapply && case_name!=null && case_name==sw_cur_case_name) 10266 { sw_cur_case_props = clone_obj(sw_cur_case_props, 1); 10267 } 10268 if (elem.ontplbeforecase) 10269 { var result = elem.ontplbeforecase(new NewElemEvent('beforecase', elem, 'switch-elem'), case_name, case_props, need_init); 10270 if (!result && result!==undefined) return result; 10271 } 10272 if (tpl_ontplevent) 10273 { result = tpl_ontplevent([new NewElemEvent('beforecase', elem, 'switch-elem'), case_name, case_props, need_init]); // let modify case_props 10274 if (!result && result!==undefined) return result; 10275 } 10276 } 10277 if (force_reapply || case_name!=sw_cur_case_name || !equals(case_props || {}, sw_cur_case_props || {})) 10278 { let save_cur_case_name = sw_cur_case_name; 10279 let save_case_name = case_name; 10280 if (save_cur_case_name!=null && !sw_map[save_cur_case_name].elem) 10281 { sw_map[save_cur_case_name].elem = elem.firstChild; 10282 } 10283 if (need_init) 10284 { sw_map[save_case_name].inited = true; 10285 } 10286 sw_cur_case_name = case_name; 10287 sw_cur_case_props = case_props || {}; 10288 onready_queue.enter(); // align ontplready/ontplload calls in valid order 10289 let autoplay = transit && !('play' in transit); 10290 let transit_set_end = transit_set_begin(transit); 10291 if (!need_init) 10292 { if (case_props) 10293 { // reapply templates 10294 let r_props = case_to_tpls_props(sw_cases_props_decl?.[case_name], case_props); 10295 if (r_props) 10296 { add_props(win_doc, ns, by_id, r_props, force_reapply ? 2 : 1, null, null, onready_queue); 10297 } 10298 } 10299 } 10300 else 10301 { props = case_to_tpls_props(sw_cases_props_decl?.[case_name], case_props, props); // merge props 10302 sw_map[save_case_name].elem = new_elem_traverse(sw_map[save_case_name].elem, props, by_id, by_id_in, remove_ids, onnewids, ontplloading, props_has_ontplloading, ontplevent, sw_map[save_case_name], parent_for_dyn_options); 10303 sw_map[save_case_name].elem._ne__switch_elem = elem; 10304 onnewids?.(); 10305 } 10306 if (tpl_ontplevent || elem.ontplcase) 10307 { let args = [new NewElemEvent('case', elem, 'switch-elem'), case_name, case_props, need_init]; 10308 if (elem.ontplcase)
10309 { onready_queue.add_onready(elem.ontplcase, elem, args); 10310 } 10311 if (tpl_ontplevent) 10312 { onready_queue.add_onready(tpl_ontplevent, elem, [args]); 10313 } 10314 } 10315 replace_elem 10316 ( ns, 10317 win_doc, 10318 onready_queue, 10319 save_cur_case_name==null ? null : sw_map[save_cur_case_name].elem, 10320 elem, 10321 null, 10322 save_case_name==null ? null : sw_map[save_case_name].elem, 10323 ontplload, 10324 transit, 10325 () => 10326 { elem.ontplcasetransitionend?.(new NewElemEvent('casetransitionend', elem, 'switch-elem')); 10327 tpl_ontplevent?.([new NewElemEvent('casetransitionend', elem, 'switch-elem')]); 10328 } 10329 ); 10330 onready_queue.leave(); 10331 if (autoplay) 10332 { deferred(transit_set_end, 60); // give to template time to load data (reduces flickering) 10333 } 10334 if (!no_update_hash) 10335 { sw_state?.set(sw_cur_case_name, sw_cur_case_props); 10336 } 10337 } 10338 else 10339 { transit_set_begin(transit)(); 10340 if (ontplload) ontplload.call(elem); 10341 } 10342 if (want_reswitch_name !== undefined) 10343 { case_name = want_reswitch_name; 10344 case_props = want_reswitch_props; 10345 force_reapply = want_reswitch_force_reapply; 10346 transit = want_reswitch_transit; 10347 ontplload = want_reswitch_ontplload 10348 want_reswitch_name = want_reswitch_props = want_reswitch_force_reapply = want_reswitch_transit = want_reswitch_ontplload = undefined; 10349 sw_set_case(case_name, case_props, force_reapply, transit, ontplload); 10350 } 10351 } 10352 elem._get_cases = function() 10353 { return sw_case_names; 10354 }; 10355 elem._get_case = function() 10356 { return sw_cur_case_name; 10357 }; 10358 elem._set_case = function(case_name, case_props, force_reapply, transit, ontplload) 10359 { sw_set_case(case_name, case_props, force_reapply, transit, ontplload); 10360 }; 10361 elem._reapply_template = function(tpl_props, set_new, only_if_diff, transit, ontplload) 10362 { if (tpl_props) 10363 { var case_name = tpl_props?.['case']; 10364 if (case_name != null) 10365 { if ((!sw_map[case_name] || (case_name in {}))) 10366 { throw new Error('No such case in $switch-elem: '+case_name); 10367 } 10368 } 10369 else 10370 { case_name = sw_cur_case_name; 10371 } 10372 var case_props_decl = sw_cases_props_decl?.[case_name]; 10373 sw_set_case(case_name, tpl_props, !only_if_diff, transit, ontplload); 10374 add_props(win_doc, ns, this, tpl_props, 0, case_props_decl, null, onready_queue, transit, true); 10375 } 10376 return this; 10377 }; 10378 sw_init(); 10379 } 10380 else 10381 { // traverse recursively 10382 for (let e=elem.firstElementChild, e_next; e; e=e_next) 10383 { e_next = e.nextElementSibling; 10384 new_elem_traverse(e, props, by_id, tpl_by_id_in, remove_ids, tpl_onnewids, tpl_ontplloading_3, props_has_ontplloading, tpl_ontpleventchildren, null, for_dyn_options ? for_dyn_options : parent_for_dyn_options); 10385 } 10386 // 10387 if (tpl_name_main) // if is template 10388 { add_props_shallow(by_id_in, tpl_by_id_in); 10389 // apply template 10390 repl_elem = new_tpl_step_2(props_has_ontplloading, tpl_ontplloading_3, tpl_ontplevent, tpl_ontpleventinner); 10391 if (tpl_names_secondary) 10392 { for (let i=0, i_end=tpl_names_secondary.length; i<i_end; i++) 10393 { let c_tpl_details = ns.templates[tpl_names_secondary[i]](); 10394 let c_for_dyn_options = 10395 { options: null, 10396 dyn_options: null, 10397 dyn_options_info: null 10398 }; 10399 let c_new_tpl_step_2 = c_tpl_details.inst(c_for_dyn_options, repl_elem, id, ap, by_id, {}, by_id_parent_use, win_doc, onready_queue, null, props); 10400 c_new_tpl_step_2(props_has_ontplloading, tpl_ontplloading_3, tpl_ontplevent, tpl_ontpleventchildren); 10401 } 10402 } 10403 } 10404 else if (ap) 10405 { // add props 10406 add_props(win_doc, ns, elem, ap, 0, null, id, onready_queue); 10407 } 10408 // replace elem? 10409 if (repl_elem!=null && repl_elem!=elem) 10410 { elem = replace_elem(ns, win_doc, onready_queue, elem, e_parent_node, e_previous_sibling, repl_elem); 10411 } 10412 } 10413 // add to by_id 10414 if (id) 10415 { if (by_id[id]) 10416 { log('Markup has duplicate id: '+id, by_id[id], elem); 10417 } 10418 by_id[id] = elem; 10419 if (by_id_in) 10420 { by_id_in[id] = true; 10421 } 10422 } 10423 // 10424 var ontplready = is_repeat_elem || !ontpl_attrs_special ? null : ontpl_attrs_special.ontplready; 10425 if (ontplready) 10426 { // ontplready legitimately fires again when the element is re-traversed (re-show, 10427 // lazy $switch-elem case init, template reapply). Stamp the element after the 10428 // first run, so handlers that must init once (e.g. DOM-appending code) can guard: 10429 // if (this.getAttribute('data-tpl-inited')) return; 10430 onready_queue.add_onready 10431 ( function() 10432 { try 10433 { return ontplready.apply(this, arguments); 10434 } 10435 finally 10436 { this.setAttribute?.('data-tpl-inited', '1'); 10437 } 10438 }, 10439 elem 10440 ); 10441 } 10442 if (tpl_ontplloading_3 != tpl_ontplloading_2) 10443 { onready_queue.add_onload(tpl_ontplloading_3); // load 10444 } 10445 if (tpl_ontplloading_2 != tpl_ontplloading) 10446 { onready_queue.add_onload(tpl_ontplloading_2); // load 10447 } 10448 if (tpl_ontplloading != ontplloading) 10449 { onready_queue.add_onload(tpl_ontplloading); // load 10450 } 10451 // 10452 if (is_repeat_elem) 10453 { elem.set_length(get_data_attr(ns, elem.repeat_elem, 'length', 0, true, ap)); 10454 } 10455 else if (tpl_name_main) // if is template, add _clone_template() and _reapply_template() to tpl 10456 { function get_tplname() // TODO: rename to _get_tplnames() 10457 { return !tpl_name_main ? tpl_names_secondary.join(' ') : !tpl_names_secondary ? tpl_name_main : tpl_name_main+' '+tpl_names_secondary.join(' '); 10458 } 10459 function get_tplelem() 10460 { return elem; 10461 } 10462 function get_tplprops() 10463 { return ap; 10464 }
10465 function get_tplwrapped() 10466 { var {inher} = tpl_details; 10467 return inher?.to_wrap_elem?._get_tplname(); 10468 } 10469 function get_tplhtml() 10470 { return tpl_details.html; 10471 } 10472 function get_tplfunc() 10473 { return tpl_details.func; 10474 } 10475 function tpl_serialize() 10476 { var attrs_str = serialize_attrs(ap, {options: 1}, ('$'+tpl_name_main+' '+tpl_details.class_name_for_serialize).trim()); 10477 var options_html; 10478 var inner_html = tpl_details.html==null ? this.innerHTML : ap.options ? (options_html = '\n'+serialize_tpl_options(ap.options, '\t')) : ''; 10479 var html = tpl_details.elem_for_serialize.outerHTML; 10480 var pos = html.match(/^<(?:[^"'>]+|"[^"]*"|'[^']*')*>/)[0].length - 1; // pos of '>' that terminates opening tag 10481 var tag_open = html.slice(0, pos); 10482 var rest = html.slice(pos + 1); 10483 if (options_html) 10484 { pos = tag_open.indexOf(' '); 10485 tag_open = pos==-1 ? '<menu' : '<menu'+tag_open.slice(pos); 10486 pos = rest.lastIndexOf('<'); 10487 rest = rest.slice(0, pos)+'</menu>'; 10488 } 10489 return tag_open+attrs_str+'>'+inner_html+rest; 10490 } 10491 function clone_template(parent_node, ontplload) 10492 { var hook = tpl_ontplloading; 10493 if (ontplload != null) 10494 { var elem = this; 10495 hook = hook_ontplloading(null, () => elem, {ontplload}, {value: false}); 10496 } 10497 onready_queue.enter(); 10498 var e2 = tpl_details.html!=null ? tpl_elem : clone_node(this, null, null, null, true); 10499 var repl_elem = ns.templates[tpl_name_main]().inst(for_dyn_options, e2, id, ap, by_id, tpl_by_id_in, by_id_parent_use, win_doc, onready_queue, tpl_inher, props)(false, hook); 10500 repl_elem._get_tplname = get_tplname; 10501 repl_elem._get_tplelem = get_tplelem; 10502 repl_elem._get_tplprops = get_tplprops; 10503 repl_elem._get_tplwrapped = get_tplwrapped; 10504 repl_elem._get_tplhtml = get_tplhtml; 10505 repl_elem._get_tplfunc = get_tplfunc; 10506 repl_elem._reapply_template = reapply_template; 10507 repl_elem._clone_template = clone_template; 10508 repl_elem._tpl_serialize = tpl_serialize; 10509 add_to_parent(repl_elem, parent_node); 10510 add_props(win_doc, ns, repl_elem, {attributes: this, style: this.style.cssText, className: this.className}); 10511 hook?.(); 10512 onready_queue.leave(); 10513 return repl_elem; 10514 } 10515 let is_reapplying = false; 10516 let want_again = false; 10517 function reapply_template(tpl_props, set_new, only_if_diff, transit, ontplload) 10518 { var no_action = false; 10519 if (tpl_props) 10520 { var tpl_props_only, to_add, to_add_ontpl; 10521 if (only_if_diff) 10522 { for (let a in tpl_props) 10523 { let is_ontpl = a.slice(0, 5) == 'ontpl'; 10524 let is_on_listeners = a.slice(0, 2)=='on' && typeof(tpl_props[a]?.func)=='function'; 10525 if ((a in tpl_details.arg_names_assoc) && !is_ontpl && !is_on_listeners) 10526 { if (!tpl_props_only) tpl_props_only = {}; 10527 tpl_props_only[a] = tpl_props[a]; 10528 } 10529 else 10530 { if (!to_add) to_add = {}; 10531 to_add[a] = tpl_props[a]; 10532 if (is_ontpl) 10533 { if (!to_add_ontpl) to_add_ontpl = {}; 10534 to_add_ontpl[a] = tpl_props[a]; 10535 } 10536 } 10537 } 10538 } 10539 if (set_new) 10540 { if (only_if_diff && equals(ap || {}, tpl_props_only || {})) 10541 { no_action = true; 10542 } 10543 else 10544 { ap = typeof(tpl_props)=='object' ? tpl_props : null; 10545 } 10546 } 10547 else if (!only_if_diff) 10548 { if (ap) 10549 { add_props_shallow(ap, tpl_props); 10550 } 10551 else 10552 { ap = tpl_props; 10553 } 10554 } 10555 else if (!tpl_props_only) 10556 { no_action = true; 10557 } 10558 else 10559 { // can use this._set_*()? 10560 let to_set = []; 10561 for (let a in tpl_props_only) 10562 { if (typeof(this['_set_'+a]) == 'function') 10563 { to_set[to_set.length] = a; 10564 } 10565 else if (!ap || !equals(ap[a], tpl_props_only[a])) 10566 { to_set = null; 10567 break; 10568 } 10569 } 10570 if (to_set) 10571 { if (to_set.length) 10572 { if (!ap) ap = {}; 10573 for (let a of to_set) 10574 { this['_set_'+a](tpl_props[a]); 10575 ap[a] = tpl_props[a]; 10576 } 10577 } 10578 no_action = true; 10579 } 10580 else 10581 { if (ap) 10582 { add_props_shallow(ap, tpl_props); 10583 } 10584 else 10585 { ap = tpl_props; 10586 } 10587 } 10588 } 10589 } 10590 var transit_set_end = transit_set_begin(transit); 10591 if (no_action || is_reapplying) 10592 { if (!no_action) 10593 { want_again = true; 10594 log('Template "'+tpl_name_main+'" is already reapplying. I will reapply it again when it finishes.'); 10595 } 10596 if (to_add) 10597 { add_props(win_doc, ns, this, to_add, 0, null, null, onready_queue, transit, true); 10598 } 10599 transit_set_end(); 10600 if (ontplload) ontplload.call(this, false); // ontplload(has_reapplied) 10601 return this; 10602 } 10603 is_reapplying = true; 10604 onready_queue.enter(); 10605 var e_parent_node = this.parentNode; 10606 var e_previous_sibling = this.previousSibling; 10607 var elem = this; 10608 var new_tpl_step_2 = ns.templates[tpl_name_main]().inst(for_dyn_options, tpl_elem, id, ap, by_id, tpl_by_id_in, by_id_parent_use, win_doc, onready_queue, tpl_inher, props); 10609 if (tpl_ontplloading_3 != tpl_ontplloading_2) 10610 { var event_obj = new NewElemEvent('unloading', elem, tpl_name_main); 10611 tpl_ontplloading_3(null, event_obj, tpl_details.props, onready_queue); // load 10612 } 10613 if (tpl_ontpleventchildren != tpl_ontplevent) 10614 { tpl_ontpleventchildren(null, tpl_details); 10615 } 10616 repl_elem = new_tpl_step_2(props_has_ontplloading, tpl_ontplloading_3, tpl_ontplevent, tpl_ontpleventchildren, transit); 10617 repl_elem._get_tplname = get_tplname; 10618 repl_elem._get_tplelem = get_tplelem; 10619 repl_elem._get_tplprops = get_tplprops;
10620 repl_elem._get_tplwrapped = get_tplwrapped; 10621 repl_elem._get_tplhtml = get_tplhtml; 10622 repl_elem._get_tplfunc = get_tplfunc; 10623 repl_elem._reapply_template = reapply_template; 10624 repl_elem._clone_template = clone_template; 10625 repl_elem._tpl_serialize = tpl_serialize; 10626 if (to_add_ontpl) 10627 { add_props(win_doc, ns, repl_elem, to_add_ontpl, 0, null, null, onready_queue, null, true); 10628 } 10629 if (id) by_id[id] = repl_elem; 10630 if (for_update_elem) for_update_elem.elem = repl_elem; 10631 repl_elem = replace_elem 10632 ( ns, 10633 win_doc, 10634 onready_queue, 10635 this, 10636 e_parent_node, 10637 e_previous_sibling, 10638 repl_elem, 10639 function() 10640 { is_reapplying = false; 10641 ontplload?.call(this, true); // ontplload(has_reapplied) 10642 if (want_again) 10643 { want_again = false; 10644 reapply_template(); 10645 } 10646 }, 10647 transit 10648 ); 10649 if (tpl_inher) 10650 { tpl_inher.to_wrap_elem = repl_elem; 10651 tpl_inher.apply_props(); 10652 } 10653 if (tpl_ontplloading_3 != tpl_ontplloading_2) 10654 { event_obj.type = 'loading'; 10655 event_obj.target = repl_elem; 10656 onready_queue.add_onready(tpl_ontplloading_3, this, [false, event_obj]); // load 10657 } 10658 transit_set_end(); 10659 onready_queue.leave(); 10660 return repl_elem; 10661 } 10662 elem._get_tplname = get_tplname; 10663 elem._get_tplelem = get_tplelem; 10664 elem._get_tplprops = get_tplprops; 10665 elem._get_tplwrapped = get_tplwrapped; 10666 elem._get_tplhtml = get_tplhtml; 10667 elem._get_tplfunc = get_tplfunc; 10668 elem._reapply_template = reapply_template; 10669 elem._clone_template = clone_template; 10670 (repl_elem ?? elem)._tpl_serialize = tpl_serialize; 10671 } 10672 return elem; 10673 } 10674 if (node_name.cloneNode) 10675 { if (parent_node) 10676 { add_to_parent(node_name, parent_node); 10677 } 10678 if (node_name.nodeType == XML_ELEMENT_NODE) 10679 { if (for_root_id) 10680 { for_root_id.id = node_name.getAttribute('id'); 10681 } 10682 ret_elem = new_elem_traverse(node_name, props, by_id, null, false, null, ontplloading, props_has_ontplloading, ontplevent); 10683 } 10684 else 10685 { ret_elem = node_name; 10686 } 10687 } 10688 else 10689 { ret_elem = win_doc.doc.createElement('div'); 10690 add_props(win_doc, ns, ret_elem, {childNodes: node_name}); 10691 elem_trim(ret_elem); 10692 let children = ret_elem.childNodes; 10693 let n_children = children.length; 10694 if (for_root_id && n_children==1) 10695 { for_root_id.id = ret_elem.firstChild.getAttribute('id'); 10696 } 10697 if (parent_node) 10698 { children = add_to_parent(children, parent_node) || children; 10699 ret_elem = null; 10700 } 10701 for (let i=0; i<n_children; i++) 10702 { let e = children[i]; 10703 if (e.nodeType == XML_ELEMENT_NODE) 10704 { e = new_elem_traverse(e, props, by_id, null, !props.no_remove_ids, null, ontplloading, props_has_ontplloading, ontplevent); 10705 } 10706 if (i==0 && n_children==1) 10707 { ret_elem = e; 10708 } 10709 } 10710 } 10711 if (props.ontplready) 10712 { onready_queue.add_onready(props.ontplready, ret_elem); 10713 } 10714 if (ontplloading && ontplloading!=parent_ontplloading) 10715 { onready_queue.add_onready(ontplloading, ret_elem); 10716 } 10717 return ret_elem; 10718} 10719function declare_template(name, wrap_id, html, arg_names, func) 10720{ var ns = this; 10721 if (!ns.templates) 10722 { ns.templates = {}; 10723 } 10724 ns.templates[name] = function() 10725 { // arguments 10726 if (!arg_names) 10727 { const RE_TPL_ARGS_TOKENIZER = /[^,(){}\[\]'"`\/]+|'(?:[^'\\]*)(?:\\?[^'\\]*)*'|"(?:[^"\\]*)(?:\\?[^"\\]*)*"|`(?:[^`\\]*)(?:\\?[^`\\]*)*`|\/\*[\S\s]*?\*\/|\/\/[^\r\n]*|./g; 10728 const RE_TPL_ARGS_COMMENTS = /\/\*[\S\s]*?\*\/|\/\/[^\r\n]*/g; 10729 arg_names = []; 10730 let level = 0; 10731 let from = 0; 10732 function add(offset, source) 10733 { var arg_name = source.slice(from, offset); 10734 arg_name = arg_name.replace(RE_TPL_ARGS_COMMENTS, ''); 10735 arg_name = arg_name.trim(); 10736 arg_name = arg_name.charAt(0)=='_' ? arg_name.slice(1) : arg_name; 10737 var eq = arg_name.indexOf('='); 10738 if (eq != -1) 10739 { arg_name = arg_name.slice(0, eq).trim(); 10740 } 10741 arg_names[arg_names.length] = arg_name; 10742 from = offset + 1; 10743 } 10744 (func+'').replace 10745 ( RE_TPL_ARGS_TOKENIZER, 10746 (token, offset, source) => 10747 { if (from != -1) 10748 { if (token=='(' || token=='{' || token=='[') 10749 { if (level++ == 0) 10750 { from = offset + 1; 10751 } 10752 } 10753 else if (token==')' || token=='}' || token==']') 10754 { if (--level == 0) 10755 { add(offset, source); 10756 from = -1; 10757 } 10758 } 10759 else if (level==1 && token==',') 10760 { add(offset, source); 10761 } 10762 } 10763 return ''; 10764 } 10765 ); 10766 } 10767 var arg_i_ns = -1; 10768 var arg_i_by_id = -1; 10769 var arg_i_by_id_parent = -1; 10770 var arg_i_by_id_in = -1; 10771 var arg_i_elem = -1; 10772 var arg_i_window = -1; 10773 var arg_i_document = -1; 10774 var arg_i_id = -1; 10775 var arg_i_report_tplload = -1; 10776 var arg_i_report_tplunload = -1; 10777 var arg_names_assoc = {}; 10778 var reports = null; 10779 for (let i=0, i_end=arg_names.length; i<i_end; i++) 10780 { let arg_name = arg_names[i]; 10781 if (arg_name == 'ns') 10782 { arg_i_ns = i; 10783 } 10784 else if (arg_name == 'by_id') 10785 { arg_i_by_id = i; 10786 } 10787 else if (arg_name == 'by_id_parent') 10788 { arg_i_by_id_parent = i; 10789 } 10790 else if (arg_name == 'by_id_in') 10791 { arg_i_by_id_in = i; 10792 } 10793 else if (arg_name == 'elem') 10794 { arg_i_elem = i; 10795 } 10796 else if (arg_name == 'window') 10797 { arg_i_window = i; 10798 } 10799 else if (arg_name == 'document') 10800 { arg_i_document = i; 10801 } 10802 else if (arg_name == 'id') 10803 { arg_i_id = i; 10804 } 10805 else if (arg_name.slice(0, 10) == 'report_tpl')
10806 { let onwhat = arg_name.slice(10); 10807 if (onwhat=='ready' || onwhat=='reload') 10808 { error('$'+name+': '+arg_name+' is invalid'); 10809 arg_names[i - 1] = ''; 10810 continue; 10811 } 10812 arg_name = arg_name.slice(10); 10813 if (onwhat == 'load') 10814 { arg_i_report_tplload = i; 10815 } 10816 else if (onwhat == 'unload') 10817 { arg_i_report_tplunload = i; 10818 } 10819 else if (onwhat != 'error') 10820 { if (!reports) 10821 { reports = []; 10822 } 10823 reports[reports.length] = arg_name; 10824 } 10825 arg_name = 'ontpl'+arg_name; 10826 } 10827 arg_names_assoc[arg_name] = true; 10828 } 10829 // tpl_details 10830 const tpl_details = 10831 { arg_names_assoc, 10832 reports, 10833 html, 10834 func, 10835 elem_for_serialize: null, 10836 class_name_for_serialize: null, 10837 props: null, 10838 has_by_id_in: false, 10839 inher: null, 10840 inst(for_dyn_options, elem, id, props, by_id_parent, by_id_in, by_id_parent_use, win_doc, onready_queue, parent_inher, parent_props) 10841 { var args = []; 10842 var by_id = by_id_parent ? {parent: by_id_parent} : {}; 10843 var n_to_load = 0; 10844 var debug_timer = null; 10845 var elem2; 10846 var by_id_parent_is_outer = get_data_attr(ns, elem, 'by_id_parent', undefined, true, props) !== undefined; 10847 var ontplloading = null; 10848 var ontplevent = null; 10849 function loading_initial(is_unload, n) 10850 { var prev_n_to_load = n_to_load; 10851 n = parseInt(n); 10852 n_to_load += is_unload ? -(isNaN(n) ? 1 : n|0) : isNaN(n) ? -n_to_load : n|0; 10853 if (prev_n_to_load==0 && n_to_load<0 || prev_n_to_load<0 && n_to_load==0) 10854 { onready_queue.add_onready 10855 ( () => 10856 { if (ontplloading) 10857 { var event_obj = new NewElemEvent(is_unload ? 'unloading' : 'loading', elem2, name); 10858 ontplloading(is_unload, event_obj); 10859 } 10860 } 10861 ); 10862 if (!NEW_ELEM_NDEBUG) 10863 { if (debug_timer) 10864 { debug_timer(); 10865 debug_timer = null; 10866 } 10867 if (is_unload) 10868 { debug_timer = deferred 10869 ( () => 10870 { log('Template $'+name+' is loading for too long', elem2); 10871 }, 10872 16000 10873 ); 10874 } 10875 } 10876 } 10877 } 10878 var loading = loading_initial; 10879 function get_arg(arg_name) 10880 { var value = get_data_attr(ns, elem, arg_name, undefined, true, props, parent_inher, for_dyn_options, parent_props, html!=null, by_id_parent); 10881 if (value !== undefined) 10882 { props[arg_name] = value; // if from attribute - copy to props, so can _reapply_template() 10883 /*if (html!=null && arg_name=='options') 10884 { elem_empty(elem); // was converted to options 10885 }*/ 10886 } 10887 else if (arg_name == 'dyn_options') 10888 { value = get_data_attr(ns, elem, 'options', undefined, true, props, parent_inher, for_dyn_options, parent_props, html!=null, by_id_parent); 10889 if (value !== undefined) 10890 { props.options = value; // if from attribute - copy to props, so can _reapply_template() 10891 props.dyn_options = value = for_dyn_options.dyn_options || {length: 0}; 10892 //elem_empty(elem); // was converted to options 10893 } 10894 } 10895 return value; 10896 } 10897 for (var i=0, i_end=arg_names.length; i<i_end; i++) 10898 { let arg_name = arg_names[i]; 10899 let value; 10900 if (i == arg_i_ns) 10901 { value = ns; 10902 } 10903 else if (i == arg_i_by_id) 10904 { value = by_id; 10905 } 10906 else if (i == arg_i_by_id_parent) 10907 { if (!by_id_parent_is_outer) 10908 { value = by_id_parent; 10909 } 10910 else 10911 { value = by_id_parent_use || by_id_parent; 10912 value = value?.parent || {}; 10913 } 10914 } 10915 else if (i == arg_i_by_id_in) 10916 { value = by_id_in || {}; 10917 } 10918 else if (i == arg_i_elem) 10919 { value = elem; 10920 } 10921 else if (i == arg_i_id) 10922 { value = id; 10923 } 10924 else if (i == arg_i_window) 10925 { value = win_doc.win; 10926 } 10927 else if (i == arg_i_document) 10928 { value = win_doc.doc; 10929 } 10930 else if (i == arg_i_report_tplload) 10931 { loading(true); 10932 value = pair_funcs(value, n => {loading(false, n)}); 10933 } 10934 else if (i == arg_i_report_tplunload) 10935 { value = pair_funcs(value, () => {loading(true)}); 10936 } 10937 else if (arg_name.slice(0, 10) == 'report_tpl') 10938 { let event_type = arg_name.slice(10); 10939 let func_name = 'ontpl'+event_type; 10940 value = pair_funcs 10941 ( value, 10942 function() 10943 { if (!elem2 || (!ontplevent && !elem2[func_name])) 10944 { if (event_type == 'error') 10945 { error(arguments[0]); 10946 } 10947 return; 10948 } 10949 var event_obj = new NewElemEvent(event_type, elem2, name); 10950 if (arguments[0] instanceof NewElemEvent) // assume: proxied like {ontplchange: report_tplchange} 10951 { var args = [...arguments]; 10952 args[0] = event_obj; 10953 } 10954 else 10955 { args = [event_obj, ...arguments]; 10956 } 10957 if (elem2[func_name]) 10958 { var stop_propagation_value = pcall(elem2[func_name], elem2, args); 10959 if (stop_propagation_value !== undefined) 10960 { return stop_propagation_value; 10961 } 10962 } 10963 if (ontplevent) 10964 { return ontplevent.call(this, args); 10965 } 10966 } 10967 ); 10968 } 10969 else 10970 { value = get_arg(arg_name); 10971 } 10972 args[i] = value; 10973 } 10974 var inher = null; 10975 if (wrap_id) 10976 { // Want find template with id "wrap_id". It will be passed to parent_inher of the found template to be filled
10976in. 10977 inher = new Inher(parent_inher, name, wrap_id, arg_names_assoc, get_arg); 10978 } 10979 if (!tpl_details.elem_for_serialize) 10980 { let elem_for_serialize = elem.cloneNode(html!=null && !('options' in arg_names_assoc)); 10981 tpl_details.elem_for_serialize = elem_for_serialize; 10982 tpl_details.class_name_for_serialize = elem_for_serialize.className; // TODO: return, don't put to tpl_details 10983 elem_for_serialize.removeAttribute('class'); 10984 } 10985 var new_elem_props = func?.apply(ns, args) || {}; 10986 tpl_details.props = new_elem_props; 10987 tpl_details.inher = inher; 10988 // Completed step 1. Returning function to call to do step 2. 10989 return function(props_has_ontplloading, set_ontplloading, set_ontplevent, set_ontpleventinner, transit) 10990 { ontplloading = set_ontplloading; 10991 ontplevent = set_ontplevent; 10992 if (NEW_ELEM_NDEBUG) 10993 { if (!ontplloading) 10994 { loading = do_nothing; 10995 } 10996 } 10997 var use_html = html==null ? elem : typeof(html)=='function' ? pcall(html, ns, [new_elem_props?.preprocess || {}], null, 'Error in preprocessing code: ')+'' : html+''; 10998 // html to elem 10999 var for_root_id = {id: null}; 11000 elem2 = new_elem_3(ns, use_html, null, new_elem_props, by_id, win_doc, onready_queue, name, by_id_parent_is_outer ? by_id_parent_use || by_id_parent : by_id, set_ontplloading, props_has_ontplloading, set_ontpleventinner, inher, for_root_id); 11001 var root_props = for_root_id.id && new_elem_props[for_root_id.id]; // props of the root elem 11002 if (parent_inher) 11003 { parent_inher.to_wrap_elem = elem2; 11004 parent_inher.to_wrap_root_props = {}; // template found 11005 if (inher?.to_wrap_root_props) 11006 { // copy from inher 11007 for (let p in inher.to_wrap_root_props) 11008 { parent_inher.to_wrap_root_props[p] = true; 11009 } 11010 } 11011 for (let p in arg_names_assoc) 11012 { parent_inher.arg_names_assoc[p] = true; 11013 } 11014 } 11015 if (inher) 11016 { if (!inher.to_wrap_elem) 11017 { error(`Template "${name}" requested to wrap element with id "${wrap_id}", but it's not found in source HTML, or it doesn't have a $class`); 11018 } 11019 else 11020 { inher.wrapper_elem = elem2; 11021 inher.apply_props(); 11022 } 11023 } 11024 if (root_props && parent_inher) 11025 { for (let p in root_props) 11026 { if (typeof(root_props[p])=='function' && p.slice(0, 2)!='on') 11027 { parent_inher.to_wrap_root_props[p] = true; 11028 } 11029 } 11030 } 11031 add_props(win_doc, ns, elem2, props, 0, arg_names_assoc, null, onready_queue, transit); 11032 add_attributes(elem2, elem, {'class': 1}, false, true); 11033 var cl_from = elem.classList; 11034 if (cl_from.length) 11035 { let cl_to = elem2.classList; 11036 for (let i=0, i_end=cl_from.length; i<i_end; i++) 11037 { cl_to.add(cl_from.item(i)); 11038 } 11039 } 11040 return elem2; 11041 }; 11042 } 11043 }; 11044 // replace with shorter function 11045 this[name] = function() 11046 { return tpl_details; 11047 }; 11048 return tpl_details; 11049 }; 11050} 11051 11052class Inher 11053{ constructor(parent_inher, wrapper_name, wrap_id, arg_names_assoc, get_arg) 11054 { this.parent_inher = parent_inher; 11055 this.wrapper_name = wrapper_name; 11056 this.wrap_id = wrap_id; 11057 this.arg_names_assoc = arg_names_assoc; // want add keys to this object 11058 this.get_arg = get_arg; 11059 this.to_wrap_elem = null 11060 this.to_wrap_root_props = null; 11061 this.wrapper_elem = null; 11062 } 11063 11064 apply_props() 11065 { var {wrapper_elem, to_wrap_elem} = this; 11066 var pp = this.to_wrap_root_props; 11067 if (wrapper_elem!=to_wrap_elem && pp) 11068 { // proxy functions from to_wrap_elem to wrapper_elem 11069 for (let prop in pp) 11070 { if (wrapper_elem[prop] == null) 11071 { let func_src = to_wrap_elem[prop] + ''; 11072 if (func_src.indexOf('function')!=-1 && !RE_THIS.test(func_src)) 11073 { wrapper_elem[prop] = to_wrap_elem[prop]; 11074 } 11075 else 11076 { wrapper_elem[prop] = function() 11077 { return to_wrap_elem[prop].apply(to_wrap_elem, arguments); 11078 }; 11079 } 11080 } 11081 } 11082 } 11083 } 11084 11085 subst_event_target(event_obj, args) 11086 { if (this.to_wrap_elem == event_obj.target) 11087 { if (this.wrapper_elem) 11088 { event_obj.target = this.wrapper_elem; 11089 } 11090 event_obj.target_name = this.wrapper_name; 11091 var func_name = 'ontpl'+event_obj.type; 11092 if (typeof(event_obj.target[func_name]) == 'function') 11093 { return pcall(event_obj.target[func_name], event_obj.target, args); 11094 } 11095 } 11096 } 11097} 11098 11099class RepeatElem 11100{ constructor(repeat_elem, e_parent_node, e_previous_sibling, splice) 11101 { this.length = 0; 11102 this.repeat_elem = repeat_elem; 11103 this.splice = splice; 11104 this.repeat_option = undefined; 11105 this._e_parent_node = e_parent_node; 11106 this._e_previous_sibling = e_previous_sibling; 11107 this._repeat_elem_id = {}; 11108 this._repeat_option_has_suboptions = false;
11109 } 11110 11111 *[Symbol?.iterator]() 11112 { for (let i=0, i_end=this.length; i<i_end; i++) 11113 { yield this[i]; 11114 } 11115 } 11116 11117 _get_node_at_index(index) 11118 { var node = this._e_previous_sibling ? this._e_previous_sibling.nextSibling : this._e_parent_node.firstChild; 11119 for (var i=0; i<index && node;) 11120 { if (node._ne__repeat_elem === this._repeat_elem_id) 11121 { i++; 11122 } 11123 node = node.nextSibling; 11124 } 11125 if (index < this.length) 11126 { while (node && node._ne__repeat_elem!==this._repeat_elem_id) 11127 { node = node.nextSibling; 11128 } 11129 } 11130 return node; 11131 } 11132 11133 swap(from_index, to_index) 11134 { if (from_index>=0 && to_index>=0 && from_index!=to_index) 11135 { var from_node = this._get_node_at_index(from_index); 11136 var to_node = this._get_node_at_index(to_index); 11137 if (from_node && to_node && from_node!=to_node) 11138 { var from = this[from_index]; 11139 var to = this[to_index]; 11140 Array.prototype.splice.call(this, from_index, 1, to); 11141 Array.prototype.splice.call(this, to_index, 1, from); 11142 var from_node_prev = from_node.previousSibling; 11143 this._e_parent_node.insertBefore(from_node, to_node); 11144 this._e_parent_node.insertBefore(to_node, from_node_prev.nextSibling); 11145 } 11146 } 11147 } 11148 11149 set_length(new_length, case_props) 11150 { if (new_length > this.length) 11151 { this.splice(this.length, 0, new_length-this.length, case_props); 11152 } 11153 else if (new_length < this.length) 11154 { this.splice(new_length, this.length-new_length, 0, case_props); 11155 } 11156 } 11157 11158 index_of_child(child) 11159 { var prev = null; 11160 while (child && child!=this._e_parent_node) 11161 { prev = child; 11162 child = child.parentNode; 11163 } 11164 if (!child || !prev) 11165 { return -1; 11166 } 11167 for (var i=this.length-1; i>=0 && this[i].elem!=prev; i--); 11168 return i; // -1 if not found 11169 } 11170} 11171 11172function add_options(ns, elem, options) 11173{ var node_name = elem?.nodeName; 11174 if (node_name=='MENU' || node_name=='OL' || node_name=='UL') 11175 { add_menu_options(ns, elem, options, node_name); 11176 } 11177 else if (node_name=='SELECT' || node_name=='DATALIST') 11178 { add_select_options(ns, elem, options); 11179 } 11180 else 11181 { return false; 11182 } 11183 return true; 11184} 11185function add_menu_options(ns, elem, options, node_name) 11186{ for (let i=0, i_end=options.length; i<i_end; i++) 11187 { let option = options[i]; 11188 let li = new_elem.call(ns, 'li', elem, {innerHTML: option.childNodes || [option.text]}); 11189 if (option.options) 11190 { let submenu = new_elem.call(ns, node_name, li); 11191 submenu.setAttribute('data-options', '1'); 11192 add_menu_options(ns, submenu, option.options, node_name); 11193 } 11194 } 11195} 11196function add_select_options(ns, elem, options) 11197{ function new_option(o) 11198 { if (typeof(o) != 'object') 11199 { return new Option(o, o); 11200 } 11201 if (Array.isArray(o)) 11202 { return new Option(o[0], o[1]!=null ? o[1] : o[0]); 11203 } 11204 var text = o.text!=null ? o.text : o[0]; 11205 var value = o.value!=null ? o.value : o[1]; 11206 if (text==null && o.childNodes) 11207 { text = ''; 11208 for (let i=0, i_end=o.childNodes.length; i<i_end; i++) 11209 { let node = o.childNodes[i]; 11210 text += node.nodeType==XML_TEXT_NODE ? node.data : node.textContent!=null ? node.textContent : node.innerText; 11211 } 11212 } 11213 var o2 = new Option(text!=null ? text : value, value!=null ? value : text); 11214 if (o.selected) 11215 { o2.selected = true; 11216 } 11217 return o2; 11218 } 11219 for (let i=0, i_end=options.length; i<i_end; i++) 11220 { let option = options[i]; 11221 if (option.options) 11222 { let submenu = new_elem.call(ns, 'optgroup', elem, {label: new_option(option).text}); 11223 add_select_options(ns, submenu, option.options); 11224 } 11225 else 11226 { elem.appendChild(new_option(option)); 11227 } 11228 } 11229} 11230function htmlspecialchars(text) 11231{ return (''+text).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); 11232} 11233function css_unit_to_px(value_with_unit, inside_elem, style_prop) 11234{ if (value_with_unit == null) 11235 { return undefined; 11236 } 11237 if (typeof(value_with_unit) == 'number') 11238 { return value_with_unit; 11239 } 11240 value_with_unit = (value_with_unit+'').trim(); 11241 var c_last = value_with_unit.charAt(value_with_unit.length - 1); 11242 if (c_last=='x' && value_with_unit.charAt(value_with_unit.length-2)=='p' || '0123456789.'.indexOf(c_last)!=-1) 11243 { return parseFloat(value_with_unit); 11244 } 11245 var is_height = style_prop=='height' || style_prop=='top' || style_prop=='bottom'; 11246 var body = (inside_elem?.ownerDocument || document).body; 11247 if (c_last == '%') 11248 { value_with_unit = parseFloat(value_with_unit); 11249 let top_elem = inside_elem?.parentNode || body; 11250 let top_elem_style = get_comp_style(top_elem); 11251 let border_box = is_height ? top_elem.offsetHeight : top_elem.offsetWidth; 11252 let border = is_height ? parseFloat(top_elem_style.borderTopWidth) + parseFloat(top_elem_style.borderBottomWidth) : parseFloat(top_elem_style.borderLeftWidth) + parseFloat(top_elem_style.borderRightWidth); 11253 let padding = is_height ? parseFloat(top_elem_style.paddingTop) + parseFloat(top_elem_style.paddingBottom) : parseFloat(top_elem_style.paddingLeft) + parseFloat(top_elem_style.paddingRight); 11254 return (border_box - (border || 0) - (padding || 0)) * value_with_unit / 100; 11255 } 11256 var top_elem = inside_elem || multipurpose_div; 11257 if (inside_elem) 11258 { if (!pcall(inside_elem.appendChild, inside_elem, [multipurpose_div])) 11259 { return undefined; 11260 } 11261 // In order to determine dimensions i need that all parent elems will not be "display:none". 11262 // I will save original values for style.display ("none" may come from CSS) to var "modified", and set default "display" value for each given tag name. 11263 // If the top element is not document.body (i.e. the elem is detached from document), i will temporarily connect the elem to document.body. 11264 var modified = []; 11265 for (let e=inside_elem; e && e!=body; e=e.parentNode) 11266 { top_elem = e; 11267 if (!e.offsetWidth && !e.offsetHeight && get_comp_style(e, 'display')=='none') 11268 { modified.push({e:e, display:e.style.display}); 11269 let tag = e.nodeName; 11270 if (!(tag in default_display_by_tags_cache)) 11271 { let temp_elem = new_elem(tag, body); 11272 default_display_by_tags_cache[tag] = get_comp_style(temp_elem, 'display', ''); 11273 body.removeChild(temp_elem); 11274 } 11275 e.style.display = default_display_by_tags_cache[tag]; 11276 } 11277 } 11278 } 11279 try 11280 { multipurpose_div.style.width = is_height ? '1px' : value_with_unit; 11281 multipurpose_div.style.height = is_height ? value_with_unit: '1px'; 11282 if (top_elem.parentNode == body) 11283 { var px = is_height ? multipurpose_div.offsetHeight : multipurpose_div.offsetWidth; 11284 } 11285 else 11286 { body.appendChild(top_elem); 11287 px = is_height ? multipurpose_div.offsetHeight : multipurpose_div.offsetWidth; 11288 body.removeChild(top_elem); 11289 } 11290 } 11291 catch (e) 11292 { error(e); 11293 } 11294 if (inside_elem) 11295 { inside_elem.removeChild(multipurpose_div); 11296 } 11297 if (modified) 11298 { for (let i=0, i_end=modified.length; i<i_end; i++) 11299 { let m = modified[i]; 11300 m.e.style.display = m.display; 11301 } 11302 } 11303 return px - 0; // undefined -> NaN 11304} 11305function with_css_unit(value, default_value) 11306{ if (typeof(value) == 'number') 11307 { value += 'px'; 11308 } 11309 else if (value != null) 11310 { value += ''; 11311 if (!/[^\d\.]/.test(value)) 11312 { value += 'px'; 11313 } 11314 } 11315 else 11316 { value = default_value; 11317 } 11318 return value; 11319} 11320function get_v_scrollbar_width() 11321{ if (scrollbar_width == null) 11322 { let outer = new_elem('div', document.body, {style: 'position:absolute; top:0; left:0; visibility:hidden; width:50px; height:50px; overflow:hidden'}); 11323 let inner = new_elem('p', outer, {style: 'width:100%; height:100px'}); 11324 let w1 = inner.offsetWidth; 11325 outer.style.overflow = 'scroll'; 11326 let w2 = inner.offsetWidth; 11327 if (w1 == w2) w2 = outer.clientWidth; 11328 document.body.removeChild(outer); 11329 scrollbar_width = w1 - w2; 11330 } 11331 return scrollbar_width; 11332} 11333function str_trim_right(str) 11334{ return str.trimEnd ? str.trimEnd() : str.trimRight ? str.trimRight() : str.replace(/\s+$/, ''); 11335} 11336function common_prefix(str_1, str_2) 11337{ var to = Math.min(str_1.length, str_2.length); 11338 var from = 0; 11339 while (from < to) 11340 { let mid = from + ((to - from + 1) >> 1); 11341 if (str_1.slice(0, mid) == str_2.slice(0, mid)) 11342 { from = mid; 11343 } 11344 else 11345 { to = mid - 1; 11346 } 11347 } 11348 return from; 11349} 11350function log() 11351{ if (!NEW_ELEM_NDEBUG) 11352 { window.console && console.log.apply(console, arguments); 11353 } 11354} 11355function error() 11356{ if (!NEW_ELEM_NDEBUG) 11357 { window.console && console.error.apply(console, arguments); 11358 } 11359} 11360function warn() 11361{ if (!NEW_ELEM_NDEBUG) 11362 { window.console && console.warn.apply(console, arguments); 11363 } 11364} 11365function info() 11366{ if (!NEW_ELEM_NDEBUG) 11367 { window.console && console.info.apply(console, arguments); 11368 } 11369} 11370class SetProp 11371{ constructor(elem, prop, is_style, value) 11372 { this.elem = elem; 11373 this.prop = prop; 11374 this.is_style = is_style; 11375 this.value = value; 11376 } 11377 11378 apply_value(value) 11379 { if (value == undefined) 11380 { value = this.value; 11381 } 11382 if (!this.is_style) 11383 { this.elem[this.prop] = value; 11384 } 11385 else if (this.prop.slice(0, 2) == '--') 11386 { this.elem.style.setProperty(this.prop, value); 11387 } 11388 else 11389 { this.elem.style[this.prop] = value; 11390 } 11391 } 11392 11393 stop_cur_transit() // if my elem has transition running, stop it 11394 { var {elem, prop} = this; 11395 var cur_transit = elem._ne__transit?.[prop]; 11396 if (cur_transit) 11397 { cur_transit.stop(prop); // stop current animation if any 11398 } 11399 } 11400} 11401class SetPropFromTo extends SetProp 11402{ constructor(set_prop) 11403 { super(set_prop.elem, set_prop.prop, set_prop.is_style, set_prop.value); 11404 this.at_step = null; // null - all steps, 0 - first, 1 - last 11405 this.format = set_prop.value; 11406 this.from_ns = null; 11407 this.to_ns = null; 11408 var that = this; 11409 set_from_to(); 11410 function set_from_to() 11411 { var p = that.prop; 11412 var elem = that.elem; 11413 that.at_step = null; 11414 var from=null, to=that.value; 11415 var cur_transit = elem._ne__transit?.[p]; 11416 if (!that.is_style) 11417 { from = elem[p]; 11418 } 11419 else 11420 { if (p == 'display') 11421 { that.at_step = to=='none' ? 1 : 0; // defer hiding to the end of animation, but apply show immediately 11422 if (cur_transit) cur_transit.stop(p, true); 11423 return; 11424 } 11425 else if (p == 'visibility') 11426 { that.at_step = to=='hidden' ? 1 : 0; 11427 return; 11428 } 11429 else 11430 { var e_style = elem.style; 11431 from = e_style[p]; 11432 if (from && (!cur_transit || !cur_transit.is_native_style) && try_set_from_to(from, to)) 11433 { return; 11434 } 11435 var e_comp_style = get_comp_style(elem); 11436 from = e_comp_style?.[p]; 11437 if (!from) 11438 { that.at_step = 0; 11439 if (is_node_linked(elem)) 11440 { log("Couldn't determine current style for "+p+" (needed for animation)", elem); 11441 } 11442 return; 11443 } 11444 if (cur_transit) cur_transit.stop(p); 11445 e_style[p] = to; // set new value to read it back computed 11446 to = e_comp_style[p]; 11447 e_style[p] = from; // back to current 11448 if (typeof(from)=='string' && from.charAt(3)=='(' && typeof(to)=='string' && to.charAt(4)=='(' && from.slice(0, 3)=='rgb' && to.slice(0, 4)=='rgba') 11449 { if (from.charAt(from.length - 1) == ')') from = 'rgba'+from.slice(3, -1)+', 1)'; 11450 } 11451 else if (typeof(to)=='string' && to.charAt(3)=='(' && typeof(from)=='string' && from.charAt(4)=='(' && to.slice(0, 3)=='rgb' && from.slice(0, 4)=='rgba') 11452 { if (to.charAt(to.length - 1) == ')') to = 'rgba'+to.slice(3, -1)+', 1)'; 11453 } 11454 } 11455 } 11456 if (!try_set_from_to(from, to)) 11457 { that.at_step = 0; 11458 /*if (!NEW_ELEM_NDEBUG) 11459 { if (from!=to && (/\b\d/.test(from) || /\b\d/.test(to))) 11460 { log('props_transit for '+p+': "from" and "to" points are not similar or no numbers to animate: "'+from+'" and "'+to+'"', elem); 11461 } 11462 }*/ 11463 } 11464 } 11465 function try_set_from_to(from, to) 11466 { if (from!=null && from!=to) 11467 { let from_ns = []; 11468 let to_ns = []; 11469 let from2 = (from+'').replace(RE_COMMA, ' , ').replace(RE_SPACES, ' ').replace(RE_NUMBER, n => {from_ns.push(parseFloat(n)); return '??'}); 11470 let to2 = (to +'').replace(RE_COMMA, ' , ').replace(RE_SPACES, ' ').replace(RE_NUMBER, n => { to_ns.push(parseFloat(n)); return '??'}); 11471 if (from_ns.length==to_ns.length && from_ns.length>0) 11472 { let format = from2.split('??'); 11473 let eq = from2 == to2; 11474 if (!eq) 11475 { var to_format = to2.split('??'); 11476 for (var i=0, i_end=format.length; i<i_end; i++) 11477 { if (format[i+1]) 11478 { if (from_ns[i] == 0) // 0px is the same as 0em 11479 { format[i+1] = to_format[i+1]; 11480 } 11481 else if (to_ns[i] == 0) 11482 { to_format[i+1] = format[i+1]; 11483 } 11484 } 11485 if (format[i] != to_format[i]) 11486 { break; 11487 } 11488 } 11489 eq = i == i_end; 11490 } 11491 if (eq) 11492 { that.format = format; 11493 that.from_ns = from_ns; 11494 that.to_ns = to_ns; 11495 that.at_step = null; 11496 return true; 11497 } 11498 } 11499 } 11500 that.at_step = 0; 11501 that.format = that.value; 11502 that.from_ns = null; 11503 that.to_ns = null; 11504 return false;
11505 } 11506 } 11507 11508 apply_value_for_step(n, timing_function_f, transit_only) 11509 { if (this.at_step == null) 11510 { var {from_ns, to_ns} = this; 11511 var text = ''; 11512 for (var i=0, i_end=from_ns.length; i<i_end; i++) 11513 { var v = n>=1 ? to_ns[i] : from_ns[i] + (to_ns[i] - from_ns[i]) * timing_function_f(n); 11514 if (v>1 || v<-1) 11515 { v |= 0; 11516 } 11517 text += this.format[i] + v; 11518 } 11519 if (this.format[i_end]) 11520 { text += this.format[i_end]; 11521 } 11522 this.apply_value(text); 11523 } 11524 else if (!transit_only && this.at_step == (n>=1 ? 1 : n)) 11525 { this.apply_value(this.format); 11526 } 11527 } 11528 11529 set_ne_state(stop, is_native_style) 11530 { if (this.at_step==null || this.at_step==1) 11531 { var {elem} = this; 11532 if (elem._ne__transit == null) 11533 { elem._ne__transit = {}; 11534 } 11535 else if ((!this.is_style || !is_native_style) && elem._ne__transit[this.prop] && elem._ne__transit[this.prop].stop!=stop) 11536 { elem._ne__transit[this.prop].stop(this.prop, true); // stop previous transition 11537 } 11538 elem._ne__transit[this.prop] = 11539 { stop, 11540 //is_style: this.is_style, 11541 is_native_style 11542 }; 11543 } 11544 } 11545 11546 clear_ne_state(stop) 11547 { var {elem, prop} = this; 11548 if (elem._ne__transit?.[prop]?.stop === stop) 11549 { delete elem._ne__transit[prop]; 11550 } 11551 } 11552} 11553function style_from_camel_case(str) 11554{ return str.replace(RE_FROM_CAMEL_1, '-$1').replace(RE_FROM_CAMEL_2, '$1-$2'); 11555} 11556function style_to_camel_case(str) 11557{ if (str.charAt(0)=='-' && str.charAt(1)=='-') 11558 { return str; 11559 } 11560 return str.replace(RE_TO_CAMEL, function(a, m) {return m.toUpperCase()}); 11561} 11562function css_text_to_obj(text) 11563{ var obj = {}; 11564 text.replace 11565 ( RE_CSS_RULE, 11566 (all, name, value) => 11567 { obj[style_to_camel_case(name)] = value.trim(); 11568 } 11569 ); 11570 return obj; 11571} 11572function props_transit(set_props, ontransitionend, ontransitionend_2, duration, fps, timing_function, emulate) 11573{ duration = duration>0 ? duration : 0.3; // secs 11574 fps = fps>0 && fps<=24 ? fps : 20; 11575 if (!timing_function) 11576 { timing_function = 'ease-in'; 11577 } 11578 var timing_function_f = null; 11579 switch (timing_function) 11580 { default: timing_function_f = x => x*x; // default is exponent 11581 } 11582 for (let i=0, i_end=set_props.length; i<i_end; i++) 11583 { if (set_props[i].is_style && set_props[i].prop=='display' && set_props[i].value!='none') 11584 { set_props[i].stop_cur_transit(); 11585 set_props[i].apply_value(); // apply immediately, so will be able to compute style 11586 } 11587 } 11588 var for_transit = []; 11589 var first_transit = null; 11590 for (let i=0, i_end=set_props.length; i<i_end; i++) 11591 { let set_prop_from_to = new SetPropFromTo(set_props[i]); 11592 if (set_prop_from_to.at_step == null) 11593 { first_transit = set_prop_from_to; 11594 if (!set_prop_from_to.is_style) 11595 { emulate = true; 11596 } 11597 } 11598 for_transit.push(set_prop_from_to); 11599 } 11600 var transit_name, event_name; 11601 if (first_transit && !emulate) 11602 { let e_style = first_transit.elem.style; 11603 if 11604 ( CSS_PREFIX && (transit_name=CSS_PREFIX+'Transition') && e_style[transit_name]!=null && (event_name=CSS_PREFIX_LC+'TransitionEnd') || 11605 (transit_name='transition') && e_style[transit_name]!=null && (event_name='transitionend') 11606 ) 11607 { // ok, have native support 11608 } 11609 else 11610 { emulate = true; 11611 } 11612 } 11613 function set_transit_style(for_transit) 11614 { var prev_elem = null; 11615 for (var i=0, i_end=for_transit.length; i<i_end; i++) 11616 { var elem = for_transit[i].elem; 11617 if (elem != prev_elem) 11618 { var all = []; 11619 for (var p in elem._ne__transit) 11620 { if (elem._ne__transit[p].is_native_style && p!='display' && p!='visibility') 11621 { all.push(p+' '+duration+'s '+timing_function); 11622 } 11623 } 11624 elem.style[transit_name] = style_from_camel_case(all.join(', ')); 11625 prev_elem = elem; 11626 } 11627 } 11628 } 11629 var call_ontransitionend = pair_funcs(ontransitionend, ontransitionend_2); 11630 // stop 11631 function stop(prop, for_reapply) 11632 { var has_for_transit = false; 11633 for (var i=0, i_end=for_transit.length; i<i_end; i++) 11634 { if (for_transit[i].prop == prop) 11635 { if (!for_reapply) 11636 { for_transit[i].clear_ne_state(stop); 11637 if (!emulate) set_transit_style(for_transit); // clear "transition" style 11638 } 11639 for_transit.splice(i--, 1); 11640 i_end--; 11641 } 11642 else if (for_transit[i].at_step == null) 11643 { has_for_transit = true; 11644 } 11645 } 11646 if (!has_for_transit) 11647 { for (var i=0; i<i_end; i++) 11648 { for_transit[i].apply_value_for_step(1, timing_function_f); 11649 } 11650 for_transit.splice(0, i_end); 11651 if (call_ontransitionend) call_ontransitionend(); 11652 } 11653 } 11654 // step 11655 var step = null; 11656 if (!first_transit) 11657 { for (let i=0, i_end=for_transit.length; i<i_end; i++) 11658 { for_transit[i].stop_cur_transit(); 11659 for_transit[i].apply_value(); 11660 } 11661 if (call_ontransitionend) 11662 { deferred(call_ontransitionend); 11663 } 11664 return; 11665 } 11666 if (!emulate) 11667 { // native 11668 step = function() 11669 { var done = false;
11670 function func() 11671 { if (!done) 11672 { done = true; 11673 remove(); 11674 for (let i=0, i_end=for_transit.length; i<i_end; i++) 11675 { let set_prop = for_transit[i]; 11676 set_prop.clear_ne_state(stop); 11677 set_transit_style(for_transit); // clear "transition" style 11678 if (!set_prop.elem._ne__transit || !set_prop.elem._ne__transit[set_prop.prop]) 11679 { set_prop.apply_value(); // set final value 11680 } 11681 } 11682 if (call_ontransitionend) 11683 { call_ontransitionend(); 11684 } 11685 } 11686 }; 11687 var remove = add_event(null, first_transit.elem, event_name, func); 11688 deferred 11689 ( () => 11690 { set_transit_style(for_transit); 11691 deferred 11692 ( () => 11693 { for (let i=0, i_end=for_transit.length; i<i_end; i++) 11694 { for_transit[i].apply_value_for_step(1, null, true); 11695 } 11696 deferred(func, duration*1000+20); // ensure ontransitionend call 11697 }, 11698 navigator.userAgent.indexOf('Gecko/')!=-1 ? 80 : 0 // Gecko doesn't play animation if "transition" style had been set too recently 11699 ); 11700 } 11701 ); 11702 }; 11703 } 11704 else 11705 { // emulated 11706 let n = 0; 11707 let inc = 1 / (duration * fps); 11708 step = function() 11709 { n += inc; 11710 for (var i=0, i_end=for_transit.length; i<i_end; i++) 11711 { for_transit[i].apply_value_for_step(n, timing_function_f); 11712 } 11713 if (n >= 1) 11714 { for (var i=0; i<i_end; i++) 11715 { for_transit[i].clear_ne_state(stop); 11716 } 11717 if (call_ontransitionend) call_ontransitionend(); 11718 } 11719 else 11720 { deferred(step, 1000/fps); // 1sec / fps 11721 } 11722 }; 11723 } 11724 // set state to elem._ne__transit 11725 for (let i=0, i_end=for_transit.length; i<i_end; i++) 11726 { for_transit[i].set_ne_state(stop, !emulate); 11727 } 11728 step(); // run first step 11729 return stop; 11730} 11731function WinDoc(props, node) 11732{ this.doc = props?.document || props?.window?.document || node?.ownerDocument || document; 11733 this.win = props?.window || this.doc.defaultView || this.doc.parentWindow || window; 11734} 11735function get_comp_style(elem, prop, default_value) 11736{ var comp_style; 11737 if (elem?.nodeType === XML_ELEMENT_NODE) 11738 { comp_style = elem.currentStyle; 11739 if (!comp_style) 11740 { let doc = elem.ownerDocument; 11741 let win = doc?.defaultView || window; 11742 if (win.getComputedStyle) 11743 { try 11744 { comp_style = win.getComputedStyle(elem, null); 11745 } 11746 catch (e) 11747 { error(e); 11748 } 11749 } 11750 } 11751 } 11752 return !prop ? comp_style || default_value || {} : comp_style?.[prop]!=null ? comp_style[prop] : default_value; 11753} 11754function add_event(win_doc, elem, onwhat, callback, capture, traditional) 11755{ // set callback2 to instrumented callback (based on http://www.quirksmode.org/js/events_properties.html) 11756 var callback2; 11757 if (IS_SAFARI) 11758 { callback2 = function(e) 11759 { if (e?.target?.nodeType == XML_TEXT_NODE) 11760 { e.target = e.target.parentNode; 11761 } 11762 return callback.call(this, e); 11763 }; 11764 } 11765 else 11766 { callback2 = callback; 11767 } 11768 // add event listener 11769 if (traditional) 11770 { elem[onwhat] = callback2; 11771 } 11772 else 11773 { if (onwhat.slice(0, 2) == 'on') 11774 { onwhat = onwhat.slice(2); 11775 } 11776 elem.addEventListener(onwhat, callback2, !!capture); 11777 return function() 11778 { elem.removeEventListener(onwhat, callback2, !!capture); 11779 }; 11780 } 11781} 11782function add_style(elem, style, set_props, idx) 11783{ if (!elem || !style) return; 11784 if (set_props && !idx) 11785 { idx = {}; 11786 } 11787 if (!elem.cloneNode) 11788 { if (Array.isArray(elem)) 11789 { for (let e of elem) 11790 { add_style(e, style, set_props, idx); 11791 } 11792 } 11793 return; 11794 } 11795 if (typeof(style) == 'string') 11796 { style = css_text_to_obj(style); 11797 } 11798 if (Array.isArray(style)) 11799 { for (let s of style) 11800 { add_style(elem, s, set_props, idx); 11801 } 11802 } 11803 else if (typeof(style) == 'object') 11804 { if (style.length != null) 11805 { // assume: elem.style object 11806 if (style != elem.style) 11807 { for (let i=0, i_end=style.length; i<i_end; i++) 11808 { add_style_prop(elem, style[i], style[style[i]], set_props, idx); 11809 } 11810 } 11811 } 11812 else 11813 { for (let name of Object.keys(style)) 11814 { add_style_prop(elem, name, style[name], set_props, idx); 11815 } 11816 } 11817 } 11818} 11819function add_style_prop(elem, name, value, set_props, idx) 11820{ var prefixed = prefix_css_prop(name) || name; 11821 if (!set_props) 11822 { elem._ne__transit?.[prefixed]?.stop(prefixed); // stop current animation if any 11823 let is_var = prefixed.slice(0, 2) == '--'; 11824 if (!Array.isArray(value)) 11825 { try 11826 { if (is_var) 11827 { elem.style.setProperty(prefixed, value); 11828 } 11829 else 11830 { elem.style[prefixed] = value; 11831 } 11832 } 11833 catch (e) 11834 { error(e); 11835 } 11836 } 11837 else 11838 { for (let i=0, i_end=value.length; i<i_end; i++) 11839 { try 11840 { if (is_var) 11841 { elem.style.setProperty(prefixed, value[i]); 11842 } 11843 else 11844 { elem.style[prefixed] = value[i]; 11845 } 11846 } 11847 catch (e) 11848 { error(e); 11849 } 11850 } 11851 } 11852 } 11853 else if (prefixed in idx) 11854 { set_props[idx[prefixed]] = new SetProp(elem, prefixed, true, value); 11855 } 11856 else 11857 { idx[prefixed] = set_props.length; 11858 set_props.push(new SetProp(elem, prefixed, true, value)); 11859 } 11860} 11861function prefix_css_prop(prop) 11862{ if (CSS_PREFIX) 11863 { let prefixed = CSS_PREFIX+prop.charAt(0).toUpperCase()+prop.slice(1); 11864 if (multipurpose_div?.style[prefixed] !== undefined) 11865 { return prefixed; 11866 } 11867 } 11868 for (let from of Object.keys(CSS_DEFAULT_REPLACE)) 11869 { prop = prop.replace(from, CSS_DEFAULT_REPLACE[from]); 11870 } 11871 if (multipurpose_div?.style[prop] !== undefined) 11872 { return prop; 11873 } 11874 return null; 11875} 11876function attr_add_to_hash(ns, elem, attrs, name, value, no_data, no_regular, process_data) 11877{ if (!(name in attrs)) 11878 { var prefix_len = name.slice(0, 5)!='data-' ? 0 : name.slice(5, 10)=='json-' || name.slice(5, 10)=='eval-' ? 10 : 5;
11878 // 'eval-' is needed for compatibility with old code 11879 if (prefix_len==0 ? no_regular : no_data) 11880 { return; 11881 } 11882 if (process_data) 11883 { if (prefix_len == 10) 11884 { try 11885 { value = JSON.parse(value); 11886 } 11887 catch (e) 11888 { value = undefined; 11889 error(e); 11890 } 11891 } 11892 name = name.slice(prefix_len); 11893 } 11894 attrs[name] = value; 11895 } 11896} 11897/** @param {Object} ns 11898 @param {Object} object to populate (usually empty). This object will be returned. 11899 @param {DOMElement} elem 11900 @param {bool} no_data 11901 @param {bool} no_regular 11902 @param {bool} process_data - unprefix data attributes, and JSON-decode if 'data-json-' 11903 **/ 11904function get_attributes(ns, attrs, elem, no_data, no_regular, process_data) 11905{ for (let a=elem.attributes.length-1; a>=0; a--) 11906 { let attr = elem.attributes.item(a); 11907 attr_add_to_hash(ns, elem, attrs, attr.name, attr.value, no_data, no_regular, process_data); 11908 } 11909 return attrs; 11910} 11911function add_attributes(elem, attrs, attrs_exclude, remove_with_data, merge_style) 11912{ if (attrs.nodeType == XML_ELEMENT_NODE) 11913 { attrs = get_attributes(null, {}, attrs); // to copy without 'data-' apply get_attributes() by yourself 11914 } 11915 for (let a in attrs) 11916 { if (!(a in attrs_exclude)) 11917 { try 11918 { let value = attrs[a]; 11919 if (value !== undefined) 11920 { if (value === null) 11921 { elem.removeAttribute(a); 11922 if (remove_with_data) 11923 { elem.removeAttribute('data-'+a); 11924 elem.removeAttribute('data-json-'+a); 11925 } 11926 } 11927 else if (a=='style' && (merge_style || typeof(value)=='object')) 11928 { add_style(elem, value); 11929 } 11930 else 11931 { elem.setAttribute(a, value); 11932 } 11933 } 11934 } 11935 catch (e) 11936 { error(e); 11937 } 11938 } 11939 } 11940} 11941/* ---------------------------------- TILL HERE --------------------------------*/ 11942function is_loaded(win_doc) 11943{ let {doc} = win_doc; 11944 try 11945 { // on some sites it's seen that "doc.readyState" is intentionally overridden to prevent detecting that document is loaded, but the original getter is still available in Document.prototype 11946 var readyState = Object.getOwnPropertyDescriptor(Document.prototype, 'readyState').get.call(doc); 11947 } 11948 catch 11949 { readyState = doc.readyState; 11950 } 11951 return ('(uninitialized|loading)').indexOf(readyState) == -1; 11952} 11953function transit_set_begin(transit, ontransitionend) 11954{ var autoplay = ontransitionend || do_nothing; 11955 if (transit) 11956 { autoplay = do_nothing; 11957 if (ontransitionend) 11958 { transit.ontransitionend = pair_funcs(transit.ontransitionend, ontransitionend); 11959 } 11960 if (!('play' in transit)) 11961 { autoplay = function(callback) 11962 { transit.play(callback); 11963 delete transit.play; 11964 }; 11965 } 11966 if (!transit.play) 11967 { var set_props = []; 11968 transit.play = function(callback_or_get_var) 11969 { if (callback_or_get_var === true) 11970 { return set_props; 11971 } 11972 pcall(props_transit, null, [set_props, transit.ontransitionend, callback_or_get_var, transit.duration, transit.fps, transit.timing_function, transit.emulate]); 11973 }; 11974 } 11975 } 11976 return autoplay; 11977} 11978/** @param {WinDoc} win_doc 11979 @param {Object} ns 11980 @param {DOMElement} elem 11981 @param {Object} props - Can contain magic props: "attributes", "onready". 11982 @param {number} reapply_level - if 0 - no reapply, 1 - yes, 2 - force. 11983 @param {Object} props_exclude - Only keys matter. E.g. to ignore props.size, props_exclude can be {size:true}. 11984 @param {string} elem_id - for internal use from new_elem(). 11985 @param {Object} onready_queue - for internal use from new_elem(). 11986 @param {Object} transit - for internal use from new_elem(). 11987 @return {any} - If "elem" hasn't changed, returns undefined. Otherwise returns the changed "elem". 11988 **/ 11989function add_props(win_doc, ns, elem, props, reapply_level, props_exclude, elem_id, onready_queue, transit, with_ontplevents, stack) 11990{ reapply_level |= 0; 11991 var has_changes = false; 11992 if (!elem || (!elem.cloneNode && elem.window!=elem)) // "elem" is not a DOMNode or is a Window 11993 { if (typeof(props)!='object' || props==null || props.cloneNode) // if "props" is scalar or a Node 11994 { if (!equals_shallow(elem, props)) 11995 { elem = props; 11996 has_changes = true; 11997 } 11998 } 11999 else // "props" is object, but not a Node 12000 { if (typeof(elem)!='object' || elem==null) 12001 { elem = Array.isArray(props) ? [] : {}; 12002 has_changes = true; 12003 } 12004 if (!stack) 12005 { stack = [elem]; 12006 } 12007 let tos = stack.length; 12008 if (Array.isArray(props)) 12009 { if (elem.length != props.length) 12010 { has_changes = true; 12011 if (elem.length > props.length) 12012 { elem.length = props.length; 12013 } 12014 } 12015 for (let p=0, p_end=props.length; p<p_end; p++) 12016 { let elem_p = elem[p]; 12017 if (typeof(elem_p)!='object' || elem_p==null) 12018 { let props_p = props[p]; 12019 if (!has_changes) 12020 { has_changes = !equals_shallow(elem_p, props_p); 12021 } 12022 elem[p] = props_p; 12023 } 12024 else if (stack.indexOf(elem_p) == -1) 12025 { stack[tos] = elem_p; 12026 let result = add_props(win_doc, ns, elem_p, props[p], reapply_level, null, elem_id, onready_queue, transit, with_ontplevents, stack); // props_exclude excludes only on top level 12027 if (result !== undefined) 12028 { elem[p] = result; 12029 has_changes = true; 12030 } 12031 } 12032 } 12033 } 12034 else 12035 { if (!props_exclude) props_exclude = {}; // by default exclude Object.prototype 12036 for (let p in props) 12037 { if (!(p in props_exclude)) 12038 { let elem_p = elem[p]; 12039 if (typeof(elem_p)!='object' || elem_p==null) 12040 { let props_p = props[p]; 12041 if (!has_changes) 12042 { has_changes = !equals_shallow(elem_p, props_p); 12043 } 12044 elem[p] = props_p; 12045 } 12046 else if (stack.indexOf(elem_p) == -1) 12047 { stack[tos] = elem_p; 12048 let result = add_props(win_doc, ns, elem_p, props[p], reapply_level, null, elem_id, onready_queue, transit, with_ontplevents, stack); // props_exclude excludes only on top level 12049 if (result !== undefined) 12050 { elem[p] = result; 12051 has_changes = true; 12052 } 12053 } 12054 } 12055 } 12056 } 12057 if (stack.length > tos) 12058 { stack.pop(); 12059 } 12060 } 12061 } 12062 else if (elem._reapply_template && reapply_level) 12063 { elem = elem._reapply_template(props, false, reapply_level<2, transit); 12064 } 12065 else if (props?.cloneNode) 12066 { if (!equals_shallow(elem, props)) 12067 { elem = props; 12068 has_changes = true; 12069 } 12070 } 12071 else if (typeof(props)=='object' && props!=null) 12072 { if (!props_exclude) props_exclude = {}; // by default exclude Object.prototype 12073 let set_props = !transit ? null : transit.play?.(true) || []; 12074 for (let p in props) 12075 { if (!(p in props_exclude) && p!='window' && p!='document' && p!='no_remove_ids') 12076 { let value = props[p]; 12077 if (value != null) 12078 { has_changes = true; 12079 if (p==='options' && value && value.length!=null && add_options(ns, elem, value)) 12080 { continue; 12081 } 12082 if (p==='innerHTML' || p==='childNodes') 12083 { let e; 12084 if (p === 'innerHTML') 12085 { elem_empty(elem); 12086 } 12087 if (value.cloneNode) 12088 { elem.appendChild(value); 12089 } 12090 else if (typeof(value)!='object' && !(IS_SAFARI && typeof(value)=='function' && typeof(value.item)=='function')) // on Safari 5.1.7 typeof(document.body.childNodes)=='function' 12091 { value = value.toString(); 12092 let level = 0; 12093 let pos = value.indexOf('>'); 12094 if (pos != -1) 12095 { let match = value.slice(0, pos).match(RE_FIRST_TAG); 12096 if (match) 12097 { switch (match[1].toLowerCase()) 12098 { case 'th': 12099 case 'td': 12100 value = '<tr>'+value+'</tr>'; 12101 level++; 12102 case 'tr': 12103 value = '<tbody>'+value+'</tbody>'; 12104 level++; 12105 case 'caption': 12106 case 'colgroup': 12107 case 'thead': 12108 case 'tbody': 12109 case 'tfoot': 12110 value = '<table>'+value+'</table>'; 12111 level++; 12112 } 12113 } 12114 } 12115 if (level==0 && p==='innerHTML') 12116 { elem.innerHTML = value; 12117 } 12118 else 12119 { let div = win_doc.doc.createElement('div'); 12120 div.innerHTML = value; 12121 while (level-- > 0) 12122 { e = get_root_child(div); 12123 if (!e) break; 12124 div = e; 12125 } 12126 let fc; 12127 while ((fc = div.firstChild)) 12128 { elem.appendChild(fc); 12129 } 12130 } 12131 } 12132 else // assume: value is array-like object 12133 { let ins_before = null; // next sibling 12134 for (let i=value.length-1; i>=0; i--) // safe to run in reverse order, so even if "value" is a NodeList or a DocumentFragment that unlinks it's children while i move them to another parent, value[i] will always exist 12135 { e = value[i]; 12136 if (e!=null && e!=='') 12137 { if (!e.cloneNode) 12138 { e = win_doc.doc.createTextNode(e); 12139 } 12140 elem.insertBefore(e, ins_before); 12141 ins_before = e;
12141 // now e can != value[i] if value[i] was unlinked 12142 } 12143 } 12144 } 12145 } 12146 else if (p === 'style') 12147 { add_style(elem, value, set_props); 12148 } 12149 else if (p === 'attributes') 12150 { add_attributes(elem, value, {}, true); 12151 } 12152 else if (p==='className' && value=='') 12153 { elem.className = ''; 12154 elem.removeAttribute('class'); 12155 } 12156 else 12157 { if (p.slice(0, 2) == 'on') 12158 { if (typeof(value) == 'string') 12159 { value = str_to_func(value, ns); 12160 } 12161 if (typeof(value) == 'function') 12162 { if (p.slice(0, 5) == 'ontpl') 12163 { if (p === 'ontplready') 12164 { if (elem!=win_doc.doc || is_loaded(win_doc)) 12165 { if (onready_queue) 12166 { onready_queue.add_onready(value, elem); 12167 } 12168 else 12169 { value.call(elem); 12170 } 12171 } 12172 else 12173 { let func_elem = elem; 12174 function call_func() 12175 { if (value != null) 12176 { value.call(func_elem); 12177 value = null; 12178 func_elem = null; 12179 } 12180 } 12181 let reg; 12182 if (win_doc.doc.addEventListener || win_doc.win.addEventListener && win_doc.doc.attachEvent) 12183 { reg = add_event(win_doc, win_doc.doc, 'DOMContentLoaded', () => {reg(); call_func()}, false); 12184 } 12185 else 12186 { (reg = () => {is_loaded(win_doc) ? call_func() : deferred(reg, 400)})(); 12187 } 12188 let prev_onload = win_doc.win.onload; 12189 win_doc.win.onload = function() 12190 { if (prev_onload) 12191 { prev_onload.call(this); 12192 } 12193 call_func(); 12194 win_doc.win.onload = null; 12195 }; 12196 } 12197 continue; 12198 } 12199 if (!with_ontplevents) continue; 12200 } 12201 add_event(win_doc, elem, p, value, false, true); 12202 continue; 12203 } 12204 else if (typeof(value.func) == 'function') 12205 { ( function(elem, p, func, capture, listeners, till_node_linked) 12206 { var func_to_add = func; 12207 if (till_node_linked) 12208 { func_to_add = function(event) 12209 { if (till_node_linked && !is_node_linked(till_node_linked, true)) 12210 { remove_full(); 12211 remove = do_nothing; 12212 return; 12213 } 12214 return func.call(this, event); 12215 }; 12216 } 12217 var remove = add_event(win_doc, elem, p, func_to_add, capture); 12218 var remove_full = remove; 12219 if (listeners) 12220 { var my_listeners; 12221 if (!elem_id) 12222 { my_listeners = listeners; 12223 } 12224 else 12225 { if (!listeners[elem_id]) 12226 { listeners[elem_id] = {}; 12227 } 12228 my_listeners = listeners[elem_id]; 12229 } 12230 if (my_listeners[p]) 12231 { my_listeners[p].remove(); 12232 } 12233 my_listeners[p] = 12234 { remove: function() 12235 { remove_full(); 12236 remove = do_nothing; 12237 }, 12238 add: function() 12239 { remove_full(); 12240 remove = add_event(win_doc, elem, p, func_to_add, capture); 12241 } 12242 }; 12243 } 12244 } 12245 )(elem, p, value.func, value.capture, value.listeners, value.till_node_linked); 12246 continue; 12247 } 12248 } 12249 if (!set_props || elem[p]==value) // assign even if equal 12250 { if (p.charAt(0) == '_') 12251 { elem[p] = value; 12252 } 12253 else if (p in elem) 12254 { try 12255 { elem._ne__transit?.[p]?.stop(p); // stop current animation if any 12256 elem[p] = value; 12257 if (elem[p]!=value && p!='value') 12258 { elem.setAttribute(p, value); 12259 } 12260 } 12261 catch (e) 12262 { try {elem.setAttribute(p, value)} catch (e) {} 12263 } 12264 } 12265 } 12266 else 12267 { set_props.push(new SetProp(elem, p, false, value)); 12268 } 12269 } 12270 } 12271 } 12272 } 12273 } 12274 if (has_changes) 12275 { return elem; 12276 } 12277} 12278/** @return viewport dimensions 12279 **/ 12280function get_vp_dim(win) 12281{ var doc = win.document; 12282 var body = doc.body; 12283 var html = doc.documentElement; 12284 var de = doc.compatMode == 'BackCompat' ? body : html; // scrolling/viewport element 12285 return { 12286 left: body?.scrollLeft || win.pageXOffset || win.scrollX || html?.scrollLeft || 0, 12287 top: body?.scrollTop || win.pageYOffset || win.scrollY || html?.scrollTop || 0, 12288 width: de?.clientWidth || win.innerWidth || 0, 12289 height: de?.clientHeight || win.innerHeight || 0 // body.clientHeight can be affected by styles on <body>, win.innerHeight includes scrollbar and is not affected by zooming 12290 }; 12291} 12292function getBoundingClientRect(win, elem) 12293{ if (elem.nodeType == XML_TEXT_NODE) 12294 { if (win.document.createRange) 12295 { let range = win.document.createRange(); 12296 if (range.selectNodeContents && range.getBoundingClientRect) 12297 { range.selectNodeContents(elem); 12298 return range.getBoundingClientRect(); // can be null if elem is invisible 12299 } 12300 } 12301 } 12302 else if (win.getComputedStyle(elem).display == 'contents') 12303 { let left, right, top, bottom; 12304 for (const child of elem.childNodes) 12305 { let rect = getBoundingClientRect(win, child); 12306 if (rect && rect.width>0 && rect.height>0) 12307 { if (left == null || rect.left < left) left = rect.left; 12308 if (right == null || rect.right > right) right = rect.right; 12309 if (top == null || rect.top < top) top = rect.top; 12310 if (bottom == null || rect.bottom > bottom) bottom = rect.bottom; 12311 } 12312 } 12313 if (left != null) 12314 { return {left, right, top, bottom, width: right-left, height: bottom-top}; 12315 } 12316 } 12317 else 12318 { return elem.getBoundingClientRect?.(); 12319 } 12320} 12321function get_dim(elem, box_sizing) 12322{ if (elem.window==elem && elem.document) // if is "window" object 12323 { return get_vp_dim(elem); 12324 } 12325 var doc = elem.ownerDocument || document; 12326 var body = doc.body; 12327 var client_rect = getBoundingClientRect(doc.defaultView || window, elem); 12328 var is_text_node = elem.nodeType == XML_TEXT_NODE; 12329 if (client_rect) 12330 { var vp_dim = get_vp_dim(doc.defaultView || window); 12331 var left = client_rect.left + vp_dim.left; 12332 var top = client_rect.top + vp_dim.top; 12333 var width = client_rect.width; 12334 var height = client_rect.height; 12335 } 12336 else 12337 { if (is_text_node) 12338 { return {left: 0, top: 0, width: 0, height: 0}; 12339 } 12340 var e = elem; 12341 var e_topmost = e; 12342 left = e.offsetLeft; 12343 top = e.offsetTop; 12344 if (get_comp_style(e, 'position', 'static') == 'static') 12345 { var offset_parent = e.offsetParent; 12346 while ((e = e.parentNode) && (e_topmost = e)!==body) 12347 { if (e == offset_parent) 12348 { left += e.offsetLeft; 12349 top += e.offsetTop; 12350 if (get_comp_style(e, 'position', 'static') != 'static') break; 12351 offset_parent = e.offsetParent; 12352 } 12353 left -= e.scrollLeft || 0; 12354 top -= e.scrollTop || 0; 12355 } 12356 } 12357 if (e && e!==body) 12358 { while ((e = e.offsetParent) && (e_topmost = e)!==body) 12359 { left += e.offsetLeft; 12360 top += e.offsetTop; 12361 } 12362 } 12363 if (e_topmost !== body) 12364 { e = e_topmost; 12365 var s = get_comp_style(e); 12366 if (s.position == 'fixed') 12367 { if (!e.offsetLeft && !e.offsetTop) 12368 { var s_left = parseFloat(s.left); 12369 var s_top = parseFloat(s.top); 12370 var vp_dim; 12371 if (isNaN(s_left)) 12372 { var s_right = parseFloat(s.right); 12373 if (!isNaN(s_right)) 12374 { vp_dim = get_vp_dim(doc.defaultView || window); 12375 s_left = vp_dim.width - s_right - e.offsetWidth; 12376 } 12377 } 12378 if (isNaN(s_top)) 12379 { var s_bottom = parseFloat(s.bottom); 12380 if (!isNaN(s_bottom)) 12381 { vp_dim = vp_dim || get_vp_dim(doc.defaultView || window); 12382 s_top = vp_dim.height - s_bottom - e.offsetHeight; 12383 } 12384 } 12385 if (!isNaN(s_left)) left += s_left + (parseFloat(s.marginLeft) || 0); // offsetLeft and offsetTop don't count margin 12386 if (!isNaN(s_top)) top += s_top + (parseFloat(s.marginTop) || 0); 12387 } 12388 left += body.scrollLeft || doc.documentElement?.scrollLeft || 0; 12389 top += body.scrollTop || doc.documentElement?.scrollTop || 0; 12390 } 12391 } 12392 width = elem.offsetWidth; 12393 height = elem.offsetHeight; 12394 } 12395 // zoom 12396 for (e=elem; e && e!==body; e=e.parentNode) 12397 { var zoom = parseFloat(get_comp_style(e, 'zoom')); 12398 if (zoom > 0) 12399 { if (IS_MSIE) zoom /= 100; 12400 left *= zoom; 12401 top *= zoom; 12402 width *= zoom; 12403 height *= zoom; 12404 } 12405 } 12406 // box_sizing 12407 if (!is_text_node && (box_sizing=='padding-box' || box_sizing=='content-box')) 12408 { var s = get_comp_style(elem); 12409 // left, width 12410 var add = parseFloat(s.borderLeftWidth); 12411 left += add || 0; 12412 add += parseFloat(s.borderRightWidth); 12413 width -= add || 0; 12414 // top, height 12415 add = parseFloat(s.borderTopWidth); 12416 top += add || 0; 12417 add += parseFloat(s.borderBottomWidth); 12418 height -= add || 0; 12419 if (box_sizing == 'content-box') 12420 { add = parseFloat(s.paddingLeft); 12421 left += add || 0; 12422 add += parseFloat(s.paddingRight); 12423 width -= add || 0; 12424 // top, height 12425 add = parseFloat(s.paddingTop); 12426 top += add || 0; 12427 add += parseFloat(s.paddingBottom); 12428 height -= add || 0; 12429 } 12430 } 12431 return {left: left, top: top, width: width, height: height}; 12432} 12433function get_set_dim_style(ns, elem, dim, box_sizing) 12434{ var style = {}; 12435 var top = parseFloat(dim.top); 12436 var bottom = parseFloat(dim.bottom); 12437 var left = parseFloat(dim.left); 12438 var right = parseFloat(dim.right); 12439 var width = parseFloat(dim.width); 12440 var height = parseFloat(dim.height); 12441 var s = get_comp_style(elem); 12442 if (!box_sizing) 12443 { box_sizing = 'border-box'; 12444 } 12445 var target_box_sizing = s.boxSizing || 'content-box'; 12446 if (!isNaN(top) || !isNaN(bottom) || !isNaN(left) || !isNaN(right)) 12447 { if (box_sizing=='padding-box' || box_sizing=='content-box') 12448 { top -= parseFloat(s.borderTopWidth) || 0; 12449 bottom -= parseFloat(s.borderBottomWidth) || 0; 12450 left -= parseFloat(s.borderLeftWidth) || 0; 12451 right -= parseFloat(s.borderRightWidth) || 0; 12452 if (box_sizing == 'content-box') 12453 { top -= parseFloat(s.paddingTop) || 0; 12454 bottom -= parseFloat(s.paddingBottom) || 0; 12455 left -= parseFloat(s.paddingLeft) || 0; 12456 right -= parseFloat(s.paddingRight) || 0; 12457 } 12458 } 12459 var e = elem; 12460 var body = (e.ownerDocument || document).body; 12461 var vp_dim = null; 12462 while ((e = e.parentNode) && e!==body) 12463 { var cs = get_comp_style(e); 12464 if (cs.position && cs.position!='static' || cs.perspective && cs.perspective!='none') 12465 { var dim = get_dim(e); 12466 top -= dim.top - e.scrollTop; 12467 left -= dim.left - e.scrollLeft; 12468 if (!isNaN(bottom) || !isNaN(right)) 12469 { if (!vp_dim) 12470 { vp_dim = get_dim(new WinDoc(elem).win); 12471 }
12472 bottom += (dim.top + dim.height) - (vp_dim.height); 12473 right += (dim.left + dim.width) - (vp_dim.width); // right: not tested 12474 } 12475 break; 12476 } 12477 } 12478 if (!isNaN(top)) 12479 { style.top = top + 'px'; 12480 style.bottom = 'auto'; 12481 } 12482 else if (!isNaN(bottom)) 12483 { style.top = 'auto'; 12484 style.bottom = bottom + 'px'; 12485 } 12486 if (!isNaN(left)) 12487 { style.left = left + 'px'; 12488 style.right = 'auto'; 12489 } 12490 else if (!isNaN(right)) 12491 { style.left = 'auto'; 12492 style.right = right + 'px'; 12493 } 12494 } 12495 if (!isNaN(width) || !isNaN(height)) 12496 { if (target_box_sizing != box_sizing) 12497 { var padding_height = (parseFloat(s.paddingTop) + parseFloat(s.paddingBottom)) || 0; 12498 var padding_width = (parseFloat(s.paddingLeft) + parseFloat(s.paddingRight)) || 0; 12499 var border_height = (parseFloat(s.borderTopWidth) + parseFloat(s.borderBottomWidth)) || 0; 12500 var border_width = (parseFloat(s.borderLeftWidth) + parseFloat(s.borderRightWidth)) || 0; 12501 if (box_sizing=='border-box' || box_sizing=='padding-box') 12502 { height -= padding_height; 12503 width -= padding_width; 12504 if (box_sizing == 'border-box') 12505 { height -= border_height; 12506 width -= border_width; 12507 } 12508 } 12509 if (target_box_sizing != 'content-box') 12510 { height += padding_height; 12511 width += padding_width; 12512 if (target_box_sizing != 'padding-box') 12513 { height += border_height; 12514 width += border_width; 12515 } 12516 } 12517 } 12518 if (width < 0) width = 0; 12519 if (height < 0) height = 0; 12520 if (!isNaN(width)) style.width = width+'px'; 12521 if (!isNaN(height)) style.height = height+'px'; 12522 } 12523 return style; 12524} 12525function set_dim(ns, elem, dim, transit, box_sizing) 12526{ if (elem.window==elem && elem.document) // if is "window" object 12527 { var doc = elem.document; 12528 var {body} = doc; 12529 var html = doc.documentElement; 12530 if (body && html) 12531 { var props = {}; 12532 if (dim.top != null) props.scrollTop = dim.top; 12533 if (dim.left != null) props.scrollLeft = dim.left; 12534 var transit_set_end = transit_set_begin(transit); 12535 if (!html.scrollTop) 12536 { add_props(new WinDoc(elem), ns, body, props, 0, null, null, null, transit); 12537 } 12538 if (!body.scrollTop) 12539 { add_props(new WinDoc(elem), ns, html, props, 0, null, null, null, transit); 12540 } 12541 transit_set_end(); 12542 } 12543 return; 12544 } 12545 var style = get_set_dim_style(ns, elem, dim, box_sizing); 12546 new NewElem(ns, elem).add_style(style, transit); 12547} 12548function scroll_into_view(ns, elem, transit, scroll_into_view_options) 12549{ if (!transit) 12550 { elem.scrollIntoView(); 12551 } 12552 else 12553 { var to_end = scroll_into_view_options ? scroll_into_view_options.block=='end' : false; 12554 var win_doc = new WinDoc(elem); 12555 var e = elem; 12556 var left = e.offsetLeft; 12557 var top = e.offsetTop; 12558 var width = e.offsetWidth; 12559 var height = e.offsetHeight; 12560 var body = document.body; 12561 var p, offset; 12562 var transit_set_end = transit_set_begin(transit); 12563 while ((p = e.offsetParent)) 12564 { var client_width = p.clientWidth; 12565 var client_height = p!=body ? p.clientHeight : Math.min(document.documentElement.clientHeight, window.innerHeight); 12566 if (client_width<p.scrollWidth-1 && ((offset=left-p.scrollLeft)<0 || (offset>0 && width>client_width) || (offset=left+width-p.scrollLeft-client_width)>0)) 12567 { add_props(win_doc, ns, p, {scrollLeft: p.scrollLeft+offset}, 0, null, null, null, transit); 12568 } 12569 if (client_height<p.scrollHeight-1 && ((offset=top-p.scrollTop)<0 || (offset>0 && height>client_height && !to_end) || (offset=top+height-p.scrollTop-client_height)>0)) 12570 { add_props(win_doc, ns, p, {scrollTop: p.scrollTop+offset}, 0, null, null, null, transit); 12571 } 12572 e = p; 12573 left += e.offsetLeft; 12574 top += e.offsetTop; 12575 } 12576 transit_set_end(); 12577 } 12578} 12579var deferred_q = null; 12580var deferred_q_for = {}; 12581function deferred(func, miliseconds) 12582{ if (func) 12583 { miliseconds |= 0; 12584 if (miliseconds <= 0) 12585 { var q = deferred_q; 12586 if (!q) 12587 { q = deferred_q = []; 12588 setTimeout 12589 ( function() 12590 { deferred_q = null; 12591 for (var i=0, i_end=q.length; i<i_end; i++) 12592 { pcall(q[i]); 12593 } 12594 }, 12595 0 12596 ); 12597 } 12598 } 12599 else 12600 { var q = deferred_q_for[miliseconds]; 12601 if (!q) 12602 { q = deferred_q_for[miliseconds] = []; 12603 deferred 12604 ( function() 12605 { delete deferred_q_for[miliseconds]; 12606 } 12607 ); 12608 setTimeout 12609 ( function() 12610 { for (var i=0, i_end=q.length; i<i_end; i++) 12611 { pcall(q[i]); 12612 } 12613 }, 12614 miliseconds 12615 ); 12616 } 12617 } 12618 var i = q.length; 12619 q[i] = func; 12620 return function() 12621 { q[i] = null; 12622 }; 12623 } 12624 else
12625 { return do_nothing; 12626 } 12627} 12628function has_something(who, what_name, what_value) 12629{ if (Array.isArray(who)) 12630 { for (var i=0, i_end=who.length; i<i_end; i++) 12631 { var n = has_something(who[i], what_name, what_value); 12632 if (n) return n; 12633 } 12634 } 12635 else if (Array.isArray(what_value)) 12636 { for (var i=0, i_end=what_value.length; i<i_end; i++) 12637 { var n = has_something(who, what_name, what_value[i]); 12638 if (n) return n; 12639 } 12640 } 12641 else if (what_name == 'parentNode') 12642 { for (var p=who.parentNode||who.host, n=1; p; p=p.parentNode||p.host, n++) 12643 { if (p == what_value) return n; 12644 } 12645 } 12646 else 12647 { for (var p=who[what_name], n=1; p; p=p[what_name], n++) 12648 { if (p == what_value) return n; 12649 } 12650 } 12651 return 0; 12652} 12653function select_child_elems(elem, tag_names) 12654{ if (!tag_names) 12655 { return elem?.children; 12656 } 12657 var children = []; 12658 tag_names = ','+(tag_names.join ? tag_names.join(',') : tag_names.replace(RE_SPACES, '')).toUpperCase()+','; 12659 for (var n=elem && elem.firstElementChild; n; n=n.nextElementSibling) 12660 { if (tag_names.indexOf(','+n.nodeName+',') != -1) 12661 { children.push(n); 12662 } 12663 } 12664 return children; 12665} 12666function select_parent_elem(elem, tag_names) 12667{ tag_names = ','+(tag_names.join ? tag_names.join(',') : tag_names).toUpperCase()+','; 12668 elem = elem?.parentNode; 12669 while (elem && tag_names.indexOf(','+elem.nodeName+',')==-1) 12670 { elem = elem.parentNode; 12671 } 12672 return elem; 12673} 12674function elem_trim(elem) 12675{ while (elem.firstChild?.nodeType==XML_COMMENT_NODE || elem.firstChild?.nodeType==XML_TEXT_NODE && elem.firstChild.data.trim()==='') 12676 { elem.removeChild(elem.firstChild); 12677 } 12678 while (elem.lastChild?.nodeType==XML_COMMENT_NODE || elem.lastChild?.nodeType==XML_TEXT_NODE && elem.lastChild.data.trim()==='') 12679 { elem.removeChild(elem.lastChild); 12680 } 12681} 12682function elem_empty(elem) 12683{ var e; 12684 while ((e = elem.firstChild)) elem.removeChild(e); 12685} 12686function strip_text_nodes(elem) 12687{ var e, e2, i=0; 12688 var child_nodes = elem.childNodes; 12689 while ((e = child_nodes[i])) 12690 { if (e.nodeType == XML_TEXT_NODE) 12691 { var e2; 12692 while ((e2 = child_nodes[i+1]) && e2.nodeType==XML_TEXT_NODE) 12693 { e.data += e2.data; 12694 elem.removeChild(e2); 12695 } 12696 var data = e.data.trim(); 12697 if (data) 12698 { e.data = data; 12699 } 12700 else 12701 { elem.removeChild(e); 12702 i--; 12703 } 12704 } 12705 i++; 12706 } 12707} 12708function get_root_child(elem) 12709{ var root_elem = elem.firstChild; 12710 while (root_elem?.nodeType == XML_TEXT_NODE) 12711 { if (RE_HAS_NONSPACE.test(root_elem.data)) return null; 12712 root_elem = root_elem.nextSibling; 12713 } 12714 if (!root_elem || root_elem.nodeType!=XML_ELEMENT_NODE) 12715 { return null; 12716 } 12717 var after_root_elem = root_elem.nextSibling; 12718 while (after_root_elem?.nodeType == XML_TEXT_NODE) 12719 { if (RE_HAS_NONSPACE.test(after_root_elem.data)) return null; 12720 after_root_elem = after_root_elem.nextSibling; 12721 } 12722 return after_root_elem ? null : root_elem; 12723} 12724function pcall(func, owner, args, errors, err_msg_prefix) 12725{ if (func) 12726 { try 12727 { return func.apply(owner || window, args); 12728 } 12729 catch (e) 12730 { if (err_msg_prefix) 12731 { error(err_msg_prefix, e); 12732 } 12733 else 12734 { error(e); 12735 } 12736 if (errors) errors.push(e); 12737 } 12738 } 12739} 12740function add_to_parent(elem, parent_node, allow_circular) 12741{ if (parent_node) 12742 { let pn = parent_node.cloneNode && parent_node; 12743 let ps, ns; 12744 if (!pn) 12745 { ps = parent_node.previousSibling; 12746 ns = parent_node.nextSibling; 12747 pn = parent_node.parentNode || ps?.parentNode || ns?.parentNode; 12748 ns = ns || ps?.nextSibling; 12749 } 12750 if (pn) 12751 { if (elem.cloneNode) 12752 { if (allow_circular && has_something(pn, 'parentNode', elem)) 12753 { var elem_pn = elem.parentNode; 12754 var elem_ns = elem.nextSibling; 12755 pn.parentNode.removeChild(pn); 12756 } 12757 pn.insertBefore(elem, ns || null); 12758 if (elem_pn) 12759 { elem_pn.insertBefore(pn, elem_ns); 12760 } 12761 return elem; 12762 } 12763 let result = Array.prototype.slice.call(elem); 12764 for (var i=0, i_end=result.length; i<i_end; i++) 12765 { pn.insertBefore(result[i], ns || null); 12766 } 12767 return result; 12768 } 12769 } 12770} 12771function clone_node(node, parent_node, ontplunload, ontplload, no_templates_on_toplevel) 12772{ if (node._clone_template && !no_templates_on_toplevel) 12773 { if (ontplunload) ontplunload(); 12774 return node._clone_template(parent_node, ontplload); 12775 } 12776 var clone = node.cloneNode(false); 12777 if (node.nodeType == XML_ELEMENT_NODE) 12778 { for (var n=node.firstChild; n; n=n.nextSibling) 12779 { clone.appendChild(clone_node(n, null, ontplunload, ontplload)); 12780 } 12781 } 12782 if (parent_node) 12783 { add_to_parent(clone, parent_node); 12784 } 12785 return clone; 12786} 12787function is_node_linked(node, respect_switch_elem) 12788{ while (node) 12789 { if (node.nodeType == XML_DOCUMENT_NODE) 12790 { return true; 12791 } 12792 node = node.parentNode || node.host || respect_switch_elem && node._ne__switch_elem; 12793 } 12794} 12795function case_props_fill_values(case_props, prop_names, case_props_taken)
12796{ if (typeof(prop_names) == 'function') 12797 { return prop_names(case_props); 12798 } 12799 else if (typeof(prop_names) != 'object') 12800 { let value = case_props[prop_names]; 12801 if (case_props_taken.hasOwnProperty(prop_names)) 12802 { value = clone_obj(value); 12803 } 12804 else 12805 { case_props_taken[prop_names] = true; 12806 } 12807 return value; 12808 } 12809 else if (Array.isArray(prop_names)) 12810 { var obj = []; 12811 for (let p=0, p_end=prop_names.length; p<p_end; p++) 12812 { obj[p] = case_props_fill_values(case_props, prop_names[p], case_props_taken); 12813 } 12814 } 12815 else 12816 { var obj = {}; 12817 for (let p of Object.getOwnPropertyNames(prop_names)) 12818 { obj[p] = case_props_fill_values(case_props, prop_names[p], case_props_taken); 12819 } 12820 } 12821 return obj; 12822} 12823function case_to_tpls_props(case_props_decl, case_props, add_to_props) 12824{ if (case_props_decl && case_props) 12825 { for (let t of Object.getOwnPropertyNames(case_props_decl)) 12826 { if (t != 'hash') 12827 { let tpl_props = case_props_decl[t]; 12828 for (let p of Object.getOwnPropertyNames(tpl_props)) 12829 { let prop_name = tpl_props[p]; 12830 let prop_value = case_props_fill_values(case_props, prop_name, {}); 12831 if (prop_value !== undefined) 12832 { if (!add_to_props) add_to_props = {}; 12833 if (!add_to_props[t]) add_to_props[t] = {}; 12834 add_to_props[t][p] = prop_value; 12835 } 12836 } 12837 } 12838 } 12839 } 12840 return add_to_props; 12841} 12842function dispatch_event(elem, event_hash, plus_call_func) 12843{ var win_doc = new WinDoc(null, elem); 12844 var win = win_doc.win; 12845 var doc = win_doc.doc; 12846 if (event_hash.bubbles === undefined) 12847 { event_hash.bubbles = true; 12848 } 12849 var type = event_hash.type + ''; 12850 if (type.slice(0, 2) == 'on') 12851 { type = type.slice(2); 12852 } 12853 if (win.MouseEvent) 12854 { try 12855 { var event_obj = new win[EVENTS_CTORS[type] || 'Event'](type, event_hash); 12856 if (event_obj.type != type) 12857 { event_obj = null; 12858 } 12859 } 12860 catch (e) 12861 { 12862 } 12863 } 12864 if (!event_obj) 12865 { if (doc.createEvent) 12866 { event_obj = doc.createEvent("HTMLEvents"); 12867 event_obj.initEvent(type, true, true); 12868 } 12869 else if (doc.createEventObject) 12870 { event_obj = doc.createEventObject(); 12871 event_obj.eventType = type; 12872 } 12873 } 12874 for (var p in event_hash) 12875 { try 12876 { if (p != 'type') 12877 { event_obj[p] = event_hash[p]; 12878 } 12879 } 12880 catch (e) 12881 { // ok 12882 } 12883 } 12884 try 12885 { if (plus_call_func && plus_call_func.call(elem, event_obj)===false) 12886 { return false; 12887 } 12888 if (elem.dispatchEvent) 12889 { return elem.dispatchEvent(event_obj); 12890 } 12891 return elem.fireEvent(event_hash.type, event_obj); 12892 } 12893 catch (e) 12894 { error(e); 12895 } 12896} 12897function by_id_find_get_deep(child, repeat, parents, i, id, arr) 12898{ if (i == -1) 12899 { var elem = repeat[id]; 12900 if (elem) 12901 { for (var r_id in repeat) 12902 { if (repeat[r_id] == child) // eventually found the right by_id scope 12903 { return elem; 12904 } 12905 } 12906 } 12907 arr.push(elem); 12908 } 12909 else 12910 { var r = repeat[parents[i]]; 12911 for (var j=0, j_len=r.length; j<j_len; j++) 12912 { var elem = by_id_find_get_deep(child, r[j].by_id, parents, i-1, id, arr); 12913 if (elem) return elem; 12914 } 12915 } 12916} 12917function by_id_find_add(all, found) 12918{ if (Array.isArray(found)) 12919 { for (let f of found) 12920 { by_id_find_add(all, f); 12921 } 12922 } 12923 else 12924 { all.push(found); 12925 } 12926} 12927function by_id_find_id(by_id, id, found) 12928{ for (var i in by_id) 12929 { var elem = by_id[i]; 12930 if (i == id) 12931 { if (elem) 12932 { found.push(elem); 12933 return; // no need to add other elements with the same id, if there are such ones in inner scope 12934 } 12935 } 12936 else 12937 { if (elem?.index_of_child && !elem.cloneNode) 12938 { for (var j=0, j_end=elem.length; j<j_end; j++) 12939 { by_id_find_id(elem[j].by_id, id, found); 12940 } 12941 } 12942 } 12943 } 12944} 12945function by_id_find(child, id) 12946{ var elem = this[id]; 12947 if (elem) return elem; 12948 var parents = this._parent_names?.[id]; 12949 var arr = []; 12950 if (!parents) 12951 { by_id_find_id(this, id, arr); 12952 } 12953 else 12954 { var repeat = this; 12955 for (var i=parents.length-1; i>=0; i--) 12956 { var r = repeat[parents[i]]; 12957 var index = r.index_of_child(child); 12958 if (index == -1) break; 12959 repeat = r[index].by_id; 12960 } 12961 if (i == -1) return repeat[id]; 12962 elem = by_id_find_get_deep(child, repeat, parents, i, id, arr); 12963 if (elem) return elem; 12964 } 12965 return arr; 12966} 12967function by_id_find_all(child, names) 12968{ var all = []; 12969 for (var i=0, il=names.length; i<il; i++) 12970 { var found = this._find(child, names[i]); 12971 if (found) by_id_find_add(all, found); 12972 } 12973 return all; 12974} 12975function by_id_export_from_repeated_child(id, parent_by_id, by_id_child) 12976{ if (!parent_by_id._parent_names) parent_by_id._parent_names = {}; 12977 var by_id_parent_names = parent_by_id._parent_names; 12978 var ignore = {_find:1, _find_all:1, _parent_names:1, parent:1}; 12979 for (var n in by_id_child) 12980 { if (!(n in by_id_parent_names) && !(n in ignore)) 12981 { by_id_parent_names[n] = [id]; 12982 } 12983 } 12984 var by_id_2_parent_names = by_id_child._parent_names; 12985 if (by_id_2_parent_names) 12986 { for (var n in by_id_2_parent_names) 12987 { if (!(n in by_id_parent_names)) 12988 { by_id_parent_names[n] = [].concat(by_id_2_parent_names[n]); 12989 by_id_parent_names[n].push(id); 12990 } 12991 } 12992 } 12993 if (!parent_by_id._find) 12994 { parent_by_id._find = by_id_find; 12995 parent_by_id._find_all = by_id_find_all; 12996 } 12997} 12998function str_to_func(code, ns, by_id) 12999{ if (!code) return null; 13000 if (typeof(code) == 'function') return code; 13001 var func = new Function('event', 'ns', 'by_id', 'arguments', code); 13002 return function(event) 13003 { return func.call(this, event, ns, by_id, arguments); 13004 }; 13005} 13006function pair_funcs(func_1, func_2, conditional_caller) 13007{ if (!func_2) 13008 { return func_1; 13009 } 13010 else if (!func_1) 13011 { return func_2; 13012 } 13013 else 13014 { return function() 13015 { var errors = []; 13016 var result = pcall(func_1, this, arguments, errors); 13017 if (!conditional_caller || conditional_caller.call(this, result, errors[0])) 13018 { result = func_2.apply(this, arguments); 13019 } 13020 if (errors.length) throw errors[0]; 13021 return result; 13022 }; 13023 } 13024} 13025function add_props_shallow(obj, obj_2) 13026{ if (typeof(obj)=='object' && typeof(obj_2)=='object' && obj!=null && obj_2!=null) 13027 { Object.assign(obj, obj_2); 13028 } 13029} 13030function NewElemEvent(type, target, target_name) 13031{ this.type = type; 13032 this.target = target; 13033 this.target_name = target_name; 13034} 13035function serialize_attrs(props, exclude, cls) 13036{ var attrs = ''; 13037 var attrs_no_data_prefix = {style: 1}; 13038 var ONW = /^on[a-z]/i; 13039 for (var p in props) 13040 { var value = props[p]; 13041 if (!(p in exclude) && value!==undefined && typeof(value)!='function') 13042 { if (p=='class' || p=='className') 13043 { cls = cls ? cls+' '+value : value; 13044 } 13045 else if (typeof(value) != 'object') 13046 { attrs += ((p in attrs_no_data_prefix) ? ' ' : ' data-')+htmlspecialchars(p)+'="'+htmlspecialchars(value===false ? '0' : value===true ? '1' : value)+'"'; 13047 } 13048 else if (p == 'style') 13049 { var sexclude = {length: 1}; // plus Object.prototype 13050 var style = ''; 13051 for (var sp in value) 13052 { var svalue = value[sp]; 13053 if (!(sp in sexclude) && isNaN(parseInt(sp)) && (svalue && typeof(svalue)!='function' || svalue===0)) 13054 { style += '; '+style_from_camel_case(sp)+':'+svalue; 13055 } 13056 } 13057 if (style) 13058 { attrs += ' style="'+htmlspecialchars(style.slice(2))+'"'; 13059 } 13060 } 13061 else if (value!==null || !ONW.test(p)) 13062 { attrs += ' data-json-'+htmlspecialchars(p)+'="'+htmlspecialchars(JSON.stringify(value))+'"'; 13063 } 13064 } 13065 } 13066 if (cls) 13067 { attrs = ' class="'+htmlspecialchars(cls)+'"'+attrs; 13068 } 13069 return attrs; 13070} 13071function serialize_tpl_options(options, indent) 13072{ var str = ''; 13073 for (var i=0, i_end=options.length; i<i_end; i++) 13074 { var option = options[i]; 13075 str += indent+'<li'+serialize_attrs(option, {options:1, text:1, childNodes:1})+'>'; 13076 if (option.options) 13077 { str += '\n'+indent+'\t'; 13078 } 13079 var child_nodes = option.childNodes; 13080 if (!child_nodes) 13081 { str += htmlspecialchars(option.text); 13082 } 13083 else 13084 { for (var j=0, j_end=child_nodes.length; j<j_end; j++) 13085 { var n = child_nodes[j]; 13086 var node_type = n.nodeType; 13087 if (node_type == XML_ELEMENT_NODE) 13088 { str += n.outerHTML; 13089 } 13090 else if (node_type==XML_TEXT_NODE || node_type==XML_CDATA_SECTION_NODE) 13091 { str += htmlspecialchars(n.data); 13092 } 13093 } 13094 } 13095 if (option.options) 13096 { str += '\n'+indent+'\t<menu data-options="1">\n'+serialize_tpl_options(option.options, indent+'\t\t')+indent+'\t</menu>\n'+indent; 13097 } 13098 str += '</li>\n'; 13099 } 13100 return str; 13101} 13102/** Use `createElement` method from `Document.prototype`, because some sites override `document.createElement` to reject creating `<script>` elements so enforcing privacy consent 13103 **/ 13104function documentCreateElement(doc, tag_name) 13105{ return doc.constructor.prototype.createElement.call(doc, tag_name); 13106} 13107function new_elem(node_name, parent_node, props, by_id) 13108{ if (typeof(parent_node) == 'function') 13109 { return traverse(node_name, node_name.parentNode, parent_node); 13110 } 13111 var pos = typeof(node_name)!='string' ? -2 : node_name.indexOf('<'); 13112 if (pos==-2 && parent_node===undefined && props===undefined && by_id===un
13112defined) 13113 { // Wrapper around elem/object 13114 return new NewElem(this, node_name); 13115 } 13116 var win_doc = new WinDoc 13117 ( props, 13118 node_name.cloneNode ? node_name : parent_node && (parent_node.cloneNode ? parent_node : parent_node.previousSibling || parent_node.nextSibling) 13119 ); 13120 if (pos == -1) 13121 { // Create element 13122 var {doc} = win_doc; 13123 var elem = documentCreateElement(doc, node_name || 'strike'); 13124 if (!node_name) 13125 { elem.style.cssText = 'text-decoration:none; display:inline; position:static; z-index:auto; float:none; clear:none; visibility:visible; border:none; color:black; background:none; width:auto; height:auto; top:auto; right:auto; bottom:auto; left:auto; margin:0; padding:0; text-align:left'; 13126 } 13127 add_to_parent(elem, parent_node); 13128 add_props(win_doc, this, elem, props); 13129 } 13130 else 13131 { // Create/parse DOM tree 13132 let onready_queue = new OnReadyQ; 13133 elem = new_elem_3(this, node_name, parent_node, props || {}, by_id || {}, win_doc, onready_queue); 13134 onready_queue.leave(); 13135 } 13136 return elem; 13137} 13138function CallBalancer(initial, onminustozero, oninc, onzerotominus) 13139{ onminustozero = str_to_func(onminustozero); 13140 let balance = initial|0; 13141 let is_ready = false; 13142 let inc = function() 13143 { oninc?.apply(this, arguments); 13144 if (++balance == 0) 13145 { is_ready = true; 13146 return onminustozero?.call(this); 13147 } 13148 }; 13149 this.dec = function() 13150 { if (balance-- == 0) 13151 { onzerotominus?.call(this); 13152 } 13153 return inc; 13154 }; 13155 this.inc = inc; 13156 this.ready = function() 13157 { return new Promise 13158 ( y => 13159 { if (is_ready) 13160 { y(); 13161 } 13162 else 13163 { onminustozero = pair_funcs(y, onminustozero); 13164 } 13165 } 13166 ); 13167 }; 13168} 13169/** @param {Object} props_for_win_doc - Window/Document specification. This can be a "window" object, or another object that contains {window:...} or {document:...}, or both. 13170 **/ 13171function get_selection_ranges(props_for_win_doc) 13172{ var win_doc = new WinDoc(props_for_win_doc); 13173 var sel = win_doc.win.getSelection(); 13174 if (!sel.anchorNode) 13175 { return [ 13176 { startContainer: win_doc.doc.body, 13177 startOffset: 0, 13178 endContainer: win_doc.doc.body, 13179 endOffset: 0, 13180 collapsed: true 13181 } 13182 ]; 13183 } 13184 if (!sel.getRangeAt) 13185 { return [ 13186 { startContainer: sel.anchorNode || sel.baseNode, 13187 startOffset: sel.anchorOffset!=null ? sel.anchorOffset : sel.baseOffset, 13188 endContainer: sel.focusNode || sel.extentNode, 13189 endOffset: sel.focusOffset!=null ? sel.focusOffset : sel.extentOffset, 13190 collapsed: sel.isCollapsed 13191 } 13192 ] 13193 } 13194 var ranges = []; 13195 for (var i=0, i_end=sel.rangeCount; i<i_end; i++) 13196 { var range = sel.getRangeAt(i); 13197 ranges[i] = 13198 { startContainer: range.startContainer, 13199 startOffset: range.startOffset, 13200 endContainer: range.endContainer, 13201 endOffset: range.endOffset, 13202 collapsed: range.collapsed 13203 }; 13204 } 13205 return ranges; 13206} 13207function set_selection_ranges(ranges) 13208{ var last_underlying_range = null; 13209 if (ranges) 13210 { var win_doc = new WinDoc(null, ranges[0].startContainer || ranges[0].endContainer); 13211 var cur_sel = win_doc.win.getSelection(); 13212 if (cur_sel) 13213 { cur_sel.removeAllRanges(); 13214 for (var i=0, i_end=ranges.length; i<i_end; i++) 13215 { last_underlying_range = new_range(ranges[i]); 13216 cur_sel.addRange(last_underlying_range); 13217 } 13218 } 13219 } 13220 return last_underlying_range; 13221} 13222function new_range(range) 13223{ var startContainer = range.startContainer || range.endContainer; 13224 var startOffset = range.startContainer ? range.startOffset : range.endOffset; 13225 var endContainer = range.endContainer || range.startContainer; 13226 var endOffset = range.endContainer ? range.endOffset : range.startOffset; 13227 var win_doc = new WinDoc(null, startContainer || endContainer); 13228 var underlying_range = win_doc.doc.createRange(); 13229 underlying_range.setStart(startContainer, startOffset); 13230 underlying_range.setEnd(endContainer, endOffset); 13231 return underlying_range; 13232} 13233function clone_obj(obj, max_levels, clone_nodes) 13234{ max_levels |= 0; 13235 if (max_levels == 0) 13236 { max_levels = 0x7FFFFFFF; 13237 } 13238 if (window.Map) 13239 { return sub(obj, max_levels, clone_nodes, new Map); 13240 } 13241 else 13242 { return sub_array(obj, max_levels, clone_nodes, [], []); 13243 } 13244 13245 function sub(obj, max_levels, clone_nodes, cloned_map) 13246 { if (max_levels==0 || typeof(obj)!='object' || obj==null) 13247 { return obj; 13248 } 13249 if (obj.cloneNode) 13250 { return clone_nodes ? obj.cloneNode(true) : obj; 13251 } 13252 let v = cloned_map.get(obj); 13253 if (v !== undefined) 13254 { return v; 13255 } 13256 let done = false;
13257 try 13258 { var cobj = new obj.constructor; 13259 } 13260 catch (e) 13261 { cobj = {}; 13262 done = clone_style(cobj, obj); 13263 } 13264 cloned_map.set(obj, cobj); 13265 if (!done) 13266 { for (let key of Object.getOwnPropertyNames(obj)) 13267 { let value = obj[key]; 13268 cobj[key] = !value || value.window==value ? value : sub(value, max_levels-1, clone_nodes, cloned_map); 13269 } 13270 } 13271 return cobj; 13272 } 13273 13274 function sub_array(obj, max_levels, clone_nodes, parents, parent_clones) 13275 { if (max_levels==0 || typeof(obj)!='object' || obj==null) 13276 { return obj; 13277 } 13278 if (obj.cloneNode) 13279 { return clone_nodes ? obj.cloneNode(true) : obj; 13280 } 13281 let pos = parents.indexOf(obj); 13282 if (pos != -1) // if circular reference 13283 { return parent_clones[pos]; 13284 } 13285 let done = false; 13286 try 13287 { var cobj = new obj.constructor; 13288 } 13289 catch (e) 13290 { cobj = {}; 13291 done = clone_style(cobj, obj); 13292 } 13293 parents[parents.length] = obj; 13294 parent_clones[parent_clones.length] = cobj; 13295 if (!done) 13296 { for (let key of Object.getOwnPropertyNames(obj)) 13297 { let value = obj[key]; 13298 cobj[key] = !value || value.window==value ? value : sub_array(value, max_levels-1, clone_nodes, parents, parent_clones); 13299 } 13300 } 13301 parents.length--; 13302 parent_clones.length--; 13303 return cobj; 13304 } 13305 13306 function clone_style(cobj, obj) 13307 { if ('cssText' in obj && 'listStyleImage' in obj && typeof(obj.length)=='number') 13308 { for (let i=0, i_end=obj.length; i<i_end; i++) 13309 { cobj[obj[i]] = obj[obj[i]]; 13310 } 13311 return true; 13312 } 13313 return false; 13314 } 13315} 13316function equals(a, b, locales, options) 13317{ return subequals(a, b, locales, options, false, false, [], []); 13318 13319 function subequals(a, b, locales, options, a_circular, b_circular, a_parents, b_parents) 13320 { if (equals_shallow(a, b, locales, options)) 13321 { return true; 13322 } 13323 // compare functions by reference only (already compared above) 13324 // compare nonobjects in regular way (already compared above) 13325 // compare objects by reference (already compared above), and recursively (see below) 13326 if (typeof(a)=='object' && typeof(b)=='object' && a!=null && b!=null) 13327 { // compare objects recursively 13328 let a_arr = Array.isArray(a); 13329 if (a_arr || Array.isArray(b)) // if any of them is array 13330 { if (!a_arr || !Array.isArray(b) || a.length!==b.length) // unless both are arrays of equal length 13331 { return false; 13332 } 13333 } 13334 else if (a.cloneNode || b.cloneNode) 13335 { return false; 13336 } 13337 if (!a_circular) a_circular = push_parent(a_parents, a); 13338 if (!b_circular) b_circular = push_parent(b_parents, b); 13339 if (!a_circular || !b_circular) 13340 { if (a_arr) 13341 { for (let i=a.length-1; i>=0; i--) 13342 { if (!subequals(a[i], b[i], locales, options, a_circular, b_circular, a_parents, b_parents)) 13343 { return false; 13344 } 13345 } 13346 } 13347 else 13348 { for (let i in a) 13349 { if (a[i]!=null || b[i]!=null) // if any of them is not null/undefined/missing 13350 { if (!subequals(a[i], b[i], locales, options, a_circular, b_circular, a_parents, b_parents)) 13351 { return false; 13352 } 13353 } 13354 } 13355 for (let i in b) 13356 { if (b[i]!=null && !(i in a)) 13357 { return false; 13358 } 13359 } 13360 } 13361 if (!a_circular) a_parents.pop(); 13362 if (!b_circular) b_parents.pop(); 13363 } 13364 return true; 13365 } 13366 return false; 13367 } 13368 13369 function push_parent(parents, obj) 13370 { parents.push(obj); 13371 if (parents.length%4 == 0) // check for circular references each 4th level 13372 { for (let i=parents.length-4, i_end=i+4; i<i_end; i++) 13373 { for (let j=0; j<i; j++) 13374 { if (parents[j] === parents[i]) 13375 { parents.pop(); 13376 return true; // inside circular 13377 } 13378 } 13379 } 13380 } 13381 return false; 13382 } 13383} 13384function equals_shallow(a, b, locales, options) 13385{ if (typeof(a) == 'string') 13386 { return a.localeCompare(b, locales, options) == 0; 13387 } 13388 if (typeof(b) == 'string') 13389 { return b.localeCompare(a, locales, options) == 0; 13390 } 13391 if (a == b) 13392 { return true; 13393 } 13394 if (typeof(a)=='number' && typeof(b)=='number' && isNaN(a) && isNaN(b)) 13395 { return true; 13396 } 13397 return false; 13398} 13399 13400class NewElem 13401{ constructor(ns, elem) 13402 { this.ns = ns; 13403 this.elem = elem; 13404 } 13405 13406 add_props(props, transit) 13407 { var transit_set_end = transit_set_begin(transit); 13408 var win_doc = new WinDoc(props, this.elem); 13409 var result = add_props(win_doc, this.ns, this.elem, props, 1, null, null, null, transit); 13410 var n_changes = 0; 13411 if (result !== undefined) 13412 { this.elem = result; 13413 n_changes = 1; 13414 } 13415 transit_set_end(); 13416 return {elem: this.elem, n_changes}; 13417 } 13418 13419 add_style(style, transit) 13420 { if (!transit) 13421 { add_style(this.elem, style); 13422 } 13423 else 13424 { var transit_set_end = transit_set_begin(transit); 13425 var win_doc = new WinDoc(null, this.elem); 13426 add_props(win_doc, this.ns, this.elem, {style}, 0, null, null, null, transit); 13427 transit_set_end(); 13428 } 13429 } 13430 13431 style(prop, default_value) 13432 { return get_comp_style(this.elem, prop, default_value); 13433 } 13434 13435 get_data_attr(arg_name, default_value, props) 13436 { return get_data_attr(this.ns, this.elem, arg_name, default_value, false, props); 13437 } 13438 13439 take_data_attr(arg_name, default_value, props) 13440 { return get_data_attr(this.ns, this.elem, arg_name, default_value, true, props); 13441 } 13442 13443 get_attributes(no_data, no_regular, process_data) 13444 { return get_attributes(this.ns, {}, this.elem, no_data, no_regular, process_data); 13445 } 13446
13447 dispatch_event(event_hash, plus_call_func) 13448 { return dispatch_event(this.elem, event_hash, plus_call_func); 13449 } 13450 13451 dim(box_sizing) 13452 { return get_dim(this.elem, box_sizing); 13453 } 13454 13455 set_dim(dim, transit, box_sizing) 13456 { set_dim(this.ns, this.elem, dim, transit, box_sizing); 13457 } 13458 13459 get_set_dim_style(dim, box_sizing) 13460 { return get_set_dim_style(this.ns, this.elem, dim, box_sizing); 13461 } 13462 13463 scroll_into_view(transit, scroll_into_view_options) 13464 { scroll_into_view(this.ns, this.elem, transit, scroll_into_view_options); 13465 } 13466 13467 css_unit_to_px(value_with_unit, style_prop) 13468 { return css_unit_to_px(value_with_unit, this.elem, style_prop); 13469 } 13470 13471 has_parent(elem) 13472 { return !this.elem || !elem ? 0 : has_something(this.elem, 'parentNode', elem); 13473 } 13474 13475 has_sibling(elem) 13476 { return !this.elem || !elem ? 0 : -has_something(this.elem, 'previousSibling', elem) || has_something(this.elem, 'nextSibling', elem); 13477 } 13478 select_child_elems(tag_names) 13479 { return select_child_elems(this.elem, tag_names); 13480 } 13481 13482 select_parent_elem(tag_name) 13483 { return select_parent_elem(this.elem, tag_name); 13484 } 13485 13486 elem_trim() 13487 { elem_trim(this.elem); 13488 } 13489 13490 elem_empty() 13491 { elem_empty(this.elem); 13492 } 13493 13494 strip_text_nodes() 13495 { strip_text_nodes(this.elem); 13496 } 13497 13498 get_root_child() 13499 { return get_root_child(this.elem); 13500 } 13501 13502 /** Can add a DOMNode or an array (or a magic collection) of DOMNode to a parent element. 13503 Can be used in place of appendChild() and insertBefore(). 13504 Works in cases when appendChild() throws "The new child element contains the parent". 13505 @param {any} - Parent can be a DOMElement, or an object like {previousSibling: node}. In the latter case, after adding, the previous sibling of the added child will be the specified node. The object can contain one or several of: previousSibling, nextSibling, parentNode. 13506 @return the child. If it was a magic collection, it will be converted to array. Will return null if parent node is not specified. 13507 **/ 13508 add_to_parent(parent_node) 13509 { return add_to_parent(this.elem, parent_node, true); 13510 } 13511 13512 is_node_linked(respect_switch_elem) 13513 { return is_node_linked(this.elem, respect_switch_elem); 13514 } 13515 13516 clone_node(parent_node, ontplload) 13517 { if (!ontplload) 13518 { return clone_node(this.elem, parent_node); 13519 } 13520 var call_balancer = new CallBalancer 13521 ( -1, 13522 function() 13523 { ontplload.call(node); 13524 } 13525 ); 13526 var node = clone_node(this.elem, parent_node, null, call_balancer.dec()); 13527 call_balancer.inc(); 13528 return node; 13529 } 13530} 13531 13532var multipurpose_div = new_elem('div', null, {style: 'left:0;top:0;position:absolute;visibility:hidden'}); 13533 13534function new_elem_bootstrap_to(target) 13535{ target.IS_MSIE = IS_MSIE; 13536 target.IS_SAFARI = IS_SAFARI; 13537 target.IS_OPERA = IS_OPERA; 13538 target.IS_CHROME = IS_CHROME; 13539 target.IS_FIREFOX = IS_FIREFOX; 13540 target.new_elem_bootstrap_to = new_elem_bootstrap_to; 13541 target.new_elem = new_elem; 13542 target.log = log; 13543 target.error = error; 13544 target.warn = warn; 13545 target.info = info; 13546 target.declare_template = declare_template; 13547 target.common_prefix = common_prefix; 13548 target.clone_obj = clone_obj; 13549 target.equals = equals; 13550 target.pcall = pcall; 13551 target.pair_funcs = pair_funcs; 13552 target.CallBalancer = CallBalancer; 13553 target.transit_set_begin = transit_set_begin; 13554 target.with_css_unit = with_css_unit; 13555 target.get_v_scrollbar_width = get_v_scrollbar_width; 13556 target.get_selection_ranges = get_selection_ranges; 13557 target.set_selection_ranges = set_selection_ranges; 13558 target.new_range = new_range; 13559 target.css_text_to_obj = css_text_to_obj; 13560 target.deferred = deferred; 13561 if (!target.templates) 13562 { target.templates = {}; 13563 } 13564 if (Array.isArray(target.template_sources)) 13565 { for (let {name, wrap_id, html, arg_names, func} of target.template_sources) 13566 { target.declare_template(name, wrap_id, html, arg_names, func); 13567 } 13568 delete target.template_sources; 13569 } 13570} 13571new_elem_bootstrap_to(window[NEW_ELEM_NS_NAME]);NS['declare_template'] 13572( 'pager_popup', 13573 null, 13574 null, 13575 ['ns', 'elem'], 13576 function(ns, elem) 13577 { let {children} = elem; 13578 let find = ['previous', 'next', 'preview']; 13579 let found = {}; 13580 for (let i=0, i_end=children.length; i<i_end; i++) 13581 { let e = children[i]; 13582 if (find.indexOf(e.dataset.type) != -1) 13583 { found[e.dataset.type] = children[i]; 13584 e.removeAttribute('data-type'); 13585 } 13586 } 13587 let {previous, next, preview} = found; 13588 elem.textContent = ''; 13589 if (!previous || !next || !preview) 13590 { throw new Error('Invalid HTML structure'); 13591 } 13592 let previous_item = preview.children[0]; 13593 let next_item = preview.children[1]; 13594 preview.textContent = ''; 13595 let popup_by_id = {p: previous, n: next}; 13596 let need_preprocess = true; 13597 function copy_a_href(item, arrow) 13598 { let arrow_a = arrow.nodeName=='A' ? arrow : arrow.getElementsByTagName('a')[0]; 13599 if (arrow_a) 13600 { let aa = item.getElementsByTagName('a'); 13601 for (let i=0; i<aa.length; i++) 13602 { let a = aa[i]; 13603 if (a.href && a.href.slice(0, 4).toLowerCase()=='http') 13604 { arrow_a.href = a.href; 13605 } 13606 } 13607 } 13608 } 13609 if (NEW_ELEM_NS_NAME != 'stgui') 13610 { if (previous_item) 13611 { document.body.appendChild(previous); 13612 copy_a_href(previous_item, previous); 13613 } 13614 if (next) 13615 { document.body.appendChild(next); 13616 copy_a_href(next_item, next); 13617 } 13618 } 13619 else 13620 { previous.style.position = 'absolute'; 13621 next.style.position = 'absolute'; 13622 elem.style.minHeight = '300px'; 13623 elem.appendChild(previous); 13624 elem.appendChild(next); 13625 preview.addEventListener('click', event => {event.stopPropagation(); event.preventDefault()}, true); 13626 } 13627 preview.id = 'd'; 13628 preview.className = '$popup'; 13629 ns.new_elem 13630 ( preview, 13631 document.body, 13632 { d: 13633 { id: null, 13634 trigger_id: 'p,n', 13635 trigger_onmouseover: true, 13636 design: 'hint', 13637 ontplbeforeshow(event, case_props, near_node) 13638 { preview.textContent = ''; 13639 let item = near_node==previous ? previous_item : next_item; 13640 if (!item) 13641 { return false;
13642 } 13643 preview.appendChild(item); 13644 if (need_preprocess) 13645 { need_preprocess = false; 13646 ns.new_elem(preview, null); 13647 } 13648 } 13649 } 13650 }, 13651 popup_by_id 13652 ); 13653 } 13654); 13655 13656// popup_global_parent 13657function _new_elem_tmp_5(ns) 13658 { const ESCAPE = 27; 13659 13660 let global_parent = null; 13661 let listeners = {}; 13662 let pp = {}; 13663 let pp_names = []; 13664 let dummies = {}; 13665 13666 function has_parent(e, parent, level) 13667 { if (!level) 13668 { level = 0; 13669 } 13670 while (e) 13671 { if (e == parent) return true; 13672 if (level<10 && typeof(e._get_near_node)=='function') 13673 { if (has_parent(e._get_near_node(), parent, level+1)) 13674 { return true; 13675 } 13676 } 13677 e = e.parentNode; 13678 } 13679 } 13680 13681 function install(document, no_supress_css_where, tracker_params) 13682 { let {body} = document; 13683 if (!global_parent) 13684 { global_parent = ns.new_elem 13685 ( '<div style="position:static; height:auto; width:auto; background-color:transparent; border:none"></div>', 13686 body 13687 ); 13688 ns.new_elem(window).add_props 13689 ( { onkeydown: 13690 { func(event) 13691 { if (event.keyCode == ESCAPE) 13692 { for (let i=pp_names.length-1; i>=0; i--) 13693 { let pp_elem = pp[pp_names[i]]; 13694 let {children} = pp_elem; 13695 for (let i=children.length-1; i>=0; i--) 13696 { let c = children[i]; 13697 if (typeof(c._hide)=='function' && c._get_is_modal?.()) 13698 { c._hide(); 13699 event.stopPropagation(); 13700 event.preventDefault(); 13701 } 13702 } 13703 } 13704 } 13705 }, 13706 listeners 13707 } 13708 } 13709 ); 13710 } 13711 if (global_parent.nextSibling) 13712 { body.appendChild(global_parent); // move to end, so the highest possible z-index will be higher than the highest possible z-index of other elements 13713 } 13714 let hash = !no_supress_css_where ? '' : ns.strhash(no_supress_css_where).toString(36); 13715 if (!pp[hash]) 13716 { let p_by_id = {}; 13717 if (NEW_ELEM_NS_NAME == '_S_T') 13718 { var data_not = !no_supress_css_where || no_supress_css_where=='*' ? '' : ` data-not="${ns.htmlspecialchars(no_supress_css_where)}"`; 13719 } 13720 else 13721 { var data_not = !no_supress_css_where || no_supress_css_where=='*' ? ` data-not="${ns.htmlspecialchars(CONF__USER_AGENT_CSS_NOT)}"` : ` data-not="${ns.htmlspecialchars(no_supress_css_where)}"`; 13722 } 13723 ns.new_elem 13724 ( '<div id="id">' + 13725 ( NEW_ELEM_NS_NAME=='_S_T' && tracker_params ? 13726 '<div id="pp"></div>' 13727 : NEW_ELEM_NS_NAME == '_S_T' ? // for compatibility with old actions that didn't pass tracker_params 13728 (no_supress_css_where=='*' ? '' : '<style class="$user_agent_css S-3T S-4T S-5T S-6T S-7T S-8T" parent_selector="#az2tr8'+hash+'"'+data_not+'></style>') + 13729 '<div id="pp" class="S-3T S-4T S-5T S-6T S-7T S-8T S-9T"></div>' 13730 : 13731 (no_supress_css_where=='*' ? '' : `<style class="$user_agent_css ${CONF__PARTIAL_BOOTSTRAP_CLASSES_WEAK}" parent_selector="#az2tr8${hash}"${data_not}></style>`) + 13732 '<style class="$style '+CONF__PARTIAL_BOOTSTRAP_CLASSES+'" parent_selector="#az2tr8'+hash+'"></style>' + 13733 '<style class="$partial_bootstrap_css '+CONF__PARTIAL_BOOTSTRAP_CLASSES+'" parent_selector="#az2tr8'+hash+'"></style>' + 13734 '<div id="pp" class="'+CONF__PARTIAL_BOOTSTRAP_CLASSES+'"></div>' 13735 ) + 13736 '</div>', 13737 global_parent, 13738 { id: 13739 { id: 'az2tr8'+hash, 13740 style: no_supress_css_where=='*' || NEW_ELEM_NS_NAME=='_S_T' && tracker_params ? 'position:static' : 'position:static; font-style:normal; font-weight:normal; font-variant:normal; text-decoration:none; text-align:left; text-align:start; line-height:normal; visibility:visible; white-space:normal; cursor:default' 13741 }, 13742 pp: 13743 { style: 'position:static' 13744 } 13745 }, 13746 p_by_id 13747 ); 13748 if (NEW_ELEM_NS_NAME=='_S_T' && tracker_params) 13749 { ns.uacss.supress_for_elem(p_by_id.pp, tracker_params.action_id, tracker_params.with_uacss); 13750 } 13751 pp[hash] = p_by_id.pp; 13752 pp_names[pp_names.length] = hash; 13753 } 13754 return pp[hash]; 13755 } 13756 13757 function uninstall(force) 13758 { for (let i=pp_names.length-1; i>=0; i--) 13759 { let pp_elem = pp[pp_names[i]]; 13760 let {children} = pp_elem; 13761 if (force || !children.length) 13762 { let p = pp_elem.parentNode; 13763 if (p) 13764 { let p_p = p.parentNode; 13765 if (p_p) 13766 { p_p.removeChild(p); 13767 } 13768 } 13769 delete pp[pp_names[i]]; 13770 pp_names.splice(i, 1); 13771 } 13772 } 13773 if (global_parent && (force || !pp_names.length)) 13774 { let p = global_parent.parentNode; 13775 if (p) 13776 { p.removeChild(global_parent); 13777 } 13778 global_parent = null; 13779 listeners.onkeydown.remove(); 13780 listeners.onkeydown = null; 13781 } 13782 } 13783 13784 function close_popups_opened_inside_this_popup(elem)
13785 { for (let name of Object.keys(pp)) 13786 { for (let popup of pp[name].children) 13787 { let near_node = popup._get_near_node?.(); 13788 if (near_node) 13789 { while (near_node) 13790 { if (near_node._by_id?.root == elem) 13791 { popup._hide_by_trigger(); 13792 break; 13793 } 13794 near_node = near_node.parentNode; 13795 } 13796 } 13797 } 13798 } 13799 } 13800 13801 var popup_global_parent = 13802 { _add_elem(document, elem, no_supress_css_where, tracker_params) 13803 { var pp_elem = install(document, no_supress_css_where, tracker_params); 13804 pp_elem.appendChild(elem); 13805 if (elem._get_name) 13806 { delete dummies[elem._get_name()]; 13807 } 13808 }, 13809 13810 _remove_elem(document, elem) 13811 { var p = elem.parentNode; 13812 for (var i=pp_names.length-1; i>=0; i--) 13813 { var pp_elem = pp[pp_names[i]]; 13814 if (p == pp_elem) 13815 { p.removeChild(elem); 13816 } 13817 } 13818 uninstall(); 13819 }, 13820 13821 _close_popups_opened_inside_this_popup : close_popups_opened_inside_this_popup, 13822 13823 _get_by_name(name) 13824 { var elems = []; 13825 for (var i=pp_names.length-1; i>=0; i--) 13826 { var pp_elem = pp[pp_names[i]]; 13827 for (var p=pp_elem.firstChild; p; p=p.nextSibling) 13828 { if (p._get_name && p._get_name()==name) 13829 { elems[elems.length] = p; 13830 } 13831 } 13832 } 13833 if (dummies.hasOwnProperty(name)) 13834 { elems[elems.length] = dummies[name]; 13835 } 13836 return elems; 13837 }, 13838 13839 _add_dummy(name, elem) 13840 { dummies[name] = elem; 13841 }, 13842 13843 has_parent 13844 }; 13845 13846 return popup_global_parent; 13847 } 13848 13849NS['declare_template'] 13850( 'popup', 13851 null, 13852 '<div id="root" class="$switch-elem"> '+ 13853 '<div data-case="on" data-prepared="1" id="container"> '+ 13854 '<div id="topbar" style="display:flex; justify-content:space-between; align-items:flex-start; width:100%; margin:0; padding:0; border-bottom:1px solid #eef3fb"> '+ 13855 '<div id="title" style="padding:0.6em; font-size:130%; font-weight:600"></div> '+ 13856 '<div style="flex-grow:1000; text-align:end; padding:1em"> '+ 13857 '<a id="button_close" href="javascript:"> '+ 13858 '× '+ 13859 '</a> '+ 13860 '</div> '+ 13861 '</div> '+ 13862 '<div id="elem"></div> '+ 13863 '</div> '+ 13864 '</div>', 13865 ['ns', 'by_id', 'by_id_parent', 'window', 'document', 'elem', 'style', 'name', 'replace_current', 'side_preference', 'with_stretch', 'prefer_overlay', 'near_node_id', 'scroll_to_near_node', 'is_modal', 'trigger_id', 'parent_is_trigger', 'trigger_transit', 'trigger_onmouseover', 'trigger_onfocus', 'hide_onclick_outside', 'position_number', 'value', 'design', 'title', 'title_id', 'arrow_width', 'arrow_height', 'no_supress_css_where', 'tracker_params', 'topbar_style', 'with_backdrop_blur', 'button_close_style', 'report_tplchange', 'report_tplbeforeshow', 'report_tplbeforehide', 'report_tplshow', 'report_tplhide', 'report_tplupdateposition', 'report_tplbeforeupdateposition'], 13866 function(ns, by_id, by_id_parent, window, document, elem, style, name, replace_current, side_preference, with_stretch, prefer_overlay, near_node_id, scroll_to_near_node, is_modal, trigger_id, parent_is_trigger, trigger_transit, trigger_onmouseover, trigger_onfocus, hide_onclick_outside, position_number, value, design, title, title_id, arrow_width, arrow_height, no_supress_css_where, tracker_params, topbar_style, with_backdrop_blur, button_close_style, report_tplchange, report_tplbeforeshow, report_tplbeforehide, report_tplshow, report_tplhide, report_tplupdateposition, report_tplbeforeupdateposition) 13867 { const HIDE_DELAY = 200; 13868 position_number |= 0; 13869 if (!elem._listeners) 13870 { elem._listeners = {}; 13871 } 13872 if (!elem._listeners_trigger) 13873 { elem._listeners_trigger = []; 13874 } 13875 if (!elem._listeners_imgs) 13876 { elem._listeners_imgs = []; 13877 } 13878 var at_x_side = side_preference=='right' || side_preference=='left' || side_preference=='x' || side_preference=='start' || side_preference=='end'; 13879 arrow_width = arrow_width-0 || (at_x_side ? 12 : 8); 13880 arrow_height = arrow_height-0 || (at_x_side ? 8 : 12); 13881 var near_node = null; 13882 var trigger_by_id = by_id_parent; 13883 var triggers = []; 13884 var listeners = elem._listeners; 13885 var listeners_trigger = elem._listeners_trigger; 13886 var listeners_imgs = elem._listeners_imgs; 13887 var is_over = false;
13888 var want_remove = false; 13889 var cancel_deferred_hide = null; 13890 var want_detach_root = true; 13891 var glass = null; 13892 var chained_popup = null; 13893 var chained_show_wrapper = null; 13894 var side = ''; 13895 var style_width_height_known = false; 13896 var style_width = null; 13897 var style_height = null; 13898 var hint_prev_side = null; 13899 var border_width = null; 13900 var border_color = null; 13901 var background_color = null; 13902 var hint_arrow_1_elem = null; 13903 var hint_arrow_2_elem = null; 13904 var flexible_elem = null; 13905 style = ns.new_elem('div', null, {style}).style; 13906 var is_position_absolute = style.position == 'absolute'; 13907 var elem_parent = null; 13908 var sw_elem = null; 13909 var design_finished = false; 13910 var is_loaded = false; 13911 var onhide_callbacks = []; 13912 var cur_onhide = null; 13913 var over_trigger = null; 13914 var with_arrow = design=='hint'; 13915 const RE_DIG = /\d/; 13916 const ARROW_STYLES = 13917 { top: 'border-width:${HEIGHT}px ${WIDTH}px 0 ${WIDTH}px; border-color:${COLOR} transparent; top:auto; right:auto; bottom:-${Y}px; left:auto', 13918 right: 'border-width:${HEIGHT}px ${WIDTH}px ${HEIGHT}px 0; border-color:transparent ${COLOR}; top:auto; right:auto; bottom:auto; left:-${X}px', 13919 bottom: 'border-width:0 ${WIDTH}px ${HEIGHT}px ${WIDTH}px; border-color:${COLOR} transparent; top:-${Y}px; right:auto; bottom:auto; left:auto', 13920 left: 'border-width:${HEIGHT}px 0 ${HEIGHT}px ${WIDTH}px; border-color:transparent ${COLOR}; top:auto; right:-${X}px; bottom:auto; left:auto' 13921 }; 13922 const DIALOG_BOTTOMBAR_STYLE = 'box-sizing:border-box; border-collapse:collapse; width:100%; border-top:1px solid #ECECEC; box-shadow:0 1px 0 #FFF inset; background-color:#F7F7F7; padding:1em; text-align:right; text-align:end'; 13923 if (elem._by_id) 13924 { elem._by_id.root._hide(); 13925 } 13926 elem._by_id = by_id; 13927 function finish_design() 13928 { if (!design_finished) 13929 { design_finished = true; 13930 var root = elem; 13931 if (sw_elem) 13932 { root = sw_elem.firstElementChild; 13933 } 13934 var placements = get_placements(root); 13935 if (placements.top || placements.bottom) 13936 { elem.style.padding = by_id.container.style.padding || '1em';
13937 by_id.container.style.padding = ''; 13938 } 13939 if (placements.top) 13940 { let for_button_close = by_id.button_close.parentNode; 13941 for_button_close.parentNode.insertBefore(placements.top, for_button_close); 13942 placements.top.style.flexGrow = for_button_close.style.flexGrow; 13943 for_button_close.style.flexGrow = ''; 13944 } 13945 else if (!title && !title_id) 13946 { var pn = by_id.topbar.parentNode; 13947 if (pn) 13948 { pn.removeChild(by_id.topbar); 13949 } 13950 by_id.topbar = null; 13951 } 13952 let container_style = ns.new_elem(by_id.container).style(); 13953 if (placements.bottom) 13954 { placements.bottom.style.cssText = DIALOG_BOTTOMBAR_STYLE+';'+placements.bottom.style.cssText; 13955 placements.bottom.style.borderBottomLeftRadius = container_style.borderBottomLeftRadius; 13956 placements.bottom.style.borderBottomRightRadius = container_style.borderBottomRightRadius; 13957 elem.parentNode.insertBefore(placements.bottom, elem.nextChild || null); 13958 by_id.bottombar = placements.bottom; 13959 } 13960 if (by_id.topbar) 13961 { by_id.topbar.style.borderTopLeftRadius = container_style.borderTopLeftRadius; 13962 by_id.topbar.style.borderTopRightRadius = container_style.borderTopRightRadius; 13963 } 13964 } 13965 } 13966 function get_placements(elem) 13967 { var placements = {}; 13968 if (elem) 13969 { for (var e=elem.firstChild; e; e=next) 13970 { var next = e.nextSibling; 13971 var placement = e.getAttribute && e.getAttribute('data-placement'); 13972 if (placement) 13973 { placements[placement] = e; 13974 } 13975 } 13976 } 13977 return placements; 13978 } 13979 function subst_params(str, params) 13980 { return str.replace(/\$\{(\w+)\}/g, (m, $1) => params[$1]); 13981 } 13982 function get_is_showing() 13983 { return by_id.root._get_case()!=null; 13984 } 13985 function get_xy_for_position(vp_dim, width, height, is_absolute) 13986 { var n = position_number || 5; 13987 var top = (is_absolute ? vp_dim.top : 0) + 13988 ( n==1 || n==2 || n==3 ? 0 13989 : 13990 n==4 || n==5 || n==6 ? (vp_dim.height - height)/2 13991 : 13992 vp_dim.height - height 13993 ); 13994 var left = (is_absolute ? vp_dim.left : 0) + 13995 ( n==1 || n==4 || n==7 ? 0 13996 : 13997 n==2 || n==5 || n==8 ? (vp_dim.width - width)/2 13998 : 13999 vp_dim.width - width 14000 ); 14001 var xy = {}; 14002 if (!is_absolute && top==vp_dim.height-height) 14003 { xy.bottom = style.bottom || 0; 14004 } 14005 else if (top == 0) 14006 { xy.top = style.top || 0; 14007 } 14008 else 14009 { xy.top = top; 14010 } 14011 if (!is_absolute && left==vp_dim.width-width) 14012 { xy.right = style.right || 0; 14013 if (vp_dim.width == width) 14014 { xy.left = style.left || 0; 14015 } 14016 } 14017 else if (left == 0) 14018 { xy.left = style.left || 0; 14019 } 14020 else 14021 { xy.left = left; 14022 } 14023 return xy; 14024 } 14025 function remove_listener(name) 14026 { if (listeners[name]) 14027 { listeners[name].remove(); 14028 listeners[name] = null; 14029 } 14030 } 14031 function remove_listeners_imgs() 14032 { while (listeners_imgs.length) 14033 { listeners_imgs.pop().remove(); 14034 } 14035 } 14036 function do_hide_by_trigger() 14037 { by_id.root._hide(ns.clone_obj(trigger_transit)); 14038 } 14039 function get_defined_near_node() 14040 { return !near_node_id ? null : near_node_id.cloneNode ? near_node_id : by_id_parent[near_node_id]; 14041 } 14042 function ontrigger(event) 14043 { if (trigger_onmouseover || event.which==1) 14044 { if (!(this._get_disabled?.() ?? false)) 14045 { if (!ns.popup_global_parent.has_parent(event.target, by_id.container)) 14046 { let currently_showing = null; 14047 if (!trigger_onmouseover || !name || !(currently_showing = ns.popup_global_parent._get_by_name(name)[0])) 14048 { by_id.root._switch(trigger_onmouseover ? true : null, ns.clone_obj(trigger_transit), get_defined_near_node() || this); 14049 event.stopPropagation(); 14050 } 14051 else if (currently_showing && currently_showing!==by_id.root) 14052 { over_trigger?._remove_onhide(cur_onhide); 14053 over_trigger = currently_showing; 14054 cur_onhide = () => 14055 { if (over_trigger) 14056 { ontrigger.call(this, event); 14057 } 14058 }; 14059 over_trigger._add_onhide(cur_onhide); 14060 } 14061 } 14062 } 14063 } 14064 } 14065 function ontriggeroff(event) 14066 { if (!cancel_deferred_hide) 14067 { if (event.type=='click' || trigger_onmouseover) 14068 { let {target} = event; 14069 if (event.isTrusted!==false || target==document || target==document.body) // some jQuery scripts generate clicks on those elements 14070 { if (target==by_id.root || triggers.indexOf(target)!=-1 || ns.popup_global_parent.has_parent(target, by_id.root) || !ns.new_elem(target).is_node_linked()) // in Chrome target may be unlinked if in onclick handler that has already been worked, the target's parent has been unlinked 14071 { return; 14072 } 14073 let {top, right, bottom, left} = by_id.root.getBoundingClientRect(); 14074 for (let trigger of triggers) 14075 { if (ns.popup_global_parent.has_parent(target, trigger)) 14076 { return; 14077 } 14078 if (trigger_onmouseover) 14079 { let trigger_rect = trigger.getBoundingClientRect(); 14080 if (trigger_rect.top < top) 14081 { top = trigger_rect.top; 14082 } 14083 if (trigger_rect.right > right) 14084 { right = trigger_rect.right; 14085 } 14086 if (trigger_rect.bottom > bottom) 14087 { bottom = trigger_rect.bottom; 14088 } 14089 if (trigger_rect.left < left) 14090 { left = trigger_rect.left; 14091 } 14092 } 14093 } 14094 let hide_delay = trigger_onmouseover && event.pageX>left && event.pageX<right && event.pageY>top && event.pageY<bottom ? HIDE_DELAY : 0; 14095 cancel_deferred_hide = ns.deferred(do_hide_by_trigger, hide_delay); 14096 } 14097 } 14098 } 14099 } 14100 function find_trigger() 14101 { // remove all triggers and listeners 14102 triggers.length = 0; 14103 for (let listeners of listeners_trigger)
14104 { for (let name of Object.keys(listeners)) 14105 { listeners[name]?.remove?.(); 14106 } 14107 } 14108 listeners_trigger.length = 0; 14109 // 14110 if (parent_is_trigger) 14111 { let trigger = elem_parent; 14112 var n = parent_is_trigger; 14113 while (--n > 0 && trigger.parentNode) 14114 { trigger = trigger.parentNode; 14115 } 14116 triggers[0] = trigger; 14117 } 14118 if (trigger_id) 14119 { if (trigger_id.cloneNode) 14120 { triggers[triggers.length] = trigger_id; 14121 } 14122 else 14123 { for (let tid of (trigger_id+'').split(',')) 14124 { tid = tid.trim(); 14125 if (tid) 14126 { let trigger = trigger_by_id[tid]; 14127 if (trigger) 14128 { triggers[triggers.length] = trigger; 14129 } 14130 } 14131 } 14132 } 14133 } 14134 for (let trigger of triggers) 14135 { let listeners = {}; 14136 listeners_trigger[listeners_trigger.length] = listeners; 14137 ns.new_elem(trigger).add_props 14138 ( trigger_onfocus ? 14139 { onfocus: 14140 { func() 14141 { by_id.root._show(ns.clone_obj(trigger_transit), get_defined_near_node() || this); 14142 this.focus(); 14143 }, 14144 listeners 14145 }, 14146 onblur: 14147 { func() 14148 { if (!is_over) 14149 { do_hide_by_trigger(); 14150 } 14151 }, 14152 listeners 14153 } 14154 } 14155 : 14156 { onkeydown: 14157 { func(event) 14158 { /*if (event.keyCode == 13) // ENTER 14159 { by_id.root._switch(null, ns.clone_obj(trigger_transit), get_defined_near_node() || this); 14160 event.stopPropagation(); 14161 } 14162 else if (event.keyCode == 32) // SPACE 14163 { by_id.root._show(ns.clone_obj(trigger_transit), this); 14164 event.stopPropagation(); 14165 } 14166 else*/ if (event.keyCode == 27) // ESCAPE 14167 { if (by_id.root._get_is_showing()) 14168 { do_hide_by_trigger(); 14169 event.stopPropagation(); 14170 } 14171 } 14172 }, 14173 listeners 14174 }, 14175 onclick: trigger_onmouseover ? null : 14176 { func: ontrigger, 14177 listeners 14178 }, 14179 onmouseenter: !trigger_onmouseover ? null : 14180 { func: ontrigger, 14181 listeners 14182 }, 14183 onmouseleave: !trigger_onmouseover ? null : 14184 { func() 14185 { if (over_trigger) 14186 { over_trigger._remove_onhide(cur_onhide); 14187 over_trigger = null; 14188 cur_onhide = null; 14189 } 14190 }, 14191 listeners 14192 } 14193 } 14194 ); 14195 } 14196 } 14197 function is_no_dim(e, dim_name) 14198 { var dim_value = e.style[dim_name]; 14199 return !dim_value || dim_value=='auto' || dim_value=='100%'; 14200 } 14201 function is_block(e) 14202 { var display = ns.new_elem(e).style('display'); 14203 return display=='block' || display=='table' || display=='table-row-group' || display=='table-row' || display=='table-cell' || display=='table-header-group' || display=='table-footer-group'; 14204 } 14205 function is_overflow_scroll(e, overflow_name) 14206 { var overflow = ns.new_elem(e).style(overflow_name); 14207 return overflow=='auto' || overflow=='scroll' || overflow=='overlay'; 14208 } 14209 function get_scrollable_descendant(e, dim_name, overflow_name) 14210 { if (is_overflow_scroll(e, overflow_name)) 14211 { return e; 14212 } 14213 if (!is_no_dim(e, dim_name) || !is_block(e)) 14214 { return null; 14215 } 14216 var {children} = e; 14217 if (children.length != 1) 14218 { return null; 14219 } 14220 return get_scrollable_descendant(children[0], dim_name, overflow_name); 14221 } 14222 class FlexibleElem 14223 { constructor(elem) 14224 { var flexible_elem = null; 14225 var flexible_width_elem = undefined; 14226 var flexible_height_elem = undefined; 14227 if (is_no_dim(elem, 'width') && is_no_dim(elem, 'height')) 14228 { var e = elem; 14229 while (true) 14230 { var {children} = e; 14231 if (children.length != 1) break; 14232 var child = children[0]; 14233 if (!is_no_dim(child, 'width') || !is_no_dim(child, 'height') || !is_block(child)) 14234 { break; 14235 } 14236 e = child; 14237 } 14238 var width = 0; 14239 var height = 0; 14240 var w_siblings = []; 14241 var h_siblings = []; 14242 for (var i=0, i_end=children.length; i<i_end; i++) 14243 { var child = children[i]; 14244 var scrollable_descendant_x = get_scrollable_descendant(child, 'width', 'overflow-x'); 14245 var scrollable_descendant_y = get_scrollable_descendant(child, 'height', 'overflow-y'); 14246 if (scrollable_descendant_x) 14247 { if (flexible_width_elem === undefined) 14248 { flexible_width_elem = scrollable_descendant_x; 14249 } 14250 else 14251 { flexible_width_elem = null; 14252 } 14253 } 14254 else 14255 { w_siblings[w_siblings.length] = child; 14256 } 14257 if (scrollable_descendant_y) 14258 { if (flexible_height_elem === undefined) 14259 { flexible_height_elem = scrollable_descendant_y; 14260 } 14261 else 14262 { flexible_height_elem = null; 14263 } 14264 } 14265 else 14266 { h_siblings[h_siblings.length] = child; 14267 } 14268 width += child.offsetWidth; 14269 height += child.offsetHeight; 14270 } 14271 if (width > elem.offsetWidth) 14272 { flexible_width_elem = null; 14273 } 14274 if (height > elem.offsetHeight) 14275 { flexible_height_elem = null; 14276 } 14277 flexible_elem = flexible_width_elem || flexible_height_elem; 14278 if (flexible_elem) 14279 { if (flexible_width_elem && flexible_height_elem && flexible_width_elem!=flexible_height_elem) 14280 { flexible_elem = null; 14281 } 14282 } 14283 } 14284 this.elem = flexible_elem || null; 14285 this.siblings = !flexible_elem ? null : flexible_width_elem ? w_siblings : h_siblings; 14286 this.f_width = flexible_elem && !!flexible_width_elem; 14287 this.f_height = flexible_elem && !!flexible_height_elem; 14288 } 14289 get_siblings_width() 14290 { var width = 0; 14291 var siblings = this.siblings; 14292 for (var i=0, i_end=siblings.length; i<i_end; i++) 14293 { width += siblings[i].offsetWidth; 14294 } 14295 return width; 14296 } 14297 get_siblings_height() 14298 { var height = 0; 14299 var siblings = this.siblings; 14300 for (var i=0, i_end=siblings.length; i<i_end; i++) 14301 { height += siblings[i].offsetHeight; 14302 } 14303 return height; 14304 } 14305 } 14306 function update_position(transit, new_near_node, new_side_preference, new_prefer_overlay) 14307 { if (new_near_node) 14308 { near_node = new_near_node; 14309 } 14310 if (near_node && !near_node.cloneNode && !(near_node.left!=null && near_node.top!=null)) 14311 { near_node = null; 14312 } 14313 if (!near_node) 14314 { near_node = get_defined_near_node(); 14315 } 14316 if (new_side_preference != null) 14317 { side_preference = new_side_preference; 14318 } 14319 if (new_prefer_overlay != null)
14320 { prefer_overlay = new_prefer_overlay; 14321 } 14322 if (!get_is_showing()) 14323 { return; 14324 } 14325 if (!flexible_elem) 14326 { flexible_elem = new FlexibleElem(elem); 14327 } 14328 if (flexible_elem.f_width) 14329 { var flexible_elem_width = flexible_elem.elem.scrollWidth; 14330 } 14331 if (flexible_elem.f_height) 14332 { var flexible_elem_height = flexible_elem.elem.scrollHeight; 14333 } 14334 if (flexible_elem.f_width) 14335 { flexible_elem.elem.style.width = flexible_elem_width+'px'; 14336 } 14337 if (flexible_elem.f_height) 14338 { flexible_elem.elem.style.height = flexible_elem_height+'px'; 14339 } 14340 var container = by_id.container; 14341 var container_style = container.style; 14342 var vp_dim = ns.new_elem(window).dim(); 14343 var dim = near_node?.cloneNode ? ns.new_elem(near_node).dim() : {left: near_node?.left || 0, top: near_node?.top || 0, width: near_node?.width || 0, height: near_node?.height || 0}; 14344 var extended_bounds_x = false; 14345 var extended_bounds_y = false; 14346 var to_top = side_preference == 'top'; // Otherwise bottom 14347 var to_left = side_preference == 'left'; // to right if 'right', otherwise depends on direction 14348 if (side_preference!='left' && side_preference!='right') 14349 { var direction = ns.new_elem(near_node?.cloneNode && near_node || document.body).style('direction'); 14350 if (direction == 'rtl') 14351 { to_left = side_preference == 'end'; 14352 } 14353 else 14354 { to_left = side_preference != 'end'; 14355 } 14356 } 14357 var comp_style_d = ns.new_elem(container).style(); 14358 // create arrow 14359 var add_arrow_width = 0; 14360 var add_arrow_height = 0; 14361 if (near_node && with_arrow) 14362 { if (border_width == null) 14363 { border_width = parseFloat(comp_style_d.borderTopWidth); 14364 border_color = comp_style_d.borderTopColor; 14365 background_color = comp_style_d.backgroundColor; 14366 } 14367 if (!hint_arrow_1_elem) 14368 { hint_arrow_1_elem = ns.new_elem('div', container, {style: 'position:absolute; border-style:solid; width:0; height:0; z-index:'+0x7FFFFFFF}); 14369 hint_arrow_2_elem = ns.new_elem('div', container, {style: 'position:absolute; border-style:solid; width:0; height:0; z-index:'+0x7FFFFFFF}); 14370 } 14371 if (at_x_side) add_arrow_width = arrow_width; 14372 if (!at_x_side) add_arrow_height = arrow_height; 14373 } 14374 // defined width and height? 14375 if (!style_width_height_known) 14376 { style_width_height_known = true; 14377 style_width = container_style.width; 14378 style_height = container_style.height; 14379 if (!style_width || style_width=='auto') style_width = null; 14380 if (!style_height || style_height=='auto') style_height = null; 14381 } 14382 var orig_left = comp_style_d.left; 14383 var orig_right = comp_style_d.right; 14384 var orig_top = comp_style_d.top; 14385 var orig_bottom = comp_style_d.bottom; 14386 var orig_width = comp_style_d.width; 14387 var orig_height = comp_style_d.height; 14388 // let occupy space 14389 container_style.width = style_width || 'auto'; 14390 container_style.height = style_height || 'auto'; 14391 container_style.left = vp_dim.left+'px'; 14392 container_style.top = '0px'; 14393 container_style.right = 'auto'; 14394 container_style.bottom = 'auto'; 14395 if (!RE_DIG.test(container_style.maxWidth)) 14396 { container_style.maxWidth = '100%'; 14397 } 14398 if (!RE_DIG.test(container_style.maxHeight)) 14399 { container_style.maxHeight = '100%'; 14400 } 14401 container_style.position = 'absolute'; 14402 elem.style.overflow = 'visible'; 14403 elem.style.width = style_width=='100%' ? '100%' : 'auto'; 14404 elem.style.height = style_height=='100%' ? '100%' : 'auto'; 14405 elem.style.boxSizing = 'border-box'; 14406 report_tplbeforeupdateposition(); 14407 var popup_dim = {}; 14408 // width, left or right 14409 var client_width = vp_dim.width; 14410 var width = style_width && style_width.indexOf('%')!=-1 ? Math.round(client_width*parseFloat(style_width)/100) : is_modal && container.offsetWidth==client_width-ns.get_v_scrollbar_width() ? client_width : container.offsetWidth; 14411 var limited_width = width; 14412 if (near_node && !position_number) 14413 { while (true) 14414 { var t_width = width + add_arrow_width; 14415 var left = at_x_side ? dim.left+dim.width : !with_arrow ? dim.left : dim.left+dim.width/2-t_width/2; 14416 var right = at_x_side ? dim.left : !with_arrow ? dim.left+dim.width : dim.left+dim.width/2+t_width/2; 14417 left = Math.min(Math.max(left, vp_dim.left), vp_dim.left+client_width); 14418 right = Math.max(Math.min(right, vp_dim.left+client_width), vp_dim.left); 14419 var overflow_l = (left+t_width) - (vp_dim.left+client_width); 14420 var overflow_r = vp_dim.left - (right-t_width); 14421 var set_left = at_x_side ? !to_left : to_left; 14422 if (set_left && (overflow_l>0 ? overflow_l>overflow_r : left+t_width>client_width) || !set_left && overflow_r>0 && overflow_r>overflow_l) // if left is set to > client_width, browser behavior is to prefer minimum possible content width 14423 { set_left = !set_left; 14424 to_left = !to_left; 14425 } 14426 var overflow_x = set_left ? overflow_l : overflow_r; 14427 if (overflow_x > 0) 14428 { if (prefer_overlay || !at_x_side) // i will extend bounds if 1) prefer_overlay, 2) this will not cause overlaying the near_node 14429 { if (t_width <= client_width) 14430 { left = vp_dim.left + Math.floor((client_width - t_width) / 2); 14431 } 14432 else 14433 { limited_width = client_width - add_arrow_width; 14434 left = vp_dim.left; 14435 } 14436 set_left = true;
14437 extended_bounds_x = true; 14438 at_x_side = false; 14439 } 14440 else 14441 { limited_width = width - overflow_x; 14442 if (at_x_side) 14443 { limited_width = width; 14444 at_x_side = false; 14445 add_arrow_height = add_arrow_width; 14446 add_arrow_width = 0; 14447 continue; 14448 } 14449 } 14450 } 14451 else if (!at_x_side && with_stretch && width<dim.width && left==dim.left && right==dim.left+dim.width) 14452 { limited_width = dim.width; // stretch width to fit near_node 14453 } 14454 if (set_left) 14455 { left += add_arrow_width; 14456 if (!at_x_side && with_arrow && left+arrow_width>dim.left) 14457 { left = Math.max(dim.left-arrow_width, vp_dim.left); 14458 } 14459 popup_dim.left = left; 14460 popup_dim.right = 'auto'; 14461 } 14462 else 14463 { left = right - t_width; 14464 right -= add_arrow_width; 14465 if (!at_x_side && with_arrow && right-arrow_width<dim.left+dim.width) 14466 { right = Math.min(dim.left+dim.width+arrow_width, vp_dim.left+client_width); 14467 left = right - t_width; 14468 } 14469 var body_position = ns.new_elem(document.body).style().position; 14470 right = (!body_position || body_position=='static' ? client_width : document.body.offsetWidth) - right; 14471 popup_dim.right = right; 14472 popup_dim.left = 'auto'; 14473 } 14474 break; 14475 } 14476 } 14477 else if (width > client_width) 14478 { limited_width = client_width; 14479 } 14480 // apply the width now to make content flow vertically. this will change the height which i will examine below 14481 var force_scroll = false; // set when a branch below deliberately asks for scrolling that the limited_* values don't express 14482 if (limited_width!=width || flexible_elem.f_width) 14483 { ns.new_elem(container).add_style({width: limited_width+'px'}); // add_style will also stop current animation 14484 elem.style.width = '100%'; 14485 if (flexible_elem.f_width) 14486 { flexible_elem.elem.style.width = (client_width - flexible_elem.get_siblings_width())+'px'; 14487 if (flexible_elem.elem.scrollWidth > limited_width) 14488 { flexible_elem.elem.style.width = limited_width+'px'; 14489 elem.style.overflowX = 'auto'; 14490 force_scroll = true; 14491 } 14492 } 14493 else 14494 { //elem.style.overflowX = 'auto'; 14495 } 14496 popup_dim.width = limited_width; 14497 } 14498 else if (style_width && style_width.indexOf('%')==-1) 14499 { popup_dim.width = style_width; 14500 }
14501 // height, top or bottom 14502 var client_height = vp_dim.height; 14503 var height = style_height && style_height.indexOf('%')!=-1 ? Math.round(client_height*parseFloat(style_height)/100) : container.offsetHeight; 14504 var limited_height = height; 14505 if (near_node && !position_number) 14506 { var t_height = height + add_arrow_height; 14507 var top = !at_x_side ? dim.top+dim.height : !with_arrow ? dim.top : dim.top+dim.height/2-t_height/2; 14508 var bottom = !at_x_side ? dim.top : !with_arrow ? dim.top+dim.height : dim.top+dim.height/2+t_height/2; 14509 top = Math.min(Math.max(top, vp_dim.top), vp_dim.top+client_height); 14510 bottom = Math.max(Math.min(bottom, vp_dim.top+client_height), vp_dim.top); 14511 var overflow_t = (top+t_height) - (vp_dim.top+client_height); 14512 var overflow_b = vp_dim.top - (bottom-t_height); 14513 var set_top = at_x_side ? to_top : !to_top; 14514 if (set_top && overflow_t>0 && overflow_t>overflow_b || !set_top && overflow_b>0 && overflow_b>overflow_t) 14515 { set_top = !set_top; 14516 to_top = !to_top; 14517 } 14518 var overflow_y = set_top ? overflow_t : overflow_b; 14519 if (overflow_y > 0) 14520 { if (prefer_overlay || at_x_side) // i will extend bounds if 1) prefer_overlay, 2) this will not cause overlaying the near_node 14521 { if (t_height <= client_height) 14522 { top = vp_dim.top + Math.floor((client_height - t_height) / 2); 14523 } 14524 else 14525 { limited_height = client_height - add_arrow_height; 14526 } 14527 set_top = true; 14528 extended_bounds_y = true; 14529 } 14530 else 14531 { limited_height = height - overflow_y; 14532 } 14533 } 14534 if (set_top) 14535 { top += add_arrow_height; 14536 popup_dim.top = top; 14537 popup_dim.bottom = 'auto'; 14538 } 14539 else 14540 { top = bottom - t_height; 14541 bottom -= add_arrow_height; 14542 var body_position = body_position || ns.new_elem(document.body).style().position; 14543 bottom = (!body_position || body_position=='static' ? client_height : document.body.offsetHeight) - bottom; 14544 popup_dim.bottom = bottom; 14545 popup_dim.top = 'auto'; 14546 } 14547 } 14548 else if (height > client_height) 14549 { limited_height = client_height; 14550 } 14551 // 14552 if (limited_height < height) 14553 { var wrapper_height = (by_id.topbar?.offsetHeight | 0) + (by_id.bottombar?.offsetHeight | 0); 14554 if (wrapper_height) 14555 { elem.style.cssText += 'height:100%; height:calc(100% - '+wrapper_height+'px); box-sizing:border-box'; 14556 } 14557 else 14558 { elem.style.height = '100%'; 14559 } 14560 if (flexible_elem.f_height) 14561 { var h = client_height - flexible_elem.get_siblings_height(); 14562 flexible_elem.elem.style.height = h+'px'; 14563 h -= elem.offsetHeight - client_height; 14564 flexible_elem.elem.style.height = h+'px'; 14565 } 14566 else 14567 { //elem.style.overflowY = 'auto'; 14568 } 14569 popup_dim.height = limited_height; 14570 } 14571 // 14572 /* Scroll only when the popup actually had to be CONSTRAINED — which is what the 14573 two `limited_*` values above mean. This element is sized to its content, so 14574 when the design is a single card the box ends up exactly the card's size, and 14575 `overflow:auto` then clips the card's own box-shadow at a square edge: on a 14576 rounded card that reads as hard grey corners, the shadow surviving only where 14577 the corner curves away. A popup that fits has nothing to scroll and loses 14578 nothing by letting the shadow paint. The layout re-runs on resize, so one that 14579 stops fitting gets its scrolling back. 14580 */ 14581 elem.style.overflow = limited_height < height || limited_width < width || force_scroll ? 'auto' : 'visible'; 14582 elem.style.flexGrow = '100000'; 14583 elem.style.flexShrink = '100000'; 14584 // 14585 var ontransitionend_reset_width = false; 14586 var ontransitionend_reset_height = false; 14587 if (!near_node || position_number) 14588 { var xy = get_xy_for_position(vp_dim, limited_width, limited_height, is_position_absolute); 14589 for (var i in xy) 14590 { popup_dim[i] = xy[i]; 14591 } 14592 } 14593 else 14594 { var n; 14595 if (transit && popup_dim.width==null && (n = parseInt(style_width)) && style_width.indexOf('px')==-1 || n<width) 14596 { popup_dim.width = width; 14597 ontransitionend_reset_width = true; 14598 } 14599 if (transit && popup_dim.height==null && (n = parseInt(style_height)) && style_height.indexOf('px')==-1 || n<height) 14600 { popup_dim.height = height; 14601 ontransitionend_reset_height = true; 14602 } 14603 } 14604 // side
14605 side = at_x_side ? (extended_bounds_x ? '' : to_left ? 'left' : 'right') : (extended_bounds_y ? '' : to_top ? 'top' : 'bottom'); 14606 // arrow? 14607 if (near_node && with_arrow && side) 14608 { if (side != hint_prev_side) 14609 { ns.new_elem(hint_arrow_1_elem).add_style 14610 ( subst_params 14611 ( ARROW_STYLES[side], 14612 { WIDTH: arrow_width+border_width, 14613 HEIGHT: arrow_height+border_width, 14614 COLOR: border_color, 14615 X: arrow_width+border_width, 14616 Y: arrow_height+border_width 14617 } 14618 ) 14619 ); 14620 ns.new_elem(hint_arrow_2_elem).add_style 14621 ( subst_params 14622 ( ARROW_STYLES[side], 14623 { WIDTH: arrow_width, 14624 HEIGHT: arrow_height, 14625 COLOR: background_color, 14626 X: arrow_width-1, 14627 Y: arrow_height-1 14628 } 14629 ) 14630 ); 14631 hint_prev_side = side; 14632 } 14633 if (!at_x_side) 14634 { var arrow_x = Math.max(0, Math.min(dim.width/2 - arrow_width - border_width + (dim.left - left), limited_width-arrow_width*2-border_width*4)); 14635 hint_arrow_1_elem.style.left = arrow_x+'px'; 14636 hint_arrow_2_elem.style.left = (arrow_x + border_width)+'px'; 14637 } 14638 else 14639 { var arrow_y = Math.max(0, Math.min(dim.height/2 - arrow_height - border_width + (dim.top - top), limited_height-arrow_height*2-border_width*4)); 14640 hint_arrow_1_elem.style.top = arrow_y+'px'; 14641 hint_arrow_2_elem.style.top = (arrow_y + border_width)+'px'; 14642 } 14643 } 14644 else 14645 { if (hint_arrow_1_elem) 14646 { container.removeChild(hint_arrow_1_elem); 14647 container.removeChild(hint_arrow_2_elem); 14648 hint_prev_side = null; 14649 hint_arrow_1_elem = null; 14650 hint_arrow_2_elem = null; 14651 } 14652 } 14653 // set 14654 if (transit) 14655 { /*if (container_style.width.indexOf('%') == -1) 14656 { container_style.width = style_width=='100%' ? '100%' : 'auto'; 14657 } 14658 if (container_style.height.indexOf('%') == -1) 14659 { container_style.height = 'auto'; 14660 }*/ 14661 if (popup_dim.width) 14662 { container_style.width = orig_width; 14663 } 14664 if (popup_dim.height) 14665 { container_style.height = orig_height; 14666 } 14667 container_style.left = popup_dim.right==null ? orig_left : 'auto'; 14668 container_style.right = popup_dim.right!=null ? orig_right : 'auto'; 14669 container_style.top = popup_dim.bottom==null ? orig_top : 'auto'; 14670 container_style.bottom = popup_dim.bottom!=null ? orig_bottom : 'auto'; 14671 } 14672 container_style.position = near_node && !position_number || is_position_absolute ? 'absolute' : 'fixed'; 14673 var transit_set_end = ns.transit_set_begin 14674 ( transit, 14675 () => 14676 { if (ontransitionend_reset_width) 14677 { container_style.width = 'auto'; 14678 } 14679 if (ontransitionend_reset_height) 14680 { container_style.height = 'auto'; 14681 } 14682 var overflow_x = elem.scrollWidth-elem.offsetWidth; 14683 if (overflow_x>0 && overflow_x<=ns.get_v_scrollbar_width() && elem.style.overflow!='visible') 14684 { // only when this box is already a scroll container: setting overflow-x on a 14685 // visible box would force the other axis to auto and start clipping the shadow 14686 elem.style.overflowX = 'hidden'; 14687 } 14688 report_tplupdateposition(); 14689 } 14690 ); 14691 ns.new_elem(container).set_dim(popup_dim, transit, 'border-box'); 14692 transit_set_end(); 14693 } 14694 function set_case_transit(case_name, transit, to_null_first) 14695 { if (transit) 14696 { // set hard bounds during animation 14697 var container = by_id.container; 14698 var container_style = container.style; 14699 var elem_style = elem.style;
14700 var container_comp_style = ns.new_elem(container).style(); 14701 var elem_comp_style = ns.new_elem(elem).style(); 14702 var container_set_width = container_comp_style.width; 14703 var container_set_height = container_comp_style.height; 14704 var elem_set_width = elem_comp_style.width; 14705 var elem_set_height = elem_comp_style.height; 14706 var cur_w = container_style.width; 14707 var cur_h = container_style.height; 14708 var cur_elem_w = elem_style.width; 14709 var cur_elem_h = elem_style.height; 14710 var cur_overflow_x = elem_style.overflowX; 14711 var cur_overflow_y = elem_style.overflowY; 14712 container_style.width = container_set_width; 14713 container_style.height = container_set_height; 14714 elem_style.overflowX = 'visible'; 14715 elem_style.overflowY = 'visible'; 14716 elem_style.width = elem_set_width; 14717 elem_style.height = elem_set_height; 14718 transit.ontransitionend = ns.pair_funcs 14719 ( transit.ontransitionend, 14720 () => 14721 { ns.deferred // let animator finish 14722 ( () => 14723 { container_style.width = cur_w; 14724 container_style.height = cur_h; 14725 elem_style.overflowX = cur_overflow_x; 14726 elem_style.overflowY = cur_overflow_y; 14727 elem_style.width = cur_elem_w; 14728 elem_style.height = cur_elem_h; 14729 } 14730 ); 14731 } 14732 ); 14733 } 14734 if (to_null_first) 14735 { by_id.root._set_case(null); 14736 } 14737 by_id.root._set_case(case_name, null, false, transit); 14738 } 14739 function show_glass(ondone) 14740 { var e = ns.new_elem('div', null, {style}); 14741 glass = ns.new_elem 14742 ( 'div', 14743 null, 14744 { style: 14745 { position: 'fixed', 14746 top: 0, 14747 left: 0, 14748 width: '100%', 14749 height: '100%', 14750 backgroundColor: with_backdrop_blur ? 'rgba(0,0,0,0)' : 'black', 14751 backdropFilter: with_backdrop_blur ? 'blur(2px)' : '', 14752 opacity: with_backdrop_blur ? '' : '0', 14753 zIndex: e.style.zIndex || 0x7FFFFFFF 14754 }, 14755 _get_name() 14756 { return 'G L A S S'; 14757 }, 14758 _chain(popup, show_wrapper, is_high_priority) 14759 { by_id.root._chain(popup, show_wrapper, is_high_priority); 14760 }, 14761 _hide(transit) 14762 { by_id.root._hide(transit); 14763 } 14764 } 14765 ); 14766 if (innerHeight<document.body.offsetHeight && !ns.IS_SAFARI) // Safari stucks if adding a `<style>` that mentions `html {}` 14767 { glass.innerHTML = `<style>html {position:fixed; overflow-y:scroll; width:100%; left:${-window.scrollX}px; top:${-window.scrollY}px}</style>`; 14768 glass._x = window.scrollX; 14769 glass._y = window.scrollY; 14770 } 14771 ns.new_elem(glass).add_style 14772 ( with_backdrop_blur ? {backgroundColor: 'rgba(0,0,0,.73)'} : {opacity: 0.7}, 14773 { duration: 0.25, 14774 ontransitionend: ondone 14775 } 14776 ); 14777 ns.popup_global_parent._add_elem(document, glass, no_supress_css_where, tracker_params); // pass no_supress_css_where, so glass will use the same div as popup 14778 return glass; 14779 } 14780 function hide_glass() 14781 { if (glass) 14782 { var cur_glass = glass; 14783 glass = null; 14784 cur_glass.style.backdropFilter = ''; 14785 ns.new_elem(cur_glass).add_style 14786 ( with_backdrop_blur ? {backgroundColor: 'rgba(0,0,0,0)'} : {opacity: 0}, 14787 { duration: 0.25, 14788 ontransitionend() 14789 { ns.popup_global_parent._remove_elem(document, cur_glass); 14790 if (cur_glass._x != null) 14791 { window.scrollTo(cur_glass._x, cur_glass._y); 14792 } 14793 } 14794 } 14795 ); 14796 cur_glass._get_name = () => {}; 14797 } 14798 } 14799 var update_position_infreq = ns.infrequent_func(update_position, 100, true); 14800 var title_elem; 14801 return { 14802 ontplready() 14803 { title_elem = title_id && by_id_parent[title_id]; 14804 if (title_elem) 14805 { by_id.title.appendChild(title_elem); 14806 } 14807 elem_parent = by_id.root.parentNode; 14808 by_id.root._ne__switch_elem = elem_parent; // for new_elem.is_node_linked 14809 remove_listener('onresize'); 14810 remove_listener('onscroll'); 14811 remove_listener('onclick'); 14812 remove_listener('onmousedown'); 14813 remove_listener('onmousemove'); 14814 remove_listeners_imgs(); 14815 find_trigger(); 14816 var elem_root = ns.new_elem(elem).get_root_child(); 14817 if (elem_root?._get_cases) 14818 { sw_elem = elem_root; 14819 } 14820 // Before detaching the popup element from it's parent, let other templates to see it attached during parsing. 14821 // This is especially needed to read attributes like data-js dedicated to $list. 14822 (window.queueMicrotask || ns.deferred) 14823 ( () => 14824 { if (want_detach_root) 14825 { want_detach_root = false;
14826 var {root} = by_id; 14827 root.parentNode?.removeChild(root); 14828 if (value) 14829 { root._show(); 14830 } 14831 } 14832 } 14833 ); 14834 // hack styles of parent template 14835 if (by_id_parent.root?.className.indexOf(CONF__PARTIAL_BOOTSTRAP_CLASSES)>=0) 14836 { this.className = by_id_parent.root.className; 14837 } 14838 }, 14839 ontplload() 14840 { is_loaded = true; 14841 update_position_infreq(); 14842 }, 14843 ontplunload() 14844 { is_loaded = false; 14845 }, 14846 ontplreload() 14847 { is_loaded = true; 14848 update_position_infreq(); 14849 }, 14850 ontplcasetransitionend(event) 14851 { if (event.target != this) // not my $switch-elem 14852 { if (is_loaded) 14853 { update_position_infreq(by_id.root && by_id.root.offsetWidth*by_id.root.offsetHeight ? {} : null); 14854 } 14855 } 14856 return false; 14857 }, 14858 container: 14859 { style: 14860 [ with_arrow ? 14861 { backgroundColor: 'white', 14862 border: 'solid #CCC 1px', 14863 boxShadow: '0 0 5px rgba(0, 0, 0, 0.3)', 14864 borderRadius: '5px', 14865 padding: '0.5em' 14866 } 14867 : design=='dialog' ? 14868 { backgroundColor: 'white', 14869 //border: '1px solid #E5ECF6', 14870 boxShadow: '-5px 0 30px rgba(0,0,0,0.3)', 14871 borderRadius: '1em', 14872 overflow: 'hidden' 14873 } 14874 : design=='menu' ? 14875 { backgroundColor: 'white', 14876 border: '0.0625em solid #E5E7EB', 14877 boxShadow: '0 0.375em 1.5em rgba(16,24,40,.06)', 14878 borderRadius: '1em', 14879 overflow: 'hidden' 14880 } 14881 : null, 14882 'z-index:'+0x7FFFFFFF, 14883 style, 14884 position_number>=1 && position_number<=4 || position_number==7 ? {borderTopLeftRadius: 0} : null, 14885 position_number>=1 && position_number<=3 || position_number==6 || position_number==9 ? {borderTopRightRadius: 0} : null, 14886 position_number==3 || position_number>=6 ? {borderBottomRightRadius: 0} : null, 14887 position_number==1 || position_number==4 || position_number>=7 ? {borderBottomLeftRadius: 0} : null, 14888 'display:flex; flex-direction:column; position:relative; box-sizing:border-box' 14889 ], 14890 onmouseenter() 14891 { is_over = true; 14892 if (cancel_deferred_hide) 14893 { cancel_deferred_hide(); 14894 cancel_deferred_hide = null; 14895 } 14896 }, 14897 onmouseleave() 14898 { is_over = false; 14899 } 14900 }, 14901 elem, 14902 topbar: 14903 { style: topbar_style 14904 }, 14905 title: 14906 { innerHTML: title 14907 }, 14908 root: 14909 { _get_name() 14910 { return name; 14911 }, 14912 _set_title(new_title) 14913 { title = new_title; 14914 by_id.title.innerHTML = title; 14915 if (title_elem) 14916 { by_id.title.appendChild(title_elem); 14917 } 14918 }, 14919 _update_position() 14920 { if (is_loaded) 14921 { update_position_infreq(); 14922 } 14923 }, 14924 _show(transit, new_near_node, new_side_preference, new_prefer_overlay, case_props, force_reapply, new_replace_current) 14925 { want_detach_root = false; 14926 if (cancel_deferred_hide) 14927 { cancel_deferred_hide(); 14928 cancel_deferred_hide = null; 14929 } 14930 if (is_modal && !transit) 14931 { transit = {template: 'animator_fade'}; 14932 } 14933 if (new_replace_current != null) 14934 { replace_current = new_replace_current; 14935 } 14936 var root = by_id.root; 14937 if (!get_is_showing()) 14938 { if (!case_props) 14939 { case_props = {}; 14940 } 14941 if (report_tplbeforeshow(case_props, new_near_node || near_node) === false) 14942 { return false; 14943 } 14944 var already = name && ns.popup_global_parent._get_by_name(name)[0] || position_number && (glass || ns.popup_global_parent._get_by_name('G L A S S')[0]); 14945 if (already && already!=root) 14946 { if (replace_current) 14947 { already._hide(transit); 14948 } 14949 else if (already._chain) 14950 { already._chain 14951 ( root, 14952 () => 14953 { root._show(transit, new_near_node, new_side_preference, new_prefer_overlay, case_props, force_reapply); 14954 }, 14955 is_modal 14956 ); 14957 return true; 14958 } 14959 } 14960 if (sw_elem) 14961 { sw_elem._set_case(sw_elem._get_cases()[0], case_props, force_reapply); 14962 } 14963 function do_show() 14964 { want_remove = false; 14965 ns.popup_global_parent._add_elem(document, root, no_supress_css_where, tracker_params); 14966 root._set_case('on'); 14967 finish_design(); 14968 ns.new_elem 14969 ( root, 14970 subelem => 14971 { if (subelem.nodeName == 'IMG')
14972 { if (subelem.complete===false || subelem.naturalWidth===0) 14973 { var listeners = {}; 14974 ns.new_elem(subelem).add_props 14975 ( { onload: 14976 { func() 14977 { update_position_infreq(); // no first argument 14978 }, 14979 listeners, 14980 till_node_linked: root 14981 } 14982 } 14983 ); 14984 listeners_imgs.push(listeners.onload); 14985 } 14986 } 14987 } 14988 ); 14989 update_position_infreq.immediate_call(null, new_near_node, new_side_preference, new_prefer_overlay); 14990 if (transit) 14991 { set_case_transit('on', transit, true); 14992 } 14993 var reg_onclick_outside = !trigger_onmouseover && (hide_onclick_outside || (triggers.length || position_number) && hide_onclick_outside==null); 14994 var reg_onmouseover_outside = triggers.length && trigger_onmouseover; 14995 if (reg_onclick_outside || reg_onmouseover_outside) 14996 { var mousedown_time = 0; 14997 setTimeout 14998 ( () => 14999 { ns.new_elem(document.body).add_props 15000 ( { onclick: !reg_onclick_outside ? null : 15001 { func(event) 15002 { if (event.which==1 && mousedown_time+300>event.timeStamp) 15003 { ontriggeroff(event); 15004 } 15005 }, 15006 listeners, 15007 till_node_linked: root 15008 }, 15009 onmousedown: !reg_onclick_outside ? null : 15010 { func(event) 15011 { mousedown_time = event.timeStamp; 15012 }, 15013 listeners, 15014 till_node_linked: root 15015 }, 15016 onmousemove: !reg_onmouseover_outside ? null : 15017 { func: ontriggeroff, 15018 listeners, 15019 till_node_linked: root 15020 } 15021 } 15022 ); 15023 }, 15024 100 15025 ); 15026 } 15027 ns.new_elem(window).add_props 15028 ( { onresize: 15029 { func() 15030 { update_position_infreq(); 15031 }, 15032 listeners, 15033 till_node_linked: root 15034 } 15035 } 15036 ); 15037 report_tplchange(); 15038 report_tplshow(); 15039 } 15040 var scroll_dim = null; 15041 if (scroll_to_near_node) 15042 { var node = new_near_node || near_node; 15043 if (node) 15044 { var dim = node.cloneNode ? ns.new_elem(node).dim() : node; 15045 var vp_dim = ns.new_elem(window).dim(); 15046 if (dim.left<vp_dim.left || dim.left+dim.width>vp_dim.left+vp_dim.width) 15047 { if (!scroll_dim) scroll_dim = {}; 15048 scroll_dim.left = dim.left - Math.max((vp_dim.width - dim.width)/2, 0); 15049 } 15050 if (dim.top<vp_dim.top || dim.top+dim.height>vp_dim.top+vp_dim.height) 15051 { if (!scroll_dim) scroll_dim = {}; 15052 scroll_dim.top = dim.top - Math.max((vp_dim.height - dim.height)/2, 0); 15053 } 15054 } 15055 } 15056 if (name && (scroll_dim || is_modal)) 15057 { ns.popup_global_parent._add_dummy(name, root); 15058 } 15059 if (scroll_dim) 15060 { ns.new_elem(window).set_dim(scroll_dim, {ontransitionend: do_show}); 15061 } 15062 else if (is_modal) 15063 { show_glass(); 15064 do_show(); 15065 } 15066 else 15067 { do_show(); 15068 } 15069 } 15070 else 15071 { update_position_infreq.immediate_call(null, new_near_node, new_side_preference, new_prefer_overlay); 15072 } 15073 return true; 15074 }, 15075 _set_dim(new_dim, transit) 15076 { var transit_set_end = ns.transit_set_begin(transit, report_tplupdateposition); 15077 ns.new_elem(by_id.container).set_dim(new_dim, transit, 'border-box'); 15078 transit_set_end(); 15079 }, 15080 _hide_by_trigger() 15081 { this._hide(trigger_transit); 15082 }, 15083 _hide(transit) 15084 { if (cancel_deferred_hide) 15085 { cancel_deferred_hide(); 15086 cancel_deferred_hide = null; 15087 } 15088 if (want_detach_root) 15089 { want_detach_root = false; 15090 by_id.root.parentNode.removeChild(by_id.root); 15091 } 15092 if (get_is_showing()) 15093 { if (report_tplbeforehide() === false) 15094 { return; 15095 } 15096 if (is_modal && !transit) 15097 { transit = {template: 'animator_fade'}; 15098 } 15099 remove_listener('onresize'); 15100 remove_listener('onscroll'); 15101 remove_listener('onclick'); 15102 remove_listener('onmousedown'); 15103 remove_listener('onmousemove'); 15104 remove_listeners_imgs(); 15105 let {root} = by_id; 15106 function do_hide() 15107 { ns.popup_global_parent._remove_elem(document, root); 15108 if (chained_popup) 15109 { if (chained_show_wrapper) 15110 { chained_show_wrapper(); 15111 chained_show_wrapper = null; 15112 } 15113 else 15114 { chained_popup._show(); 15115 } 15116 chained_popup = null; 15117 } 15118 for (let c of onhide_callbacks) 15119 { c(); 15120 } 15121 onhide_callbacks.length = 0; 15122 } 15123 hide_glass(); 15124 ns.popup_global_parent._close_popups_opened_inside_this_popup(root); 15125 if (!transit) 15126 { root._set_case(null); 15127 do_hide(); 15128 } 15129 else 15130 { want_remove = true; 15131 transit.ontransitionend = ns.pair_funcs 15132 ( transit.ontransitionend, 15133 () => 15134 { if (want_remove && root.parentNode) 15135 { do_hide(); 15136 } 15137 } 15138 ); 15139 set_case_transit(null, transit); 15140 } 15141 report_tplchange(); 15142 report_tplhide(); 15143 } 15144 else if (transit && transit.ontransitionend) 15145 { transit.ontransitionend(); 15146 } 15147 }, 15148 _switch(new_value, transit, new_near_node, new_side_preference, new_prefer_overlay) 15149 { var {root} = by_id; 15150 if (new_value == null) 15151 { new_value = !get_is_showing(); 15152 } 15153 if (new_value) 15154 { root._show(transit, new_near_node, new_side_preference, new_prefer_overlay); 15155 } 15156 else 15157 { root._hide(transit); 15158 } 15159 }, 15160 _chain(popup, show_wrapper, is_high_priority) 15161 { if (!chained_popup) 15162 { chained_popup = popup; 15163 chained_show_wrapper = show_wrapper; 15164 } 15165 else if (chained_popup != popup) 15166 { if (is_high_priority) 15167 { popup._chain(chained_popup, chained_show_wrapper); 15168 chained_popup = popup; 15169 chained_show_wrapper = show_wrapper; 15170 } 15171 else 15172 { chained_popup._chain(popup, show_wrapper); 15173 } 15174 } 15175 }, 15176 _add_onhide(callback) 15177 { this._remove_onhide(callback); 15178 onhide_callbacks.push(callback); 15179 }, 15180 _remove_onhide(callback) 15181 { let i = onhide_callbacks.indexOf(callback); 15182 if (i != -1) 15183 { onhide_callbacks.splice(i, 1); 15184 } 15185 }, 15186 _get_near_node() 15187 { return near_node; 15188 }, 15189 _set_near_node(new_near_node) 15190 { near_node_id = new_near_node; 15191 near_node = get_defined_near_node(); 15192 }, 15193 _set_trigger_id(new_trigger_id, new_trigger_by_id) 15194 { trigger_id = new_trigger_id; 15195 if (new_trigger_by_id != null) 15196 { trigger_by_id = new_trigger_by_id; 15197 } 15198 find_trigger(); 15199 }, 15200 _set_parent_is_trigger(new_parent_is_trigger) 15201 { parent_is_trigger = new_parent_is_trigger; 15202 find_trigger(); 15203 }, 15204 _get_trigger_elems() 15205 { return triggers; 15206 }, 15207 _get_is_showing: get_is_showing, 15208 _get_value: get_is_showing, 15209 _set_value(new_value) 15210 { this._switch(new_value); 15211 }, 15212 _get_side() 15213 { // at which side of near_node the popup is currently showing? 15214 // can be: 'left', 'right', 'top', 'bottom', '' (empty string if overlaying). 15215 return side; 15216 }, 15217 _set_content(content) 15218 { ns.new_elem(by_id.container).add_props({innerHTML: content}); 15219 hint_prev_side = null; 15220 hint_arrow_1_elem = null; 15221 hint_arrow_2_elem = null; 15222 }, 15223 _get_elem() 15224 { return elem; 15225 }, 15226 _get_side_preference() 15227 { return side_preference; 15228 }, 15229 _set_side_preference(new_side_preference) 15230 { side_preference = new_side_preference; 15231 }, 15232 _get_is_modal() 15233 { return is_modal; 15234 } 15235 }, 15236 button_close: 15237 { style: ['color:#6B7A90; font-size:190%; text-decoration:none; line-height:1', button_close_style], 15238 onclick() 15239 { by_id.root._hide_by_trigger(); 15240 } 15241 } 15242 }; 15243 } 15244); 15245 15246NS['declare_template'] 15247( 'popup_minimizeable', 15248 null, 15249 null, 15250 ['ns', 'elem', 'value', 'remember_state', 'rotate_trigger', 'position_number'], 15251 function(ns, elem, value, remember_state, rotate_trigger, position_number) 15252 { value = !!value; 15253 var trigger = elem.firstElementChild; 15254 if (trigger && trigger.dataset.isTrigger) 15255 { let content = ns.new_elem('div', null, {childNodes: elem.childNodes}); 15256 elem.appendChild(trigger); 15257 elem.appendChild(content); 15258 ns.new_elem(elem).add_style('direction:ltr; display:flex; margin:0; padding:0; visibility:hidden'); 15259 ns.new_elem(trigger).add_style('margin:auto; cursor:pointer');
15260 trigger.onclick = sw; 15261 elem._switch = function(new_value) 15262 { if (new_value == null || value != !!new_value) 15263 { sw(); 15264 } 15265 }; 15266 let animate_prop = ''; 15267 let animate_from_prop = ''; 15268 let freeze_prop = ''; 15269 let freeze_from_prop = ''; 15270 let is_inited = false; 15271 /* position_number: 15272 1 2 3 15273 4 5 6 15274 7 8 9 15275 */ 15276 let session_key = null; 15277 if (window.IntersectionObserver) 15278 { let observer = new IntersectionObserver 15279 ( records => 15280 { if (records[records.length - 1].intersectionRatio > 0) 15281 { observer.disconnect(); 15282 begin(); 15283 } 15284 }, 15285 {threshold: 0.0001} 15286 ); 15287 observer.observe(elem); 15288 } 15289 else 15290 { begin(); 15291 } 15292 function begin() 15293 { init(); 15294 if (value) 15295 { // initially on 15296 value = false; 15297 sw(); // switch to true 15298 } 15299 } 15300 function init() 15301 { if (!is_inited) 15302 { is_inited = true; 15303 // init 15304 let action = ns.get_owner_action && ns.get_owner_action(elem); 15305 let action_id = (action && action.action_id)|0; 15306 if (action_id && remember_state && window.sessionStorage) 15307 { session_key = '_S_T.popup_state_'+action_id; 15308 let state = sessionStorage[session_key]|0; 15309 if (state) 15310 { value = state > 0; 15311 } 15312 } 15313 // 15314 if (!position_number) 15315 { let dim = elem.getBoundingClientRect(); 15316 let horiz = dim.left - (window.innerWidth - dim.right); // center 0; left: dim.width-innerWidth; right: innerWidth-dim.width 15317 let vert = dim.top - (window.innerHeight - dim.bottom); // center: 0; top: dim.height-innerHeight; bottom: innerHeight-dim.height 15318 let w = (window.innerWidth - dim.width)/2; 15319 let h = (window.innerHeight - dim.height)/2; 15320 position_number = (vert<-h ? 1 : vert<=h ? 4 : 7) + (horiz>w ? 2 : horiz>=-w ? 1 : 0); 15321 } 15322 let rotate = ''; 15323 if (position_number==1 || position_number==4 || position_number==7) 15324 { animate_prop = 'marginLeft'; 15325 animate_from_prop = 'offsetWidth'; 15326 freeze_prop = 'height'; 15327 freeze_from_prop = 'offsetHeight'; 15328 rotate = 'transform:rotate(90deg); transform-origin:0 100%; margin-top:-50%'; 15329 } 15330 else if (position_number==3 || position_number==6 || position_number==9) 15331 { animate_prop = 'marginRight'; 15332 animate_from_prop = 'offsetWidth'; 15333 freeze_prop = 'height'; 15334 freeze_from_prop = 'offsetHeight'; 15335 rotate = 'transform:rotate(-90deg); transform-origin:100% 100%; margin-top:-50%'; 15336 } 15337 else if (position_number == 2) 15338 { animate_prop = 'marginTop'; 15339 animate_from_prop = 'offsetHeight'; 15340 freeze_prop = 'width'; 15341 freeze_from_prop = 'offsetWidth'; 15342 } 15343 else if (position_number == 8) 15344 { animate_prop = 'marginBottom'; 15345 animate_from_prop = 'offsetHeight'; 15346 freeze_prop = 'width'; 15347 freeze_from_prop = 'offsetWidth'; 15348 } 15349 if (rotate_trigger && rotate) 15350 { let wrapper = ns.new_elem('div', null, {childNodes: trigger.childNodes, style: rotate}); 15351 trigger.appendChild(wrapper); 15352 } 15353 elem.style.flexDirection = position_number==2 || position_number==5 || position_number==8 ? 'column' : 'row'; 15354 elem.insertBefore(content, position_number==1 || position_number==2 || position_number==4 || position_number==7 ? trigger : null); 15355 elem.style[freeze_prop] = elem[freeze_from_prop]+'px'; 15356 elem.style.visibility = 'visible'; 15357 content.style.display = 'none'; 15358 } 15359 } 15360 function sw(event) 15361 { var no_animation = !event; 15362 init(); 15363 value = !value; 15364 if (session_key) 15365 { sessionStorage[session_key] = value ? 1 : -1; 15366 } 15367 if (value) 15368 { // switch on 15369 content.style.display = ''; 15370 elem.style[freeze_prop] = ''; 15371 content.style[animate_prop] = -content[animate_from_prop]+'px'; 15372 ns.new_elem(content).add_style({[animate_prop]: '0px'}, no_animation ? null : {}); 15373 } 15374 else 15375 { // switch off 15376 elem.style[freeze_prop] = elem[freeze_from_prop]+'px'; 15377 ns.new_elem(content).add_style({[animate_prop]: -content[animate_from_prop]+'px', display: 'none'}, no_animation ? null : {}); 15378 } 15379 // call data-onminimize 15380 let elems = elem.querySelectorAll('*[data-onminimize]'); 15381 for (let i=0; i<elems.length; i++) 15382 { _S_T.pcall(new Function('value', elems[i].dataset.onminimize), elems[i], [value]); 15383 } 15384 } 15385 } 15386 } 15387); 15388 15389NS['declare_template'] 15390( 'round_x', 15391 null, 15392 '<div id="root" onmouseenter="this.style.backgroundColor=this.style.outlineColor" onmouseleave="this.style.backgroundColor=this.style.textDecorationColor"><div></div></div>', 15393 ['ns', 'style'], 15394 function(ns, style) 15395 { const OUTER_STYLE = 15396 { display: 'inline-block', 15397 verticalAlign: 'text-bottom', 15398 width: '2em', 15399 height: '2em', 15400 boxSizing: 'border-box', 15401 borderRadius: '50%', 15402 border: 'solid 0px',
15403 boxShadow: 'rgba(255, 255, 255, 0.2) 0px 1px 0px inset, rgba(0, 0, 0, 0.2) 0px 1px 2px', 15404 padding: '4px', 15405 backgroundColor: 'black', 15406 textDecorationColor: 'black', // i use this to store original backgroundColor 15407 outlineColor: '#FF6666', // i use this for hover color 15408 outlineWidth: '10px', // i use this for line thickness, unit doesn't matter, means % (outlineWidth doesn't accept %) 15409 color: 'white', 15410 cursor: 'pointer', 15411 transition: 'all 0.3s ease' 15412 }; 15413 const INNER_STYLE = 15414 { display: 'block', 15415 width: '100%', 15416 height: '100%', 15417 transition: 'all 0.3s ease 0s', 15418 borderRadius: '50%', 15419 backgroundImage: 'linear-gradient(-45deg, transparent 0%, transparent 45%, currentcolor 45%, currentcolor 55%, transparent 55%, transparent 100%), linear-gradient(45deg, transparent 0%, transparent 45%, currentcolor 45%, currentcolor 55%, transparent 55%, transparent 100%)' 15420 }; 15421 return { 15422 root: 15423 { ontplready() 15424 { this._set_style(style); 15425 }, 15426 _set_style(new_style) 15427 { var {firstChild, style} = this; 15428 ns.new_elem(this).add_style([OUTER_STYLE, new_style]); 15429 ns.new_elem(firstChild).add_style(INNER_STYLE); 15430 if (style.outlineWidth) 15431 { let t = Math.round(parseFloat(style.outlineWidth)/2); 15432 firstChild.style.backgroundImage = firstChild.style.backgroundImage.replace(/45%/g, (50-t)+1+'%').replace(/55%/g, (50+t)+1+'%'); // +1 seems to fix rendering defects 15433 } 15434 style.height = style.width; 15435 style.textDecorationColor = style.backgroundColor; 15436 firstChild.style.borderRadius = style.borderRadius; 15437 } 15438 } 15439 }; 15440 } 15441); 15442 15443// uacss 15444function _new_elem_tmp_6(ns) 15445 { const RE_CSS_TOKENIZER = /\s+|[\w\x80-\xFF\-]+|'(?:[^\\']*\\[\S\s])*[^\\']*'|"(?:[^\\"]*\\[\S\s])*[^\\"]*"|\S/gi; 15446 const XML_ELEMENT_NODE = 1; 15447 15448 var uacss = []; // uacss[0]: suppress all css, uacss[1]: supress except <a>, uacss[2]: supress except a, button, input, textarea, select 15449 var uacss_all = []; // uacss_all[0], uacss_all[1], uacss_all[2]: arrays of selectors 15450 15451 function supress_for_elem(elem, action_id, with_uacss, style_root_elem) 15452 { let with_uacss_level = !with_uacss ? -1 : with_uacss=='-a' ? 1 : with_uacss=='-b' ? 2 : 0; 15453 let mutation_observer = null; 15454 if (action_id>0 && with_uacss_level!=-1) 15455 { var uacss_id = '_'+action_id.toString(36)+'y'; 15456 var uacss_selector = '#'+uacss_id+'.S-3T.S-4T.S-5T.S-6T.S-7T.S-8T.S-9T'; 15457 var uacss_selector_weak = '#'+uacss_id+'.S-3T.S-4T.S-5T.S-6T.S-7T.S-8T'; 15458 use_uacss(uacss_selector_weak, with_uacss_level); 15459 } 15460 if (uacss_id) 15461 { if (elem.nodeType==XML_ELEMENT_NODE && elem.nodeName!='STYLE' && elem.nodeName!='SCRIPT') 15462 { elem.setAttribute('id', uacss_id); 15463 elem.className = (!elem.className ? '' : elem.className+' ') + 'S-3T S-4T S-5T S-6T S-7T S-8T S-9T'; 15464 ns.new_elem(elem).add_style('font-style:normal; font-weight:normal; font-variant:normal; text-decoration:none; text-align:left; text-align:start; line-height:normal; visibility:visible; white-space:normal; cursor:default'); 15465 conv_style_elems(); 15466 if (window.MutationObserver) 15467 { mutation_observer = new MutationObserver(conv_style_elems); 15468 mutation_observer.observe 15469 ( elem, 15470 { childList: true, 15471 subtree: true 15472 } 15473 ); 15474 } 15475 function conv_style_elems() 15476 { // convert all <style> tags 15477 let styles = elem.getElementsByTagName('style'); 15478 for (let i=0, i_end=styles.length; i<i_end; i++) 15479 { let style = styles[i]; 15480 let css = style.textContent || ''; 15481 if (css.indexOf(uacss_selector) == -1) 15482 { let new_css = conv_css_for_action(css, uacss_selector); 15483 if (new_css && style.textContent!=new_css) // to avoid infinite loop 15484 { style.textContent = new_css; 15485 } 15486 } 15487 } 15488 } 15489 } 15490 } 15491 return revert_for_elem; 15492 function revert_for_elem() 15493 { if (mutation_observer) 15494 { mutation_observer.disconnect(); 15495 mutation_observer = null; 15496 } 15497 let u = uacss_all[with_uacss_level]; 15498 if (u) 15499 { let i = u.indexOf(uacss_selector_weak); 15500 if (i != -1) 15501 { u.splice(i, 1); 15502 } 15503 } 15504 } 15505 function use_uacss(selector, level) 15506 { level |= 0; 15507 let uacss_all_i = uacss_all[level] || []; 15508 if (selector && uacss_all_i.indexOf(selector)==-1) 15509 { uacss_all_i.push(selector); 15510 uacss_all[level] = uacss_all_i; 15511 let uacss_i = uacss[level]; 15512 uacss_i?.parentNode?.removeChild(uacss_i); 15513 uacss[level] = ns.new_elem('<style class="$user_agent_css" data-selector="'+uacss_all_i.join(',')+(level==1 ? '" data-not="a' : level==2 ? '" data-not="a,button,select,input,textarea' : '')+'"></style>', style_root_elem || document.getElementsByTagName('head')[0]); 15514 } 15515 } 15516 } 15517 15518 function rollback() 15519 { for (var i=0; i<3; i++) 15520 { let uacss_i = uacss[i]; 15521 uacss_i?.parentNode?.removeChild(uacss_i); 15522 } 15523 uacss = []; 15524 uacss_all = []; 15525 } 15526 15527 function conv_css_for_action(css, inject_root_selector) 15528 { inject_root_selector += ' '; 15529 RE_CSS_TOKENIZER.lastIndex = 0; 15530 let in_at_block = 0; 15531 let in_selector = false;
15532 let in_block = false; 15533 let level = 0; 15534 let new_css = ''; 15535 let new_css_i = 0; 15536 let token; 15537 while ((token = RE_CSS_TOKENIZER.exec(css)?.[0])) 15538 { if (token == '@') 15539 { if (!in_block) 15540 { let at_rule = RE_CSS_TOKENIZER.exec(css)?.[0]; 15541 if (!at_rule) 15542 { break; 15543 } 15544 while ((token = RE_CSS_TOKENIZER.exec(css)?.[0])) 15545 { if (token == ';') 15546 { break; 15547 } 15548 if (token == '{') 15549 { if (at_rule=='media' || at_rule=='layer' || at_rule=='supports') 15550 { in_at_block++; 15551 } 15552 else 15553 { while ((token = RE_CSS_TOKENIZER.exec(css)?.[0]) && token!='}'); 15554 } 15555 break; 15556 } 15557 } 15558 if (!token) 15559 { break; 15560 } 15561 } 15562 } 15563 else if (token == '{') 15564 { if (in_block) 15565 { return ''; 15566 } 15567 in_block = true; 15568 in_selector = false; 15569 } 15570 else if (token == '}') 15571 { if (in_block) 15572 { in_block = false; 15573 level = 0; 15574 } 15575 else if (in_at_block > 0) 15576 { in_at_block--; 15577 } 15578 else 15579 { return ''; 15580 } 15581 } 15582 else if (!in_block && !in_selector && token.trim()) 15583 { if (token.length!=4 || token.toLowerCase()!='html') 15584 { inject(inject_root_selector, token.length); 15585 } 15586 in_selector = true; 15587 } 15588 else if (!in_block && in_selector && level==0 && token==',') 15589 { in_selector = false; 15590 } 15591 else if (in_selector && (token=='(' || token=='[')) 15592 { level++; 15593 } 15594 else if (in_selector && (token==')' || token==']')) 15595 { level--; 15596 } 15597 } 15598 new_css += css.slice(new_css_i); 15599 return new_css; 15600 function inject(what, scroll_back) 15601 { let i = RE_CSS_TOKENIZER.lastIndex - scroll_back; 15602 new_css += css.slice(new_css_i, i); 15603 new_css_i = i; 15604 new_css += what; 15605 } 15606 } 15607 15608 return { 15609 supress_for_elem, 15610 rollback 15611 }; 15612 } 15613 15614// htmlspecialchars 15615function _new_elem_tmp_7(ns) 15616 { return function(text) 15617 { return (''+text).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); 15618 }; 15619 } 15620 15621// load_scripts 15622function _new_elem_tmp_8(ns) 15623 { var load_script_loading = {}; 15624 15625 function load_script(src, checker, ondone) 15626 { let checker_func = 15627 typeof(checker)=='function' ? checker : 15628 checker && function() 15629 { let obj = checker[0]; 15630 for (let i=1; obj && i<checker.length; i++) 15631 { obj = obj[checker[i]]; 15632 } 15633 return obj; 15634 } 15635 ; 15636 function call_ondone(event) 15637 { let handlers = load_script_loading[src]; 15638 if (handlers) 15639 { let result = checker_func ? checker_func() : event?.type!='error'; 15640 for (let i=0; i<handlers.length; i++) 15641 { handlers[i](src, result); 15642 } 15643 delete load_script_loading[src]; 15644 script?.parentNode.removeChild(script); 15645 script = null; 15646 } 15647 } 15648 let i = 0; 15649 function wait() 15650 { !checker_func || checker_func() ? call_ondone() : i++<100 ? ns.deferred(wait, 600) : call_ondone(); 15651 } 15652 let loading = load_script_loading[src]; 15653 if (loading) 15654 { loading.push(ondone); 15655 } 15656 else 15657 { load_script_loading[src] = [ondone]; 15658 if (checker_func?.()) 15659 { call_ondone(); 15660 } 15661 else 15662 { let load_src = src.slice(0, 2)=='//' ? location.protocol+src : src; // sometimes wrong <base> tag is used 15663 var script = ns.new_elem('script', null, {src: load_src, async: true}); 15664 if ('onload' in script) 15665 { script.onload = function() 15666 { i = 95; // wait (100-95)*600 = 3 sec 15667 ns.deferred(wait, 10); // 1) i assume that wait() will trigger call_ondone() immediately 2) timeout is needed at least for Chrome 15668 }; 15669 } 15670 else if ('onreadystatechange' in script) 15671 { script.onreadystatechange = function() 15672 { if (script.readyState == 'complete') 15673 { call_ondone(); 15674 } 15675 else if (script.readyState == 'loaded') 15676 { i = 90; // wait (100-90)*600 = 6 sec 15677 ns.deferred(wait, checker_func ? 20 : 600); 15678 } 15679 }; 15680 } 15681 else 15682 { ns.deferred(wait, checker_func ? 20 : 2000); 15683 } 15684 script.onerror = call_ondone; 15685 (document.getElementsByTagName('head')[0] || document.body || document.documentElement).appendChild(script); 15686 } 15687 } 15688 } 15689 15690 return function(sources_checkers, onload, onerror) 15691 { let sources_checkers_arr = sources_checkers.join ? sources_checkers : [sources_checkers]; 15692 let cur = 0; 15693 let failed_srcs = []; 15694 let call_balancer = new ns.CallBalancer 15695 ( -1, 15696 () => 15697 { if (failed_srcs.length == 0) 15698 { if (++cur < sources_checkers_arr.length) 15699 { call_balancer.dec(); 15700 next(); 15701 } 15702 else 15703 { ns.pcall(onload, window); 15704 } 15705 } 15706 else 15707 { let e = new Error(`load_scripts() failed for ${failed_srcs.length} sources: ${failed_srcs.join(', ')}`); 15708 if (!onerror && !onload) 15709 { ns.error(e); 15710 } 15711 ns.pcall(onerror || onload, window, [e]); 15712 } 15713 }, 15714 (src, result) => 15715 { if (!result) failed_srcs.push(src); 15716 } 15717 ); 15718 function next() 15719 { let sc = sources_checkers_arr[cur]; 15720 for (let src in sc) 15721 { if (sc.hasOwnProperty(src)) 15722 { load_script(src, sc[src], call_balancer.dec()); 15723 } 15724 } 15725 call_balancer.inc(null, true); 15726 } 15727 next(); 15728 }; 15729 } 15730 15731// Location 15732function _new_elem_tmp_9(ns) 15733 { const RE_PROTOCOL = /[^\w\-\+]:/; 15734 const RE_URL_PARAMS = /([^=&]+)(?:=([^&]*))?/g; 15735 15736 return class Location 15737 { constructor(href, in_context_of_location) 15738 { if (typeof(href) == 'object') 15739 { in_context_of_location = href; 15740 href = ''; 15741 } 15742 else if (in_context_of_location) 15743 { if (typeof(in_context_of_location) != 'object') 15744 { in_context_of_location = new Location(in_context_of_location); 15745 } 15746 } 15747 else 15748 { in_context_of_location = location; 15749 } 15750 this.protocol = in_context_of_location.protocol; 15751 this.host = in_context_of_location.host; 15752 this.username = in_context_of_location.username; 15753 this.password = in_context_of_location.password; 15754 this.hostname = in_context_of_location.hostname; 15755 this.port = in_context_of_location.port; 15756 this.pathname = in_context_of_location.pathname; 15757 this.search = in_context_of_location.search; 15758 this.hash = in_context_of_location.hash; 15759 var protocol_set = false;
15760 href = href==null ? '' : (href+'').trim(); 15761 if (href) 15762 { let len_p1 = href.length + 1; 15763 let from = 0; 15764 let c = href.charAt(0); 15765 let no_pathname_slash = false; 15766 if (c!='/' && c!='?' && c!='#') 15767 { let pos = href.indexOf(':'); 15768 if (pos!=-1 && pos<=32 && !RE_PROTOCOL.test(href)) 15769 { if (href.substr(pos+1, 2) != '//') 15770 { no_pathname_slash = true; 15771 } 15772 pos += no_pathname_slash ? 1 : 3; 15773 this.protocol = href.slice(0, pos); 15774 from = pos - 2; // later will +2 15775 protocol_set = true; 15776 } 15777 else 15778 { let pathname = this.pathname; 15779 let pathname_to = pathname.length; 15780 // example: ./path/path?query#hash or ../path/path?query#hash or path/path?query#hash 15781 if (c == '.') 15782 { // example: ./path/path?query#hash or ../path/path?query#hash 15783 while (pathname_to > 0) 15784 { if (href.substr(from, 2) == './') 15785 { from += 2; 15786 } 15787 else if (href.substr(from, 3) == '../') 15788 { from += 3; 15789 let pos = pathname.lastIndexOf('/', pathname_to-1); 15790 pathname_to = pos==-1 ? 0 : pos; 15791 while (pathname_to>0 && pathname.charAt(pathname_to-1)=='/') 15792 { pathname_to--; 15793 } 15794 } 15795 else 15796 { break; 15797 } 15798 while (href.charAt(from) == '/') 15799 { from++; 15800 } 15801 } 15802 } 15803 href = pathname.slice(0, pathname.lastIndexOf('/', pathname_to-1)) + '/' + href.slice(from); 15804 from = 0; 15805 len_p1 = href.length + 1; 15806 c = '/'; // assume: href.charAt(0) == '/' 15807 } 15808 } 15809 if (protocol_set || c=='/') 15810 { // example: //path/path?query#hash or /path/path?query#hash 15811 if (protocol_set || href.charAt(from+1)=='/') 15812 { let pos = Math.min(href.indexOf('/', from+2)+1 || len_p1, href.indexOf('?', from+2)+1 || len_p1, href.indexOf('#', from+2)+1 || len_p1) - 1; 15813 let host = href.slice(from+2, pos); 15814 let username = ''; 15815 let password = ''; 15816 let hostname = host; 15817 let port = ''; 15818 let a_pos = hostname.lastIndexOf('@'); 15819 if (a_pos != -1) 15820 { username = hostname.slice(0, a_pos); 15821 hostname = hostname.slice(a_pos + 1); 15822 a_pos = username.indexOf(':'); 15823 if (a_pos != -1) 15824 { password = username.slice(a_pos + 1); 15825 username = username.slice(0, a_pos); 15826 } 15827 } 15828 a_pos = hostname.lastIndexOf(':'); 15829 if (a_pos != -1) 15830 { port = hostname.slice(a_pos + 1); 15831 hostname = hostname.slice(0, a_pos); 15832 } 15833 this.host = host; 15834 this.username = username; 15835 this.password = password; 15836 this.hostname = hostname; 15837 this.port = port; 15838 from = href.charAt(pos)=='/' ? pos+1 : pos; 15839 } 15840 else 15841 { from++; 15842 } 15843 let pos = Math.min(href.indexOf('?', from)+1 || len_p1, href.indexOf('#', from)+1 || len_p1) - 1; 15844 let pathname = href.slice(from, pos); 15845 this.pathname = no_pathname_slash ? pathname : '/'+pathname; 15846 from = pos; 15847 c = href.charAt(from); 15848 } 15849 if (c == '?') 15850 { // example: ?query#hash 15851 let pos = href.indexOf('#', from+1); 15852 this.search = pos==-1 ? (from==len_p1-2 ? '' : href.slice(from)) : (from+1==pos ? '' : href.slice(from, pos)); 15853 this.hash = pos==-1 || pos==len_p1-2 ? '' : href.slice(pos); 15854 } 15855 else if (c == '#') 15856 { // example: #hash 15857 this.hash = from==len_p1-2 ? '' : href.slice(from); 15858 } 15859 else 15860 { this.search = ''; 15861 this.hash = ''; 15862 } 15863 } 15864 if (!protocol_set && in_context_of_location===location) 15865 { this.protocol += '//'; 15866 } 15867 this.get_href(); // update href 15868 } 15869 15870 get_href() 15871 { this.href = this.protocol + this.host + this.pathname + this.search + this.hash; 15872 return this.href; 15873 } 15874 15875 toString() 15876 { return this.get_href(); 15877 } 15878 15879 params(new_value, set_new_value) 15880 { var params = {}; 15881 var new_str = ''; 15882 var url_changed = false; 15883 this.search.slice(1).replace 15884 ( RE_URL_PARAMS, 15885 function(a, k_enc, v_enc) 15886 { var k = decodeURIComponent(k_enc); 15887 var v = decodeURIComponent(v_enc); 15888 if (new_value?.[k]!==undefined && new_value[k]!=v) 15889 { url_changed = true; // param is to be modified/removed 15890 if (new_value[k] != null) 15891 { params[k] = new_value[k]; 15892 new_str += '&' + k_enc + '=' + encodeURIComponent(new_value[k]); 15893 } 15894 } 15895 else if (!new_value || !set_new_value || new_value[k]===undefined) 15896 { params[k] = v; 15897 if (new_value) 15898 { new_str += '&' + k_enc + '=' + v_enc; 15899 } 15900 } 15901 else 15902 { url_changed = true; // param is to be removed 15903 } 15904 } 15905 ); 15906 if (new_value) 15907 { for (var new_k in new_value) 15908 { if (!(new_k in params) && new_value[new_k]!=null) 15909 { params[new_k] = new_value[new_k]; 15910 url_changed = true; // param is to be added 15911 new_str += '&' + encodeURIComponent(new_k) + '=' + encodeURIComponent(new_value[new_k]); 15912 } 15913 } 15914 } 15915 if (url_changed) 15916 { this.search = !new_str ? '' : '?'+new_str.slice(1); 15917 this.get_href(); // update href 15918 } 15919 return params; 15920 } 15921 }; 15922 } 15923 15924// sleep 15925function _new_elem_tmp_10(ns) 15926 { return function(msec) 15927 { return new Promise(resolve => {ns.deferred(resolve, msec|0)}); 15928 }; 15929 } 15930 15931// random_id 15932function _new_elem_tmp_11(ns) 15933 { return function(id_len) 15934 { const ABC = 'abcdefghijklmnopqrstuvwxyz0123456789'; 15935 id_len = id_len|0 || 4; 15936 let id = ''; 15937 for (let j=0; j<id_len; j++) 15938 { id += ABC.charAt(Math.floor(Math.random() * (j==0 ? 26 : 26+10))); 15939 } 15940 return id; 15941 }; 15942 } 15943 15944// infrequent_func 15945function _new_elem_tmp_12(ns) 15946 { return function(func, period, less) 15947 { var cancel, owner, args, result; 15948 var is_cancelled = false;
15949 period = period|0; 15950 function call() 15951 { cancel = null; 15952 if (args) 15953 { if (!is_cancelled) 15954 { result = ns.pcall(func, owner, args); 15955 } 15956 is_cancelled = false; 15957 owner = null; 15958 args = null; 15959 if (period > 0) 15960 { cancel = ns.deferred(call, period); 15961 } 15962 } 15963 } 15964 var infreq = function() 15965 { is_cancelled = false; 15966 owner = this; 15967 args = arguments; 15968 if (less && period>0) 15969 { if (cancel) cancel(); 15970 } 15971 if (cancel==null && period>0) 15972 { call(); 15973 } 15974 else if (less || period<=0) 15975 { cancel = ns.deferred(call, period); 15976 } 15977 return result; 15978 }; 15979 infreq.cancel = function() 15980 { is_cancelled = true; 15981 }; 15982 infreq.immediate_call = function() 15983 { is_cancelled = false; 15984 owner = this; 15985 args = arguments; 15986 if (cancel) cancel(); 15987 call(); 15988 return result; 15989 }; 15990 infreq.flush = function() 15991 { if (cancel != null) 15992 { call(); 15993 } 15994 }; 15995 infreq.set_period = function(new_period) 15996 { period = new_period|0; 15997 }; 15998 return infreq; 15999 }; 16000 } 16001 16002// ftime 16003function _new_elem_tmp_13(ns) 16004 { let ftime_value = null; 16005 16006 function get_ftime() 16007 { if (!ftime_value) 16008 { var date = new Date(1999, 0, 3); // Sun 16009 var locale = navigator.language; 16010 var weekdays = []; 16011 var weekdays_abbr = []; 16012 var months = []; 16013 var months_abbr = []; 16014 var with_ampm = date.toLocaleTimeString(locale, {hour: 'numeric'}) == date.toLocaleTimeString(locale, {hour: 'numeric', hour12: true}); 16015 var time_format = with_ampm ? '%I:%M:%S %p' : '%H:%M:%S'; 16016 var time_format_short = with_ampm ? '%I:%M %p' : '%H:%M'; 16017 // weekdays 16018 for (var i=0; i<7; i++) 16019 { date.setDate(3+i); 16020 weekdays[i] = date.toLocaleDateString(locale, {weekday: 'long'}); 16021 weekdays_abbr[i] = date.toLocaleDateString(locale, {weekday: 'short'}); 16022 } 16023 // months 16024 for (var i=0; i<12; i++) 16025 { date.setMonth(i); 16026 months[i] = date.toLocaleDateString(locale, {month: 'long'}); 16027 months_abbr[i] = date.toLocaleDateString(locale, {month: 'short'}); 16028 } 16029 // date_format 16030 date.setDate(3); // 1999-12-03 16031 var date_str = date.toLocaleDateString('en', {day: 'numeric', month: 'short', year: '2-digit'}); 16032 var day_str = date.toLocaleDateString('en', {day: 'numeric'}); 16033 var month_str = date.toLocaleDateString('en', {month: 'short'}); 16034 var year_str = date.toLocaleDateString('en', {year: '2-digit'}); 16035 var date_format = date_str.replace(day_str, '%d').replace(month_str, '%b').replace(year_str, '%y'); 16036 // first_weekday 16037 var first_weekday = navigator.language=='he' ? 0 : navigator.language=='ar' ? 6 : 1; 16038 // 16039 ftime_value = {weekdays, weekdays_abbr, months, months_abbr, with_ampm, time_format, time_format_short, date_format, first_weekday}; 16040 } 16041 return ftime_value; 16042 } 16043 16044 function lpad(v, n_digits, fill_char) 16045 { fill_char = fill_char ? ''+fill_char : '0000'; 16046 while (fill_char.length < n_digits) 16047 { fill_char += fill_char; 16048 } 16049 v = ''+v; 16050 return fill_char.slice(0, n_digits-v.length) + v; 16051 } 16052 16053 function cond_zeropad(value, yes) 16054 { return yes ? lpad(value, 2) : value; 16055 } 16056 16057 function strftime(fmt, time, is_gm) 16058 { var locale = navigator.language; 16059 var date = time==null || time==='' ? new Date() : (time instanceof Date) ? time : new Date(time*1000); 16060 var time_zone = is_gm ? 'GMT' : undefined; 16061 return (fmt || '%c').replace 16062 ( /%(0?)(.)/g, 16063 function(all, zero, f) 16064 { if (f == 'j') 16065 { var d = new Date(date.getTime()); 16066 d.setMonth(0); 16067 d.setDate(1); 16068 return (date.getTime() - d.getTime()) / (24*60*60*1000); 16069 } 16070 if (f == 'Z') 16071 { if (is_gm) return "GMT+0000"; 16072 var tz = date.getTimezoneOffset(); 16073 var atz = Math.abs(tz); 16074 var m = atz % 60; 16075 var h = (atz-m) / 60; 16076 var gmt = tz<=0 ? 'GMT+' : 'GMT-'; 16077 return gmt + lpad(h, 2) + lpad(m, 2); 16078 } 16079 return f=='A' ? date.toLocaleDateString(locale, {timeZone: time_zone, weekday: 'long'}) : 16080 f=='a' ? date.toLocaleDateString(locale, {timeZone: time_zone, weekday: 'short'}) : 16081 f=='B' ? date.toLocaleDateString(locale, {timeZone: time_zone, month: 'long'}) : 16082 f=='b' ? date.toLocaleDateString(locale, {timeZone: time_zone, month: 'short'}) : 16083 f=='D' ? date.toLocaleDateString(locale, {timeZone: time_zone, day: 'numeric', month: 'short', year: '2-digit'}) : 16084 f=='x' ? date.toLocaleDateString(locale, {timeZone: time_zone, day: 'numeric', month: 'short'}) : 16085 f=='T' || f=='r' || f=='X' ? date.toLocaleTimeString(locale, {timeZone: time_zone}) : 16086 f=='R' ? date.toLocaleTimeString(locale, {timeZone: time_zone, hour: 'numeric', minute: 'numeric'}) : 16087 f=='c' ? strftime('%D %T', date, is_gm) :
16088 f=='d' ? cond_zeropad(is_gm ? date.getUTCDate() : date.getDate(), zero) : 16089 f=='H' ? cond_zeropad(is_gm ? date.getUTCHours() : date.getHours(), zero) : 16090 f=='I' ? cond_zeropad(((is_gm ? date.getUTCHours() : date.getHours()) % 12) || 12, zero) : 16091 f=='m' ? cond_zeropad((is_gm ? date.getUTCMonth() : date.getMonth())+1, zero) : 16092 f=='M' ? cond_zeropad(is_gm ? date.getUTCMinutes() : date.getMinutes(), zero) : 16093 f=='p' ? ((is_gm ? date.getUTCHours() : date.getHours())<12 ? 'AM' : 'PM') : 16094 f=='S' ? cond_zeropad(is_gm ? date.getUTCSeconds() : date.getSeconds(), zero) : 16095 f=='y' ? (is_gm ? date.getUTCFullYear() : date.getYear()) % 100 : 16096 f=='Y' ? (is_gm ? date.getUTCFullYear() : date.getFullYear()) : 16097 f; 16098 } 16099 ); 16100 } 16101 16102 function gmstrftime(fmt, time) 16103 { return strftime(fmt, time, true); 16104 } 16105 16106 const obj = 16107 { get_ftime, 16108 strftime, 16109 gmstrftime, 16110 }; 16111 16112 return obj; 16113 } 16114 16115// get_cookie 16116function _new_elem_tmp_14(ns) 16117 { return function(name) 16118 { var value; 16119 document.cookie.replace 16120 ( /(?:^|;)\s*([^;=]+)=([^;]+)/g, 16121 function(a, n, v) 16122 { if (value===undefined && decodeURIComponent(n)==name) 16123 { value = decodeURIComponent(v); 16124 } 16125 } 16126 ); 16127 return value; 16128 }; 16129 } 16130 16131// strhash 16132function _new_elem_tmp_15(ns) 16133 { return function(str) 16134 { const HASHWORDBITS = 32; 16135 var sum = 0; 16136 var shift_by = 8; 16137 for (let i=0, i_end=str.length; i<i_end; i++) 16138 { if (i == HASHWORDBITS/8) 16139 { shift_by = 7; 16140 } 16141 let c = (str.charCodeAt(i) ^ i) & 0xFF; 16142 let high = sum & (0xFF << (HASHWORDBITS - shift_by)); 16143 sum ^= high; // zero high 8 or 7 bits 16144 sum = (sum << shift_by) ^ (high >> (HASHWORDBITS - shift_by)) ^ c; 16145 } 16146 return sum; 16147 }; 16148 } 16149 16150if (!('ElemsAnnotator' in NS)) NS['ElemsAnnotator'] = _new_elem_tmp_0.call(NS, NS); 16151if (!('inject_code' in NS)) NS['inject_code'] = _new_elem_tmp_2.call(NS, NS); 16152if (!('joyquery' in NS)) NS['joyquery'] = _new_elem_tmp_3.call(NS, NS); 16153if (!('popup_global_parent' in NS)) NS['popup_global_parent'] = _new_elem_tmp_5.call(NS, NS); 16154if (!('uacss' in NS)) NS['uacss'] = _new_elem_tmp_6.call(NS, NS); 16155if (!('htmlspecialchars' in NS)) NS['htmlspecialchars'] = _new_elem_tmp_7.call(NS, NS); 16156if (!('load_scripts' in NS)) NS['load_scripts'] = _new_elem_tmp_8.call(NS, NS); 16157if (!('Location' in NS)) NS['Location'] = _new_elem_tmp_9.call(NS, NS); 16158if (!('sleep' in NS)) NS['sleep'] = _new_elem_tmp_10.call(NS, NS); 16159if (!('random_id' in NS)) NS['random_id'] = _new_elem_tmp_11.call(NS, NS); 16160if (!('infrequent_func' in NS)) NS['infrequent_func'] = _new_elem_tmp_12.call(NS, NS); 16161if (!('ftime' in NS)) NS['ftime'] = _new_elem_tmp_13.call(NS, NS); 16162if (!('get_cookie' in NS)) NS['get_cookie'] = _new_elem_tmp_14.call(NS, NS); 16163if (!('strhash' in NS)) NS['strhash'] = _new_elem_tmp_15.call(NS, NS); 16164if (!('_s_t_loaded' in NS)) NS['_s_t_loaded'] = _new_elem_tmp_1.call(NS, NS, NS["infrequent_func"], NS["ftime"], NS["new_elem"]); 16165if (!('joyquery_ext' in NS)) NS['joyquery_ext'] = _new_elem_tmp_4.call(NS, NS, NS["joyquery"]); 16166 16167})(); // end ns
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.