github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/docs/themes/hugo-theme-relearn/static/js/theme.js (about) 1 window.relearn = window.relearn || {}; 2 3 var theme = true; 4 var isIE = /*@cc_on!@*/false || !!document.documentMode; 5 if( isIE ){ 6 // we don't support sidebar flyout in IE 7 document.querySelector( 'body' ).classList.remove( 'mobile-support' ); 8 } 9 else{ 10 document.querySelector( 'body' ).classList.add( 'mobile-support' ); 11 } 12 13 var isPrint = document.querySelector( 'body' ).classList.contains( 'print' ); 14 15 var isRtl = document.querySelector( 'html' ).getAttribute( 'dir' ) == 'rtl'; 16 var lang = document.querySelector( 'html' ).getAttribute( 'lang' ); 17 var dir_padding_start = 'padding-left'; 18 var dir_padding_end = 'padding-right'; 19 var dir_key_start = 37; 20 var dir_key_end = 39; 21 var dir_scroll = 1; 22 if( isRtl && !isIE ){ 23 dir_padding_start = 'padding-right'; 24 dir_padding_end = 'padding-left'; 25 dir_key_start = 39; 26 dir_key_end = 37; 27 dir_scroll = -1; 28 } 29 30 var touchsupport = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0) 31 32 var formelements = 'button, datalist, fieldset, input, label, legend, meter, optgroup, option, output, progress, select, textarea'; 33 34 // PerfectScrollbar 35 var psc; 36 var psm; 37 var pst = new Map(); 38 var elc = document.querySelector('#R-body-inner'); 39 40 function regexEscape( s ){ 41 return s.replace( /[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&' ); 42 } 43 44 function documentFocus(){ 45 elc.focus(); 46 psc && psc.scrollbarY.focus(); 47 } 48 49 function scrollbarWidth(){ 50 // https://davidwalsh.name/detect-scrollbar-width 51 // Create the measurement node 52 var scrollDiv = document.createElement("div"); 53 scrollDiv.className = "scrollbar-measure"; 54 document.body.appendChild(scrollDiv); 55 // Get the scrollbar width 56 var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; 57 // Delete the DIV 58 document.body.removeChild(scrollDiv); 59 return scrollbarWidth; 60 } 61 62 var scrollbarSize = scrollbarWidth(); 63 function adjustContentWidth(){ 64 var start = parseFloat( getComputedStyle( elc ).getPropertyValue( dir_padding_start ) ); 65 var end = start; 66 if( elc.scrollHeight > elc.clientHeight ){ 67 // if we have a scrollbar reduce the end margin by the scrollbar width 68 end = Math.max( 0, start - scrollbarSize ); 69 } 70 elc.style[ dir_padding_end ] = '' + end + 'px'; 71 } 72 73 function fixCodeTabs(){ 74 /* if only a single code block is contained in the tab and no style was selected, treat it like style=code */ 75 var codeTabContents = Array.from( document.querySelectorAll( '.tab-content.tab-panel-style' ) ).filter( function( tabContent ){ 76 return tabContent.querySelector( '*:scope > .tab-content-text > div.highlight:only-child, *:scope > .tab-content-text > pre:not(.mermaid).pre-code:only-child'); 77 }); 78 79 codeTabContents.forEach( function( tabContent ){ 80 var tabId = tabContent.dataset.tabItem; 81 var tabPanel = tabContent.parentNode.parentNode; 82 var tabButton = tabPanel.querySelector( '.tab-nav-button.tab-panel-style[data-tab-item="'+tabId+'"]' ); 83 if( tabContent.classList.contains( 'initial' ) ){ 84 tabButton.classList.remove( 'initial' ); 85 tabButton.classList.add( 'code' ); 86 tabContent.classList.remove( 'initial' ); 87 tabContent.classList.add( 'code' ); 88 } 89 // mark code blocks for FF without :has() 90 tabContent.classList.add( 'codify' ); 91 }); 92 } 93 94 function switchTab(tabGroup, tabId) { 95 var tabs = Array.from( document.querySelectorAll( '.tab-panel[data-tab-group="'+tabGroup+'"]' ) ).filter( function( e ){ 96 return !!e.querySelector( '[data-tab-item="'+tabId+'"]' ); 97 }); 98 var allTabItems = tabs && tabs.reduce( function( a, e ){ 99 return a.concat( Array.from( e.querySelectorAll( '[data-tab-item]' ) ).filter( function( es ){ 100 return es.parentNode.parentNode == e; 101 }) ); 102 }, [] ); 103 var targetTabItems = tabs && tabs.reduce( function( a, e ){ 104 return a.concat( Array.from( e.querySelectorAll( '[data-tab-item="'+tabId+'"]' ) ).filter( function( es ){ 105 return es.parentNode.parentNode == e; 106 }) ); 107 }, [] ); 108 109 // if event is undefined then switchTab was called from restoreTabSelection 110 // so it's not a button event and we don't need to safe the selction or 111 // prevent page jump 112 var isButtonEvent = event && event.target && event.target.getBoundingClientRect; 113 if(isButtonEvent){ 114 // save button position relative to viewport 115 var yposButton = event.target.getBoundingClientRect().top; 116 } 117 118 allTabItems && allTabItems.forEach( function( e ){ e.classList.remove( 'active' ); e.removeAttribute( 'tabindex' ); }); 119 targetTabItems && targetTabItems.forEach( function( e ){ e.classList.add( 'active' ); e.setAttribute( 'tabindex', '-1' ); }); 120 121 if(isButtonEvent){ 122 initMermaid( true ); 123 124 // reset screen to the same position relative to clicked button to prevent page jump 125 var yposButtonDiff = event.target.getBoundingClientRect().top - yposButton; 126 window.scrollTo(window.scrollX, window.scrollY+yposButtonDiff); 127 128 // Store the selection to make it persistent 129 if(window.localStorage){ 130 var selectionsJSON = window.localStorage.getItem(window.relearn.absBaseUri+"/tab-selections"); 131 if(selectionsJSON){ 132 var tabSelections = JSON.parse(selectionsJSON); 133 }else{ 134 var tabSelections = {}; 135 } 136 tabSelections[tabGroup] = tabId; 137 window.localStorage.setItem(window.relearn.absBaseUri+"/tab-selections", JSON.stringify(tabSelections)); 138 } 139 } 140 } 141 142 function restoreTabSelections() { 143 if(window.localStorage){ 144 var selectionsJSON = window.localStorage.getItem(window.relearn.absBaseUri+"/tab-selections"); 145 if(selectionsJSON){ 146 var tabSelections = JSON.parse(selectionsJSON); 147 }else{ 148 var tabSelections = {}; 149 } 150 Object.keys(tabSelections).forEach(function(tabGroup) { 151 var tabItem = tabSelections[tabGroup]; 152 switchTab(tabGroup, tabItem); 153 }); 154 } 155 } 156 157 function initMermaid( update, attrs ) { 158 var doBeside = true; 159 var isImageRtl = false; 160 161 // we are either in update or initialization mode; 162 // during initialization, we want to edit the DOM; 163 // during update we only want to execute if something changed 164 var decodeHTML = function( html ){ 165 var txt = document.createElement( 'textarea' ); 166 txt.innerHTML = html; 167 return txt.value; 168 }; 169 170 var parseGraph = function( graph ){ 171 // See https://github.com/mermaid-js/mermaid/blob/9a080bb975b03b2b1d4ef6b7927d09e6b6b62760/packages/mermaid/src/diagram-api/frontmatter.ts#L10 172 // for reference on the regex originally taken from jekyll 173 var YAML=1; 174 var INIT=2; 175 var GRAPH=3; 176 var d = /^(?:\s*[\n\r])*(?:-{3}(\s*[\n\r](?:.*?)[\n\r])-{3}(?:\s*[\n\r]+)+)?(?:\s*(?:%%\s*\{\s*\w+\s*:([^%]*?)%%\s*[\n\r]?))?(.*)$/s 177 var m = d.exec( graph ); 178 var yaml = {}; 179 var dir = {}; 180 var content = graph; 181 if( m && m.length == 4 ){ 182 yaml = m[YAML] ? jsyaml.load( m[YAML] ) : yaml; 183 dir = m[INIT] ? JSON.parse( '{ "init": ' + m[INIT] ).init : dir; 184 content = m[GRAPH] ? m[GRAPH] : content; 185 } 186 var ret = { yaml: yaml, dir: dir, content: content.trim() } 187 return ret; 188 }; 189 190 var serializeGraph = function( graph ){ 191 var yamlPart = ''; 192 if( Object.keys( graph.yaml ).length ){ 193 yamlPart = '---\n' + jsyaml.dump( graph.yaml ) + '---\n'; 194 } 195 var dirPart = ''; 196 if( Object.keys( graph.dir ).length ){ 197 dirPart = '%%{init: ' + JSON.stringify( graph.dir ) + '}%%\n'; 198 } 199 return yamlPart + dirPart + graph.content; 200 }; 201 202 var init_func = function( attrs ){ 203 var is_initialized = false; 204 var theme = attrs.theme; 205 document.querySelectorAll('.mermaid').forEach( function( element ){ 206 var parse = parseGraph( decodeHTML( element.innerHTML ) ); 207 208 if( parse.yaml.theme ){ 209 parse.yaml.relearn_user_theme = true; 210 } 211 if( parse.dir.theme ){ 212 parse.dir.relearn_user_theme = true; 213 } 214 if( !parse.yaml.relearn_user_theme && !parse.dir.relearn_user_theme ){ 215 parse.yaml.theme = theme; 216 } 217 is_initialized = true; 218 219 var graph = serializeGraph( parse ); 220 var new_element = document.createElement( 'div' ); 221 Array.from( element.attributes ).forEach( function( attr ){ 222 new_element.setAttribute( attr.name, attr.value ); 223 element.removeAttribute( attr.name ); 224 }); 225 new_element.classList.add( 'mermaid-container' ); 226 new_element.classList.remove( 'mermaid' ); 227 element.classList.add( 'mermaid' ); 228 229 element.innerHTML = graph; 230 if( element.offsetParent !== null ){ 231 element.classList.add( 'mermaid-render' ); 232 } 233 new_element.innerHTML = '<div class="mermaid-code">' + graph + '</div>' + element.outerHTML; 234 element.parentNode.replaceChild( new_element, element ); 235 }); 236 return is_initialized; 237 } 238 239 var update_func = function( attrs ){ 240 var is_initialized = false; 241 var theme = attrs.theme; 242 document.querySelectorAll( '.mermaid-container' ).forEach( function( e ){ 243 var element = e.querySelector( '.mermaid' ); 244 var code = e.querySelector( '.mermaid-code' ); 245 var parse = parseGraph( decodeHTML( code.innerHTML ) ); 246 247 if( element.classList.contains( 'mermaid-render' ) ){ 248 if( parse.yaml.relearn_user_theme || parse.dir.relearn_user_theme ){ 249 return; 250 } 251 if( parse.yaml.theme == theme || parse.dir.theme == theme ){ 252 return; 253 } 254 } 255 if( element.offsetParent !== null ){ 256 element.classList.add( 'mermaid-render' ); 257 } 258 else{ 259 element.classList.remove( 'mermaid-render' ); 260 return; 261 } 262 is_initialized = true; 263 264 parse.yaml.theme = theme; 265 var graph = serializeGraph( parse ); 266 element.removeAttribute('data-processed'); 267 element.innerHTML = graph; 268 code.innerHTML = graph; 269 }); 270 return is_initialized; 271 }; 272 273 var state = this; 274 if( update && !state.is_initialized ){ 275 return; 276 } 277 if( typeof variants == 'undefined' ){ 278 return; 279 } 280 if( typeof mermaid == 'undefined' || typeof mermaid.mermaidAPI == 'undefined' ){ 281 return; 282 } 283 284 if( !state.is_initialized ){ 285 state.is_initialized = true; 286 window.addEventListener( 'beforeprint', function(){ 287 initMermaid( true, { 288 'theme': variants.getColorValue( 'PRINT-MERMAID-theme' ), 289 }); 290 }.bind( this ) ); 291 window.addEventListener( 'afterprint', function(){ 292 initMermaid( true ); 293 }.bind( this ) ); 294 } 295 296 attrs = attrs || { 297 'theme': variants.getColorValue( 'MERMAID-theme' ), 298 }; 299 300 var search; 301 if( update ){ 302 search = sessionStorage.getItem( window.relearn.absBaseUri+'/search-value' ); 303 unmark(); 304 } 305 var is_initialized = ( update ? update_func( attrs ) : init_func( attrs ) ); 306 if( is_initialized ){ 307 mermaid.initialize( Object.assign( { "securityLevel": "antiscript", "startOnLoad": false }, window.relearn.mermaidConfig, { theme: attrs.theme } ) ); 308 mermaid.run({ 309 postRenderCallback: function( id ){ 310 // zoom for Mermaid 311 // https://github.com/mermaid-js/mermaid/issues/1860#issuecomment-1345440607 312 var svgs = d3.selectAll( 'body:not(.print) .mermaid-container.zoomable > .mermaid > #' + id ); 313 svgs.each( function(){ 314 var parent = this.parentElement; 315 // we need to copy the maxWidth, otherwise our reset button will not align in the upper right 316 parent.style.maxWidth = this.style.maxWidth || this.getAttribute( 'width' ); 317 // if no unit is given for the width 318 parent.style.maxWidth = parent.style.maxWidth || 'calc( ' + this.getAttribute( 'width' ) + 'px + 1rem )'; 319 var svg = d3.select( this ); 320 svg.html( '<g>' + svg.html() + '</g>' ); 321 var inner = svg.select( '*:scope > g' ); 322 parent.insertAdjacentHTML( 'beforeend', '<span class="svg-reset-button" title="' + window.T_Reset_view + '"><i class="fas fa-undo-alt"></i></span>' ); 323 var button = parent.querySelector( '.svg-reset-button' ); 324 var zoom = d3.zoom().on( 'zoom', function( e ){ 325 inner.attr( 'transform', e.transform ); 326 if( e.transform.k == 1 && e.transform.x == 0 && e.transform.y == 0 ){ 327 button.classList.remove( 'zoomed' ); 328 } 329 else{ 330 button.classList.add( 'zoomed' ); 331 } 332 }); 333 button.addEventListener( 'click', function( event ){ 334 svg.transition() 335 .duration( 350 ) 336 .call( zoom.transform, d3.zoomIdentity ); 337 this.setAttribute( 'aria-label', window.T_View_reset ); 338 this.classList.add( 'tooltipped', 'tooltipped-' + (doBeside ? 'w' : 's'+(isImageRtl?'e':'w')) ); 339 }); 340 button.addEventListener( 'mouseleave', function() { 341 if( this.classList.contains( 'tooltipped' ) ){ 342 this.classList.remove( 'tooltipped', 'tooltipped-w', 'tooltipped-se', 'tooltipped-sw' ); 343 this.removeAttribute( 'aria-label' ); 344 } 345 }); 346 svg.call( zoom ); 347 }); 348 }, 349 querySelector: '.mermaid.mermaid-render', 350 suppressErrors: true 351 }); 352 } 353 if( update && search && search.length ){ 354 sessionStorage.setItem( window.relearn.absBaseUri+'/search-value', search ); 355 mark(); 356 } 357 } 358 359 function initOpenapi( update, attrs ){ 360 if( isIE ){ 361 return; 362 } 363 364 var state = this; 365 if( update && !state.is_initialized ){ 366 return; 367 } 368 if( typeof variants == 'undefined' ){ 369 return; 370 } 371 372 if( !state.is_initialized ){ 373 state.is_initialized = true; 374 window.addEventListener( 'beforeprint', function(){ 375 initOpenapi( true, { isPrintPreview: true } ); 376 }.bind( this ) ); 377 window.addEventListener( 'afterprint', function(){ 378 initOpenapi( true, { isPrintPreview: false } ); 379 }.bind( this ) ); 380 } 381 382 attrs = attrs || { 383 isPrintPreview: false 384 }; 385 386 function addFunctionToResizeEvent(){ 387 388 } 389 function getFirstAncestorByClass(){ 390 391 } 392 function renderOpenAPI(oc) { 393 var relBasePath = window.relearn.relBasePath; 394 var mod = window.relearn.themeVariantModifier; 395 var buster = window.themeUseOpenapi.assetsBuster ? '?' + window.themeUseOpenapi.assetsBuster : ''; 396 var print = isPrint || attrs.isPrintPreview ? "PRINT-" : ""; 397 var theme = print ? `${relBasePath}/css/theme-relearn-light${mod}.css${buster}` : document.querySelector( '#R-variant-style' ).attributes.href.value 398 var swagger_theme = variants.getColorValue( print + 'OPENAPI-theme' ); 399 var swagger_code_theme = variants.getColorValue( print + 'OPENAPI-CODE-theme' ); 400 401 const openapiId = 'relearn-swagger-ui'; 402 const openapiIframeId = openapiId + "-iframe"; 403 const openapiIframe = document.getElementById(openapiIframeId); 404 if (openapiIframe) { 405 openapiIframe.remove(); 406 } 407 const openapiErrorId = openapiId + '-error'; 408 const openapiError = document.getElementById(openapiErrorId); 409 if (openapiError) { 410 openapiError.remove(); 411 } 412 const oi = document.createElement('iframe'); 413 oi.id = openapiIframeId; 414 oi.classList.toggle('sc-openapi-iframe', true); 415 oi.srcdoc = 416 '<!doctype html>' + 417 '<html lang="' + lang + '" dir="' + (isRtl ? 'rtl' : 'ltr') + '">' + 418 '<head>' + 419 '<link rel="stylesheet" href="' + window.themeUseOpenapi.css + '">' + 420 '<link rel="stylesheet" href="' + theme + '">' + 421 '<link rel="stylesheet" href="' + relBasePath + '/css/swagger.css' + buster + '">' + 422 '<link rel="stylesheet" href="' + relBasePath + '/css/swagger-' + swagger_theme + '.css' + buster + '">' + 423 '</head>' + 424 '<body>' + 425 '<a class="relearn-expander" href="" onclick="return relearn_collapse_all()">Collapse all</a>' + 426 '<a class="relearn-expander" href="" onclick="return relearn_expand_all()">Expand all</a>' + 427 '<div id="relearn-swagger-ui"></div>' + 428 '<script>' + 429 'function relearn_expand_all(){' + 430 'document.querySelectorAll( ".opblock-summary-control[aria-expanded=false]" ).forEach( btn => btn.click() );' + 431 'document.querySelectorAll( ".model-container > .model-box > button[aria-expanded=false]" ).forEach( btn => btn.click() );' + 432 'return false;' + 433 '}' + 434 'function relearn_collapse_all(){' + 435 'document.querySelectorAll( ".opblock-summary-control[aria-expanded=true]" ).forEach( btn => btn.click() );' + 436 'document.querySelectorAll( ".model-container > .model-box > .model-box > .model > span > button[aria-expanded=true]" ).forEach( btn => btn.click() );' + 437 'return false;' + 438 '}' + 439 '</script>' + 440 '</body>' + 441 '</html>'; 442 oi.height = '100%'; 443 oi.width = '100%'; 444 oi.onload = function(){ 445 const openapiWrapper = getFirstAncestorByClass(oc, 'sc-openapi-wrapper'); 446 const openapiPromise = new Promise( function(resolve){ resolve() }); 447 openapiPromise 448 .then( function(){ 449 var options = { 450 defaultModelsExpandDepth: 2, 451 defaultModelExpandDepth: 2, 452 docExpansion: isPrint || attrs.isPrintPreview ? 'full' : 'list', 453 domNode: oi.contentWindow.document.getElementById(openapiId), 454 filter: !( isPrint || attrs.isPrintPreview ), 455 layout: 'BaseLayout', 456 onComplete: function(){ 457 if( isPrint || attrs.isPrintPreview ){ 458 oi.contentWindow.document.querySelectorAll( '.model-container > .model-box > button[aria-expanded=false]' ).forEach( function(btn){ btn.click() }); 459 setOpenAPIHeight(oi); 460 } 461 }, 462 plugins: [ 463 SwaggerUIBundle.plugins.DownloadUrl 464 ], 465 presets: [ 466 SwaggerUIBundle.presets.apis, 467 SwaggerUIStandalonePreset, 468 ], 469 syntaxHighlight: { 470 activated: true, 471 theme: swagger_code_theme, 472 }, 473 validatorUrl: 'none', 474 }; 475 if( oc.dataset.openapiSpec ){ 476 try{ 477 Object.assign( options, { spec: JSON.parse( oc.dataset.openapiSpec ) }); 478 } catch( err ){ 479 try{ 480 Object.assign( options, { spec: jsyaml.load( oc.dataset.openapiSpec ) }); 481 } catch( err ){ 482 console.error( 'OpenAPI: file "' + oc.dataset.openapiUrl + '" could not be parsed as JSON or YAML'); 483 } 484 } 485 } 486 else{ 487 Object.assign( options, { url: oc.dataset.openapiUrl }); 488 } 489 SwaggerUIBundle( options ); 490 }) 491 .then( function(){ 492 let observerCallback = function () { 493 setOpenAPIHeight(oi); 494 }; 495 let observer = new MutationObserver(observerCallback); 496 observer.observe(oi.contentWindow.document.documentElement, { 497 childList: true, 498 subtree: true, 499 }); 500 }) 501 .then( function(){ 502 if (openapiWrapper) { 503 openapiWrapper.classList.toggle('is-loading', false); 504 } 505 setOpenAPIHeight(oi); 506 }) 507 .catch( function(error){ 508 const ed = document.createElement('div'); 509 ed.classList.add('sc-alert', 'sc-alert-error'); 510 ed.innerHTML = error; 511 ed.id = openapiErrorId; 512 while (oc.lastChild) { 513 oc.removeChild(oc.lastChild); 514 } 515 if (openapiWrapper) { 516 openapiWrapper.classList.toggle('is-loading', false); 517 openapiWrapper.insertAdjacentElement('afterbegin', ed); 518 } 519 }); 520 }; 521 oc.appendChild(oi); 522 } 523 function setOpenAPIHeight(oi) { 524 // add empirical offset if in print preview (GC 103) 525 oi.style.height = 526 (oi.contentWindow.document.documentElement.getBoundingClientRect().height + (attrs.isPrintPreview ? 200 : 0) )+ 527 'px'; 528 } 529 function resizeOpenAPI() { 530 let divi = document.getElementsByClassName('sc-openapi-iframe'); 531 for (let i = 0; i < divi.length; i++) { 532 setOpenAPIHeight(divi[i]); 533 } 534 }; 535 let divo = document.getElementsByClassName('sc-openapi-container'); 536 for (let i = 0; i < divo.length; i++) { 537 renderOpenAPI(divo[i]); 538 } 539 if (divo.length) { 540 addFunctionToResizeEvent(resizeOpenAPI); 541 } 542 } 543 544 function initAnchorClipboard(){ 545 document.querySelectorAll( 'h1~h2,h1~h3,h1~h4,h1~h5,h1~h6').forEach( function( element ){ 546 var url = encodeURI( (document.location.origin == "null" ? (document.location.protocol + "//" + document.location.host) : document.location.origin )+ document.location.pathname); 547 var link = url + "#" + element.id; 548 var new_element = document.createElement( 'span' ); 549 new_element.classList.add( 'anchor' ); 550 new_element.setAttribute( 'title', window.T_Copy_link_to_clipboard ); 551 new_element.setAttribute( 'data-clipboard-text', link ); 552 new_element.innerHTML = '<i class="fas fa-link fa-lg"></i>'; 553 element.appendChild( new_element ); 554 }); 555 556 var anchors = document.querySelectorAll( '.anchor' ); 557 for( var i = 0; i < anchors.length; i++ ) { 558 anchors[i].addEventListener( 'mouseleave', function( e ){ 559 this.removeAttribute( 'aria-label' ); 560 this.classList.remove( 'tooltipped', 'tooltipped-se', 'tooltipped-sw' ); 561 }); 562 } 563 564 var clip = new ClipboardJS( '.anchor' ); 565 clip.on( 'success', function( e ){ 566 e.clearSelection(); 567 e.trigger.setAttribute( 'aria-label', window.T_Link_copied_to_clipboard ); 568 e.trigger.classList.add( 'tooltipped', 'tooltipped-s'+(isRtl?'e':'w') ); 569 }); 570 } 571 572 function initCodeClipboard(){ 573 function getCodeText( node ){ 574 // if highlight shortcode is used in inline lineno mode, remove lineno nodes before generating text, otherwise it doesn't hurt 575 var code = node.cloneNode( true ); 576 Array.from( code.querySelectorAll( '*:scope > span > span:first-child:not(:last-child)' ) ).forEach( function( lineno ){ 577 lineno.remove(); 578 }); 579 var text = code.textContent; 580 // remove a trailing line break, this may most likely 581 // come from the browser / Hugo transformation 582 text = text.replace( /\n$/, '' ); 583 return text; 584 } 585 586 function fallbackMessage( action ){ 587 var actionMsg = ''; 588 var actionKey = (action === 'cut' ? 'X' : 'C'); 589 if (/iPhone|iPad/i.test(navigator.userAgent)) { 590 actionMsg = 'No support :('; 591 } 592 else if (/Mac/i.test(navigator.userAgent)) { 593 actionMsg = 'Press ⌘-' + actionKey + ' to ' + action; 594 } 595 else { 596 actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action; 597 } 598 return actionMsg; 599 } 600 601 var codeElements = document.querySelectorAll( 'code' ); 602 for( var i = 0; i < codeElements.length; i++ ){ 603 var code = codeElements[i]; 604 var text = getCodeText( code ); 605 var inPre = code.parentNode.tagName.toLowerCase() == 'pre'; 606 var inTable = inPre && 607 code.parentNode.parentNode.tagName.toLowerCase() == 'td' && 608 code.parentNode.parentNode.classList.contains('lntd'); 609 // avoid copy-to-clipboard for highlight shortcode in table lineno mode 610 var isFirstLineCell = inTable && 611 code.parentNode.parentNode.parentNode.querySelector( 'td:first-child > pre > code' ) == code; 612 613 if( !isFirstLineCell && ( inPre || text.length > 5 ) ){ 614 code.classList.add( 'copy-to-clipboard-code' ); 615 if( inPre ){ 616 code.classList.add( 'copy-to-clipboard' ); 617 code.parentNode.classList.add( 'pre-code' ); 618 } 619 else{ 620 var clone = code.cloneNode( true ); 621 var span = document.createElement( 'span' ); 622 span.classList.add( 'copy-to-clipboard' ); 623 span.appendChild( clone ); 624 code.parentNode.replaceChild( span, code ); 625 code = clone; 626 } 627 var button = document.createElement( 'span' ); 628 button.classList.add( 'copy-to-clipboard-button' ); 629 button.setAttribute( 'title', window.T_Copy_to_clipboard ); 630 button.innerHTML = '<i class="far fa-copy"></i>'; 631 button.addEventListener( 'mouseleave', function() { 632 this.removeAttribute( 'aria-label' ); 633 this.classList.remove( 'tooltipped', 'tooltipped-w', 'tooltipped-se', 'tooltipped-sw' ); 634 }); 635 if( inTable ){ 636 var table = code.parentNode.parentNode.parentNode.parentNode.parentNode; 637 table.dataset.code = text; 638 table.parentNode.insertBefore( button, table.nextSibling ); 639 } 640 else if( inPre ){ 641 var pre = code.parentNode; 642 pre.dataset.code = text; 643 var p = pre.parentNode; 644 // indented code blocks are missing the div 645 while( p != document && ( p.tagName.toLowerCase() != 'div' || !p.classList.contains( 'highlight' ) ) ){ 646 p = p.parentNode; 647 } 648 if( p == document ){ 649 var clone = pre.cloneNode( true ); 650 var div = document.createElement( 'div' ); 651 div.classList.add( 'highlight' ); 652 div.appendChild( clone ); 653 pre.parentNode.replaceChild( div, pre ); 654 pre = clone; 655 } 656 pre.parentNode.insertBefore( button, pre.nextSibling ); 657 } 658 else{ 659 code.dataset.code = text; 660 code.parentNode.insertBefore( button, code.nextSibling ); 661 } 662 } 663 } 664 665 var clip = new ClipboardJS( '.copy-to-clipboard-button', { 666 text: function( trigger ){ 667 if( !trigger.previousElementSibling ){ 668 return ''; 669 } 670 return trigger.previousElementSibling.dataset.code || ''; 671 } 672 }); 673 674 clip.on( 'success', function( e ){ 675 e.clearSelection(); 676 var inPre = e.trigger.previousElementSibling && e.trigger.previousElementSibling.tagName.toLowerCase() == 'pre'; 677 var isCodeRtl = !inPre ? isRtl : false; 678 var doBeside = inPre || (e.trigger.previousElementSibling && e.trigger.previousElementSibling.tagName.toLowerCase() == 'table' ); 679 e.trigger.setAttribute( 'aria-label', window.T_Copied_to_clipboard ); 680 e.trigger.classList.add( 'tooltipped', 'tooltipped-' + (doBeside ? 'w' : 's'+(isCodeRtl?'e':'w')) ); 681 }); 682 683 clip.on( 'error', function( e ){ 684 var inPre = e.trigger.previousElementSibling && e.trigger.previousElementSibling.tagName.toLowerCase() == 'pre'; 685 var isCodeRtl = !inPre ? isRtl : false; 686 var doBeside = inPre || (e.trigger.previousElementSibling && e.trigger.previousElementSibling.tagName.toLowerCase() == 'table' ); 687 e.trigger.setAttribute( 'aria-label', fallbackMessage(e.action) ); 688 e.trigger.classList.add( 'tooltipped', 'tooltipped-' + (doBeside ? 'w' : 's'+(isCodeRtl?'e':'w')) ); 689 var f = function(){ 690 e.trigger.setAttribute( 'aria-label', window.T_Copied_to_clipboard ); 691 e.trigger.classList.add( 'tooltipped', 'tooltipped-' + (doBeside ? 'w' : 's'+(isCodeRtl?'e':'w')) ); 692 document.removeEventListener( 'copy', f ); 693 }; 694 document.addEventListener( 'copy', f ); 695 }); 696 } 697 698 function initChroma( update ){ 699 var chroma = variants.getColorValue( 'CODE-theme' ); 700 var link = document.querySelector( '#R-variant-chroma-style' ); 701 var old_path = link.getAttribute( 'href' ); 702 var new_path = old_path.replace( /^(.*\/chroma-).*?(\.css.*)$/, '$1' + chroma + '$2' ); 703 link.setAttribute( 'href', new_path ); 704 } 705 706 function initArrowVerticalNav(){ 707 var topMain = 0; 708 if( !isPrint ){ 709 topMain = document.querySelector("main").getClientRects()[0].top; 710 } 711 712 document.addEventListener('keydown', function(event){ 713 var elems = Array.from( document.querySelectorAll( `main :not(.include.hide-first-heading) > :where( 714 .article-subheading, 715 :not(.article-subheading) + h1:not(.a11y-only), 716 h1:not(.a11y-only):first-child, 717 h2, h3, h4, h5, h6 718 ), 719 main .include.hide-first-heading > :where( h1, h2, h3, h4, h5, h6 ) ~ :where( h1, h2, h3, h4, h5, h6 ) 720 ` )); 721 if( !event.shiftKey && !event.ctrlKey && event.altKey && !event.metaKey ){ 722 if( event.which == 38 ){ // up 723 var target = isPrint ? document.querySelector( '#R-body' ) : document.querySelector( '.flex-block-wrapper' ); 724 elems.some( function( elem, i ){ 725 var top = elem.getBoundingClientRect().top; 726 var topBoundary = top - topMain; 727 if( topBoundary > -1 ){ 728 target.scrollIntoView(); 729 return true; 730 } 731 target = elem 732 }) 733 } 734 else if( event.which == 40 ){ // down 735 elems.some( function( elem, i ){ 736 var top = elem.getBoundingClientRect().top; 737 var topBoundary = top - topMain; 738 if( topBoundary > -1 && topBoundary < 1 ){ 739 if( i+1 < elems.length ){ 740 var target = elems[ i+1 ]; 741 target.scrollIntoView(); 742 } 743 return true; 744 } 745 if( topBoundary >= 1 ){ 746 var target = elem; 747 target.scrollIntoView(); 748 return true; 749 } 750 }) 751 } 752 } 753 }); 754 } 755 756 function initArrowHorizontalNav(){ 757 if( isPrint ){ 758 return; 759 } 760 761 // button navigation 762 var prev = document.querySelector( '.topbar-button-prev a' ); 763 prev && prev.addEventListener( 'click', navPrev ); 764 var next = document.querySelector( '.topbar-button-next a' ); 765 next && next.addEventListener( 'click', navNext ); 766 767 // keyboard navigation 768 // avoid prev/next navigation if we are not at the start/end of the 769 // horizontal area 770 var el = document.querySelector('#R-body-inner'); 771 var scrollStart = 0; 772 var scrollEnd = 0; 773 document.addEventListener('keydown', function(event){ 774 if( !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey ){ 775 if( event.which == dir_key_start ){ 776 if( !scrollStart && +el.scrollLeft.toFixed()*dir_scroll <= 0 ){ 777 prev && prev.click(); 778 } 779 else if( scrollStart != -1 ){ 780 clearTimeout( scrollStart ); 781 } 782 scrollStart = -1; 783 } 784 if( event.which == dir_key_end ){ 785 if( !scrollEnd && +el.scrollLeft.toFixed()*dir_scroll + +el.clientWidth.toFixed() >= +el.scrollWidth.toFixed() ){ 786 next && next.click(); 787 } 788 else if( scrollEnd != -1 ){ 789 clearTimeout( scrollEnd ); 790 } 791 scrollEnd = -1; 792 } 793 } 794 }); 795 document.addEventListener('keyup', function(event){ 796 if( !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey ){ 797 if( event.which == dir_key_start ){ 798 // check for false indication if keyup is delayed after navigation 799 if( scrollStart == -1 ){ 800 scrollStart = setTimeout( function(){ scrollStart = 0; }, 300 ); 801 } 802 } 803 if( event.which == dir_key_end ){ 804 if( scrollEnd == -1 ){ 805 scrollEnd = setTimeout( function(){ scrollEnd = 0; }, 300 ); 806 } 807 } 808 } 809 }); 810 811 // avoid keyboard navigation for input fields 812 document.querySelectorAll( formelements ).forEach( function( e ){ 813 e.addEventListener( 'keydown', function( event ){ 814 if( event.which == dir_key_start || event.which == dir_key_end ){ 815 event.stopPropagation(); 816 } 817 }); 818 }); 819 } 820 821 function initMenuScrollbar(){ 822 if( isPrint ){ 823 return; 824 } 825 826 var elm = document.querySelector('#R-content-wrapper'); 827 var elt = document.querySelector('.topbar-button.topbar-flyout .topbar-content-wrapper'); 828 829 var autofocus = true; 830 document.addEventListener('keydown', function(event){ 831 // for initial keyboard scrolling support, no element 832 // may be hovered, but we still want to react on 833 // cursor/page up/down. because we can't hack 834 // the scrollbars implementation, we try to trick 835 // it and give focus to the scrollbar - only 836 // to just remove the focus right after scrolling 837 // happend 838 autofocus = false; 839 if( event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.which < 32 || event.which > 40 ){ 840 // if tab key was pressed, we are ended with our initial 841 // focus job 842 return; 843 } 844 845 var c = elc && elc.matches(':hover'); 846 var m = elm && elm.matches(':hover'); 847 var t = elt && elt.matches(':hover'); 848 var f = event.target.matches( formelements ); 849 if( !c && !m && !t && !f ){ 850 // only do this hack if none of our scrollbars 851 // is hovered 852 // if we are showing the sidebar as a flyout we 853 // want to scroll the content-wrapper, otherwise we want 854 // to scroll the body 855 var nt = document.querySelector('body').matches('.topbar-flyout'); 856 var nm = document.querySelector('body').matches('.sidebar-flyout'); 857 if( nt ){ 858 var psb = pst.get( document.querySelector('.topbar-button.topbar-flyout') ); 859 psb && psb.scrollbarY.focus(); 860 } 861 else if( nm ){ 862 psm && psm.scrollbarY.focus(); 863 } 864 else{ 865 document.querySelector('#R-body-inner').focus(); 866 psc && psc.scrollbarY.focus(); 867 } 868 } 869 }); 870 // scrollbars will install their own keyboard handlers 871 // that need to be executed inbetween our own handlers 872 // PSC removed for #242 #243 #244 873 // psc = elc && new PerfectScrollbar('#R-body-inner'); 874 psm = elm && new PerfectScrollbar('#R-content-wrapper', { scrollingThreshold: 2000, swipeEasing: false, wheelPropagation: false }); 875 document.querySelectorAll('.topbar-button .topbar-content-wrapper').forEach( function( e ){ 876 var button = getTopbarButtonParent( e ); 877 if( !button ){ 878 return; 879 } 880 pst.set( button, new PerfectScrollbar( e, { scrollingThreshold: 2000, swipeEasing: false, wheelPropagation: false }) ); 881 e.addEventListener( 'click', toggleTopbarFlyoutEvent ); 882 }); 883 884 document.addEventListener('keydown', function(){ 885 // if we facked initial scrolling, we want to 886 // remove the focus to not leave visual markers on 887 // the scrollbar 888 if( autofocus ){ 889 psc && psc.scrollbarY.blur(); 890 psm && psm.scrollbarY.blur(); 891 pst.forEach( function( psb ){ 892 psb.scrollbarY.blur(); 893 }); 894 autofocus = false; 895 } 896 }); 897 // on resize, we have to redraw the scrollbars to let new height 898 // affect their size 899 window.addEventListener('resize', function(){ 900 pst.forEach( function( psb ){ 901 setTimeout( function(){ psb.update(); }, 10 ); 902 }); 903 psm && setTimeout( function(){ psm.update(); }, 10 ); 904 psc && setTimeout( function(){ psc.update(); }, 10 ); 905 }); 906 // now that we may have collapsible menus, we need to call a resize 907 // for the menu scrollbar if sections are expanded/collapsed 908 document.querySelectorAll('#R-sidebar .collapsible-menu input').forEach( function(e){ 909 e.addEventListener('change', function(){ 910 psm && setTimeout( function(){ psm.update(); }, 10 ); 911 }); 912 }); 913 // bugfix for PS in RTL mode: the initial scrollbar position is off; 914 // calling update() once, fixes this 915 pst.forEach( function( psb ){ 916 setTimeout( function(){ psb.update(); }, 10 ); 917 }); 918 psm && setTimeout( function(){ psm.update(); }, 10 ); 919 psc && setTimeout( function(){ psc.update(); }, 10 ); 920 921 // finally, we want to adjust the contents end padding if there is a scrollbar visible 922 window.addEventListener('resize', adjustContentWidth ); 923 adjustContentWidth(); 924 } 925 926 function imageEscapeHandler( event ){ 927 if( event.key == "Escape" ){ 928 var image = event.target; 929 image.click(); 930 } 931 } 932 933 function navShortcutHandler( event ){ 934 if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 78 /* n */ ){ 935 toggleNav(); 936 } 937 } 938 939 function searchShortcutHandler( event ){ 940 if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 70 /* f */ ){ 941 showSearch(); 942 } 943 } 944 945 function tocShortcutHandler( event ){ 946 if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 84 /* t */ ){ 947 toggleToc(); 948 } 949 } 950 951 function editShortcutHandler( event ){ 952 if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 87 /* w */ ){ 953 showEdit(); 954 } 955 } 956 957 function printShortcutHandler( event ){ 958 if( !event.shiftKey && event.altKey && event.ctrlKey && !event.metaKey && event.which == 80 /* p */ ){ 959 showPrint(); 960 } 961 } 962 963 function showSearch(){ 964 var s = document.querySelector( '#R-search-by' ); 965 if( !s ){ 966 return; 967 } 968 var b = document.querySelector( 'body' ); 969 if( s == document.activeElement ){ 970 if( b.classList.contains( 'sidebar-flyout' ) ){ 971 closeNav(); 972 } 973 documentFocus(); 974 } else { 975 if( !b.classList.contains( 'sidebar-flyout' ) ){ 976 openNav(); 977 } 978 s.focus(); 979 } 980 } 981 982 function openNav(){ 983 closeSomeTopbarButtonFlyout(); 984 var b = document.querySelector( 'body' ); 985 b.classList.add( 'sidebar-flyout' ); 986 psm && setTimeout( function(){ psm.update(); }, 10 ); 987 psm && psm.scrollbarY.focus(); 988 var a = document.querySelector( '#R-sidebar a' ) 989 if( a ){ 990 a.focus(); 991 } 992 } 993 994 function closeNav(){ 995 var b = document.querySelector( 'body' ); 996 b.classList.remove( 'sidebar-flyout' ); 997 documentFocus(); 998 } 999 1000 function toggleNav(){ 1001 var b = document.querySelector( 'body' ); 1002 if( b.classList.contains( 'sidebar-flyout' ) ){ 1003 closeNav(); 1004 } 1005 else{ 1006 openNav(); 1007 } 1008 } 1009 1010 function navEscapeHandler( event ){ 1011 if( event.key == "Escape" ){ 1012 closeNav(); 1013 } 1014 } 1015 1016 function getTopbarButtonParent( e ){ 1017 var button = e; 1018 while( button && !button.classList.contains( 'topbar-button' ) ){ 1019 button = button.parentElement; 1020 } 1021 return button; 1022 } 1023 1024 function openTopbarButtonFlyout( button ){ 1025 closeNav(); 1026 var body = document.querySelector( 'body' ); 1027 button.classList.add( 'topbar-flyout' ); 1028 body.classList.add( 'topbar-flyout' ); 1029 var psb = pst.get( button ); 1030 psb && setTimeout( function(){ psb.update(); }, 10 ); 1031 psb && psb.scrollbarY.focus(); 1032 var a = button.querySelector( '.topbar-content-wrapper a' ); 1033 if( a ){ 1034 a.focus(); 1035 } 1036 } 1037 1038 function closeTopbarButtonFlyout( button ){ 1039 var body = document.querySelector( 'body' ); 1040 button.classList.remove( 'topbar-flyout' ); 1041 body.classList.remove( 'topbar-flyout' ); 1042 documentFocus(); 1043 } 1044 1045 function closeSomeTopbarButtonFlyout(){ 1046 var someButton = document.querySelector( '.topbar-button.topbar-flyout' ); 1047 if( someButton ){ 1048 closeTopbarButtonFlyout( someButton ); 1049 }; 1050 return someButton 1051 } 1052 1053 function toggleTopbarButtonFlyout( button ){ 1054 var someButton = closeSomeTopbarButtonFlyout(); 1055 if( button && button != someButton ){ 1056 openTopbarButtonFlyout( button ); 1057 } 1058 } 1059 1060 function toggleTopbarFlyout( e ){ 1061 var button = getTopbarButtonParent( e ); 1062 if( !button ){ 1063 return; 1064 } 1065 toggleTopbarButtonFlyout( button ); 1066 } 1067 1068 function toggleTopbarFlyoutEvent( event ){ 1069 if( event.target.classList.contains( 'topbar-content' ) 1070 || event.target.classList.contains( 'topbar-content-wrapper' ) 1071 || event.target.classList.contains( 'ps__rail-x' ) 1072 || event.target.classList.contains( 'ps__rail-y' ) 1073 || event.target.classList.contains( 'ps__thumb-x' ) 1074 || event.target.classList.contains( 'ps__thumb-y' ) 1075 ){ 1076 // the scrollbar was used, don't close flyout 1077 return; 1078 } 1079 toggleTopbarFlyout( event.target ) 1080 } 1081 1082 function topbarFlyoutEscapeHandler( event ){ 1083 if( event.key == "Escape" ){ 1084 closeSomeTopbarButtonFlyout(); 1085 } 1086 } 1087 1088 function toggleToc(){ 1089 toggleTopbarButtonFlyout( document.querySelector( '.topbar-button-toc' ) ); 1090 } 1091 1092 function showEdit(){ 1093 var l = document.querySelector( '.topbar-button-edit a' ); 1094 if( l ){ 1095 l.click(); 1096 } 1097 } 1098 1099 function showPrint(){ 1100 var l = document.querySelector( '.topbar-button-print a' ); 1101 if( l ){ 1102 l.click(); 1103 } 1104 } 1105 1106 function navPrev(){ 1107 var e = document.querySelector( '.topbar-button-prev a' ); 1108 location.href = e && e.getAttribute( 'href' ); 1109 }; 1110 1111 function navNext(){ 1112 var e = document.querySelector( '.topbar-button-next a' ); 1113 location.href = e && e.getAttribute( 'href' ); 1114 }; 1115 1116 function initToc(){ 1117 if( isPrint ){ 1118 return; 1119 } 1120 1121 document.addEventListener( 'keydown', editShortcutHandler ); 1122 document.addEventListener( 'keydown', navShortcutHandler ); 1123 document.addEventListener( 'keydown', printShortcutHandler ); 1124 document.addEventListener( 'keydown', searchShortcutHandler ); 1125 document.addEventListener( 'keydown', tocShortcutHandler ); 1126 document.addEventListener( 'keydown', navEscapeHandler ); 1127 document.addEventListener( 'keydown', topbarFlyoutEscapeHandler ); 1128 1129 var b = document.querySelector( '#R-body-overlay' ); 1130 if( b ){ 1131 b.addEventListener( 'click', closeNav ); 1132 } 1133 var m = document.querySelector( '#R-main-overlay' ); 1134 if( m ){ 1135 m.addEventListener( 'click', closeSomeTopbarButtonFlyout ); 1136 } 1137 1138 // finally give initial focus to allow keyboard scrolling in FF 1139 documentFocus(); 1140 } 1141 1142 function initSwipeHandler(){ 1143 if( !touchsupport ){ 1144 return; 1145 } 1146 1147 var startx = null; 1148 var starty = null; 1149 var handleStartX = function(evt) { 1150 startx = evt.touches[0].clientX; 1151 starty = evt.touches[0].clientY; 1152 }; 1153 var handleMoveX = function(evt) { 1154 if( startx !== null ){ 1155 var diffx = startx - evt.touches[0].clientX; 1156 var diffy = starty - evt.touches[0].clientY || .1 ; 1157 if( diffx / Math.abs( diffy ) < 2 ){ 1158 // detect mostly vertical swipes and reset our starting pos 1159 // to not detect a horizontal move if vertical swipe is unprecise 1160 startx = evt.touches[0].clientX; 1161 } 1162 else if( diffx > 30 ){ 1163 startx = null; 1164 starty = null; 1165 closeNav(); 1166 } 1167 } 1168 }; 1169 var handleEndX = function(evt) { 1170 startx = null; 1171 starty = null; 1172 }; 1173 1174 var s = document.querySelector( '#R-body-overlay' ); 1175 s && s.addEventListener("touchstart", handleStartX, { capture: false, passive: true}); 1176 document.querySelector( '#R-sidebar' ).addEventListener("touchstart", handleStartX, { capture: false, passive: true}); 1177 document.querySelectorAll( '#R-sidebar *' ).forEach( function(e){ e.addEventListener("touchstart", handleStartX, { capture: false, passive: true}) }); 1178 s && s.addEventListener("touchmove", handleMoveX, { capture: false, passive: true}); 1179 document.querySelector( '#R-sidebar' ).addEventListener("touchmove", handleMoveX, { capture: false, passive: true}); 1180 document.querySelectorAll( '#R-sidebar *' ).forEach( function(e){ e.addEventListener("touchmove", handleMoveX, { capture: false, passive: true}) }); 1181 s && s.addEventListener("touchend", handleEndX, { capture: false, passive: true}); 1182 document.querySelector( '#R-sidebar' ).addEventListener("touchend", handleEndX, { capture: false, passive: true}); 1183 document.querySelectorAll( '#R-sidebar *' ).forEach( function(e){ e.addEventListener("touchend", handleEndX, { capture: false, passive: true}) }); 1184 } 1185 1186 function initImage(){ 1187 document.querySelectorAll( '.lightbox-back' ).forEach( function(e){ e.addEventListener( 'keydown', imageEscapeHandler ); }); 1188 } 1189 1190 function initExpand(){ 1191 document.querySelectorAll( '.expand > input' ).forEach( function(e){ e.addEventListener( 'change', initMermaid.bind( null, true, null ) ); }); 1192 } 1193 1194 function clearHistory() { 1195 var visitedItem = window.relearn.absBaseUri + '/visited-url/' 1196 for( var item in sessionStorage ){ 1197 if( item.substring( 0, visitedItem.length ) === visitedItem ){ 1198 sessionStorage.removeItem( item ); 1199 var url = item.substring( visitedItem.length ); 1200 // in case we have `relativeURLs=true` we have to strip the 1201 // relative path to root 1202 url = url.replace( /\.\.\//g, '/' ).replace( /^\/+\//, '/' ); 1203 document.querySelectorAll( '[data-nav-id="'+url+'"]' ).forEach( function( e ){ 1204 e.classList.remove( 'visited' ); 1205 }); 1206 } 1207 } 1208 } 1209 1210 function initHistory() { 1211 var visitedItem = window.relearn.absBaseUri + '/visited-url/' 1212 sessionStorage.setItem( visitedItem+document.querySelector( 'body' ).dataset.url, 1); 1213 1214 // loop through the sessionStorage and see if something should be marked as visited 1215 for( var item in sessionStorage ){ 1216 if( item.substring( 0, visitedItem.length ) === visitedItem && sessionStorage.getItem( item ) == 1 ){ 1217 var url = item.substring( visitedItem.length ); 1218 // in case we have `relativeURLs=true` we have to strip the 1219 // relative path to root 1220 url = url.replace( /\.\.\//g, '/' ).replace( /^\/+\//, '/' ); 1221 document.querySelectorAll( '[data-nav-id="'+url+'"]' ).forEach( function( e ){ 1222 e.classList.add( 'visited' ); 1223 }); 1224 } 1225 } 1226 } 1227 1228 function initScrollPositionSaver(){ 1229 function savePosition( event ){ 1230 var state = window.history.state || {}; 1231 state = Object.assign( {}, ( typeof state === 'object' ) ? state : {} ); 1232 state.contentScrollTop = +elc.scrollTop; 1233 window.history.replaceState( state, '', window.location ); 1234 }; 1235 1236 var ticking = false; 1237 elc.addEventListener( 'scroll', function( event ){ 1238 if( !ticking ){ 1239 window.requestAnimationFrame( function(){ 1240 savePosition(); 1241 ticking = false; 1242 }); 1243 ticking = true; 1244 } 1245 }); 1246 1247 document.addEventListener( "click", savePosition ); 1248 } 1249 1250 function scrollToPositions() { 1251 // show active menu entry 1252 window.setTimeout( function(){ 1253 var e = document.querySelector( '#R-sidebar li.active a' ); 1254 if( e && e.scrollIntoView ){ 1255 e.scrollIntoView({ 1256 block: 'center', 1257 }); 1258 } 1259 }, 10 ); 1260 1261 // scroll the content to point of interest; 1262 // if we have a scroll position saved, the user was here 1263 // before in his history stack and we want to reposition 1264 // to the position he was when he left the page; 1265 // otherwise if he used page search before, we want to position 1266 // to its last outcome; 1267 // otherwise he may want to see a specific fragment 1268 1269 var state = window.history.state || {}; 1270 state = ( typeof state === 'object') ? state : {}; 1271 if( state.hasOwnProperty( 'contentScrollTop' ) ){ 1272 window.setTimeout( function(){ 1273 elc.scrollTop = +state.contentScrollTop; 1274 }, 10 ); 1275 return; 1276 } 1277 1278 var search = sessionStorage.getItem( window.relearn.absBaseUri+'/search-value' ); 1279 if( search && search.length ){ 1280 search = regexEscape( search ); 1281 var found = elementContains( search, elc ); 1282 var searchedElem = found.length && found[ 0 ]; 1283 if( searchedElem ){ 1284 searchedElem.scrollIntoView( true ); 1285 var scrolledY = window.scrollY; 1286 if( scrolledY ){ 1287 window.scroll( 0, scrolledY - 125 ); 1288 } 1289 } 1290 return; 1291 } 1292 1293 if( window.location.hash && window.location.hash.length > 1 ){ 1294 window.setTimeout( function(){ 1295 try{ 1296 var e = document.querySelector( window.location.hash ); 1297 if( e && e.scrollIntoView ){ 1298 e.scrollIntoView({ 1299 block: 'start', 1300 }); 1301 } 1302 } catch( e ){} 1303 }, 10 ); 1304 return; 1305 } 1306 } 1307 1308 window.addEventListener( 'popstate', function ( event ){ 1309 scrollToPositions(); 1310 }); 1311 1312 const observer = new PerformanceObserver( function(){ 1313 scrollToPositions(); 1314 }); 1315 observer.observe({ type: "navigation" }); 1316 1317 function mark() { 1318 // mark some additional stuff as searchable 1319 var bodyInnerLinks = document.querySelectorAll( '#R-body-inner a:not(.lightbox-link):not(.btn):not(.lightbox-back)' ); 1320 for( var i = 0; i < bodyInnerLinks.length; i++ ){ 1321 bodyInnerLinks[i].classList.add( 'highlight' ); 1322 } 1323 1324 var value = sessionStorage.getItem( window.relearn.absBaseUri + '/search-value' ); 1325 var highlightableElements = document.querySelectorAll( '.highlightable' ); 1326 highlight( highlightableElements, value, { element: 'mark', className: 'search' } ); 1327 1328 var markedElements = document.querySelectorAll( 'mark.search' ); 1329 for( var i = 0; i < markedElements.length; i++ ){ 1330 var parent = markedElements[i].parentNode; 1331 while( parent && parent.classList ){ 1332 if( parent.classList.contains( 'expand' ) ){ 1333 var expandInputs = parent.querySelectorAll( 'input:not(.expand-marked)' ); 1334 if( expandInputs.length ){ 1335 expandInputs[0].classList.add( 'expand-marked' ); 1336 expandInputs[0].dataset.checked = expandInputs[0].checked ? 'true' : 'false'; 1337 expandInputs[0].checked = true; 1338 } 1339 } 1340 if( parent.tagName.toLowerCase() === 'li' && parent.parentNode && parent.parentNode.tagName.toLowerCase() === 'ul' && parent.parentNode.classList.contains( 'collapsible-menu' )){ 1341 var toggleInputs = parent.querySelectorAll( 'input:not(.menu-marked)' ); 1342 if( toggleInputs.length ){ 1343 toggleInputs[0].classList.add( 'menu-marked' ); 1344 toggleInputs[0].dataset.checked = toggleInputs[0].checked ? 'true' : 'false'; 1345 toggleInputs[0].checked = true; 1346 } 1347 } 1348 parent = parent.parentNode; 1349 } 1350 } 1351 psm && setTimeout( function(){ psm.update(); }, 10 ); 1352 } 1353 window.relearn.markSearch = mark; 1354 1355 function highlight( es, words, options ){ 1356 var settings = { 1357 className: 'highlight', 1358 element: 'span', 1359 caseSensitive: false, 1360 wordsOnly: false 1361 }; 1362 Object.assign( settings, options ); 1363 1364 if( !words ){ return; } 1365 if( words.constructor === String ){ 1366 words = [ words ]; 1367 } 1368 words = words.filter( function( word, i ){ 1369 return word != ''; 1370 }); 1371 words = words.map( function( word, i ){ 1372 return regexEscape( word ); 1373 }); 1374 if( words.length == 0 ){ return this; } 1375 1376 var flag = settings.caseSensitive ? '' : 'i'; 1377 var pattern = "(" + words.join( '|' ) + ')'; 1378 if( settings.wordsOnly ){ 1379 pattern = '\\b' + pattern + '\\b'; 1380 } 1381 var re = new RegExp( pattern, flag ); 1382 1383 for( var i = 0; i < es.length; i++ ){ 1384 highlightNode( es[i], re, settings.element, settings.className ); 1385 } 1386 }; 1387 1388 function highlightNode( node, re, nodeName, className ){ 1389 if( node.nodeType === 3 && node.parentElement && node.parentElement.namespaceURI == 'http://www.w3.org/1999/xhtml' ) { // text nodes 1390 var match = node.data.match( re ); 1391 if( match ){ 1392 var highlight = document.createElement( nodeName || 'span' ); 1393 highlight.className = className || 'highlight'; 1394 var wordNode = node.splitText( match.index ); 1395 wordNode.splitText( match[0].length ); 1396 var wordClone = wordNode.cloneNode( true ); 1397 highlight.appendChild( wordClone ); 1398 wordNode.parentNode.replaceChild( highlight, wordNode ); 1399 return 1; //skip added node in parent 1400 } 1401 } else if( (node.nodeType === 1 && node.childNodes) && // only element nodes that have children 1402 !/(script|style)/i.test(node.tagName) && // ignore script and style nodes 1403 !(node.tagName === nodeName.toUpperCase() && node.className === className) ){ // skip if already highlighted 1404 for( var i = 0; i < node.childNodes.length; i++ ){ 1405 i += highlightNode( node.childNodes[i], re, nodeName, className ); 1406 } 1407 } 1408 return 0; 1409 }; 1410 1411 function unmark() { 1412 sessionStorage.removeItem( window.relearn.absBaseUri + '/search-value' ); 1413 var markedElements = document.querySelectorAll( 'mark.search' ); 1414 for( var i = 0; i < markedElements.length; i++ ){ 1415 var parent = markedElements[i].parentNode; 1416 while( parent && parent.classList ){ 1417 if( parent.tagName.toLowerCase() === 'li' && parent.parentNode && parent.parentNode.tagName.toLowerCase() === 'ul' && parent.parentNode.classList.contains( 'collapsible-menu' )){ 1418 var toggleInputs = parent.querySelectorAll( 'input.menu-marked' ); 1419 if( toggleInputs.length ){ 1420 toggleInputs[0].checked = toggleInputs[0].dataset.checked === 'true'; 1421 toggleInputs[0].dataset.checked = null; 1422 toggleInputs[0].classList.remove( 'menu-marked' ); 1423 } 1424 } 1425 if( parent.classList.contains( 'expand' ) ){ 1426 var expandInputs = parent.querySelectorAll( 'input.expand-marked' ); 1427 if( expandInputs.length ){ 1428 expandInputs[0].checked = expandInputs[0].dataset.checked === 'true'; 1429 expandInputs[0].dataset.checked = null; 1430 expandInputs[0].classList.remove( 'expand-marked' ); 1431 } 1432 } 1433 parent = parent.parentNode; 1434 } 1435 } 1436 1437 var highlighted = document.querySelectorAll( '.highlightable' ); 1438 unhighlight( highlighted, { element: 'mark', className: 'search' } ); 1439 psm && setTimeout( function(){ psm.update(); }, 10 ); 1440 } 1441 1442 function unhighlight( es, options ){ 1443 var settings = { 1444 className: 'highlight', 1445 element: 'span' 1446 }; 1447 Object.assign( settings, options ); 1448 1449 for( var i = 0; i < es.length; i++ ){ 1450 var highlightedElements = es[i].querySelectorAll( settings.element + '.' + settings.className ); 1451 for( var j = 0; j < highlightedElements.length; j++ ){ 1452 var parent = highlightedElements[j].parentNode; 1453 parent.replaceChild( highlightedElements[j].firstChild, highlightedElements[j] ); 1454 parent.normalize(); 1455 } 1456 } 1457 }; 1458 1459 // replace jQuery.createPseudo with https://stackoverflow.com/a/66318392 1460 function elementContains( txt, e ){ 1461 var regex = RegExp( txt, 'i' ); 1462 var nodes = []; 1463 if( e ){ 1464 var tree = document.createTreeWalker( e, 4 /* NodeFilter.SHOW_TEXT */, function( node ){ 1465 return regex.test( node.data ); 1466 }, false ); 1467 var node = null; 1468 while( node = tree.nextNode() ){ 1469 nodes.push( node.parentElement ); 1470 } 1471 } 1472 return nodes; 1473 } 1474 1475 function searchInputHandler( value ){ 1476 unmark(); 1477 if( value.length ){ 1478 sessionStorage.setItem( window.relearn.absBaseUri+'/search-value', value ); 1479 mark(); 1480 } 1481 } 1482 1483 function initSearch() { 1484 // sync input/escape between searchbox and searchdetail 1485 var inputs = document.querySelectorAll( 'input.search-by' ); 1486 inputs.forEach( function( e ){ 1487 e.addEventListener( 'keydown', function( event ){ 1488 if( event.key == 'Escape' ){ 1489 var input = event.target; 1490 var search = sessionStorage.getItem( window.relearn.absBaseUri+'/search-value' ); 1491 if( !search || !search.length ){ 1492 input.blur(); 1493 } 1494 searchInputHandler( '' ); 1495 inputs.forEach( function( e ){ 1496 e.value = ''; 1497 }); 1498 if( !search || !search.length ){ 1499 documentFocus(); 1500 } 1501 } 1502 }); 1503 e.addEventListener( 'input', function( event ){ 1504 var input = event.target; 1505 var value = input.value; 1506 searchInputHandler( value ); 1507 inputs.forEach( function( e ){ 1508 if( e != input ){ 1509 e.value = value; 1510 } 1511 }); 1512 }); 1513 }); 1514 1515 document.querySelectorAll( '[data-search-clear]' ).forEach( function( e ){ 1516 e.addEventListener( 'click', function(){ 1517 inputs.forEach( function( e ){ 1518 e.value = ''; 1519 var event = document.createEvent( 'Event' ); 1520 event.initEvent( 'input', false, false ); 1521 e.dispatchEvent( event ); 1522 }); 1523 unmark(); 1524 }); 1525 }); 1526 1527 var urlParams = new URLSearchParams( window.location.search ); 1528 var value = urlParams.get( 'search-by' ); 1529 if( value ){ 1530 sessionStorage.setItem( window.relearn.absBaseUri+'/search-value', value ); 1531 } 1532 mark(); 1533 1534 // set initial search value for inputs on page load 1535 if( sessionStorage.getItem( window.relearn.absBaseUri+'/search-value' ) ){ 1536 var search = sessionStorage.getItem( window.relearn.absBaseUri+'/search-value' ); 1537 inputs.forEach( function( e ){ 1538 e.value = search; 1539 var event = document.createEvent( 'Event' ); 1540 event.initEvent( 'input', false, false ); 1541 e.dispatchEvent( event ); 1542 }); 1543 } 1544 1545 window.relearn.isSearchInit = true; 1546 window.relearn.runInitialSearch && window.relearn.runInitialSearch(); 1547 } 1548 1549 function updateTheme( detail ){ 1550 if( window.relearn.lastVariant == detail.variant ){ 1551 return; 1552 } 1553 window.relearn.lastVariant = detail.variant; 1554 1555 initChroma( true ); 1556 initMermaid( true ); 1557 initOpenapi( true ); 1558 document.dispatchEvent( new CustomEvent( 'themeVariantLoaded', { 1559 detail: detail 1560 })); 1561 } 1562 1563 (function(){ 1564 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 1565 initChroma( true ); 1566 initMermaid( true ); 1567 initOpenapi( true ); 1568 }); 1569 })(); 1570 1571 function useMermaid( config ){ 1572 if( !Object.assign ){ 1573 // We don't support Mermaid for IE11 anyways, so bail out early 1574 return; 1575 } 1576 window.relearn.mermaidConfig = config; 1577 if (typeof mermaid != 'undefined' && typeof mermaid.mermaidAPI != 'undefined') { 1578 mermaid.initialize( Object.assign( { "securityLevel": "antiscript", "startOnLoad": false }, config ) ); 1579 if( config.theme && variants ){ 1580 var write_style = variants.findLoadedStylesheet( 'R-variant-style' ); 1581 write_style.setProperty( '--CONFIG-MERMAID-theme', config.theme ); 1582 } 1583 } 1584 } 1585 if( window.themeUseMermaid ){ 1586 useMermaid( window.themeUseMermaid ); 1587 } 1588 1589 function useOpenapi( config ){ 1590 if( config.css && config.cssInProject ){ 1591 config.css = window.relearn.relBasePath + config.css; 1592 } 1593 } 1594 if( window.themeUseOpenapi ){ 1595 useOpenapi( window.themeUseOpenapi ); 1596 } 1597 1598 ready( function(){ 1599 initArrowVerticalNav(); 1600 initArrowHorizontalNav(); 1601 initMermaid(); 1602 initOpenapi(); 1603 initMenuScrollbar(); 1604 initToc(); 1605 initAnchorClipboard(); 1606 initCodeClipboard(); 1607 fixCodeTabs(); 1608 restoreTabSelections(); 1609 initSwipeHandler(); 1610 initHistory(); 1611 initSearch(); 1612 initImage(); 1613 initExpand(); 1614 initScrollPositionSaver(); 1615 }); 1616 1617 (function(){ 1618 var body = document.querySelector( 'body' ); 1619 var topbar = document.querySelector( '#R-topbar' ); 1620 function addTopbarButtonInfos(){ 1621 // initially add some management infos to buttons and areas 1622 var areas = body.querySelectorAll( '.topbar-area' ); 1623 areas.forEach( function( area ){ 1624 area.dataset.area = 'area-' + area.dataset.area; 1625 var buttons = area.querySelectorAll( ':scope > .topbar-button' ); 1626 buttons.forEach( function( button ){ 1627 button.dataset.origin = area.dataset.area; 1628 button.dataset.action = 'show'; 1629 var placeholder = document.createElement( 'div' ); 1630 placeholder.classList.add( 'topbar-placeholder' ); 1631 placeholder.dataset.action = 'show'; 1632 button.insertAdjacentElement( 'afterend', placeholder ); 1633 }); 1634 var placeholder = document.createElement( 'div' ); 1635 area.insertAdjacentElement( 'beforeend', placeholder ); 1636 var hidden = document.createElement( 'div' ); 1637 hidden.classList.add( 'topbar-hidden' ); 1638 hidden.dataset.area = area.dataset.area; 1639 var hplaceholder = document.createElement( 'div' ); 1640 hidden.insertAdjacentElement( 'beforeend', hplaceholder ); 1641 area.insertAdjacentElement( 'afterend', hidden ); 1642 }); 1643 } 1644 function moveAreaTopbarButtons( width ){ 1645 topbar.querySelectorAll( '.topbar-hidden .topbar-button' ).forEach( function( button ){ 1646 // move hidden to origins area 1647 var placeholder = button.parentNode.parentNode.querySelector( ':scope > .topbar-area .topbar-placeholder[data-action="hide"]' ); 1648 placeholder.dataset.action = 'show'; 1649 button.dataset.action = 'show'; 1650 placeholder.insertAdjacentElement( 'beforebegin', button ); 1651 }); 1652 topbar.querySelectorAll( '.topbar-area .topbar-button' ).forEach( function( button ){ 1653 var current_area = button.dataset.action; 1654 var origin_area = button.dataset.origin; 1655 if( current_area != 'show' && origin_area != current_area ){ 1656 // move moved to origins area 1657 var placeholder = topbar.querySelector( '.topbar-area[data-area="' + origin_area + '"] > .topbar-placeholder[data-action="' + current_area + '"]' ); 1658 placeholder.dataset.action = 'show'; 1659 button.dataset.action = 'show'; 1660 placeholder.insertAdjacentElement( 'beforebegin', button ); 1661 } 1662 }); 1663 Array.from( topbar.querySelectorAll( '.topbar-area .topbar-button' ) ).reverse().forEach( function( button ){ 1664 var parent = button.parentElement; 1665 var current_area = parent.dataset.area; 1666 var action = button.dataset[ 'width' + width.toUpperCase() ]; 1667 if( action == 'show' ){ 1668 } 1669 else if( action == 'hide' ){ 1670 // move to origins hidden 1671 var hidden = button.parentNode.parentNode.querySelector( ':scope > .topbar-hidden > *' ); 1672 var placeholder = button.nextSibling; 1673 placeholder.dataset.action = action; 1674 button.dataset.action = action; 1675 hidden.insertAdjacentElement( 'beforebegin', button ); 1676 } 1677 else if( action != current_area ){ 1678 // move to action area 1679 var dest = button.parentNode.parentNode.querySelector( '.topbar-area[data-area="' + action + '"] > *' ); 1680 if( dest ){ 1681 var placeholder = button.nextSibling; 1682 placeholder.dataset.action = action; 1683 button.dataset.action = action; 1684 dest.insertAdjacentElement( 'beforebegin', button ); 1685 } 1686 } 1687 }); 1688 } 1689 function moveTopbarButtons(){ 1690 var isS = body.classList.contains( 'menu-width-s' ); 1691 var isM = body.classList.contains( 'menu-width-m' ); 1692 var isL = body.classList.contains( 'menu-width-l' ); 1693 // move buttons once, width has a distinct value 1694 if( isS && !isM && !isL ){ 1695 moveAreaTopbarButtons( 's' ) 1696 } 1697 else if( !isS && isM && !isL ){ 1698 moveAreaTopbarButtons( 'm' ) 1699 } 1700 else if( !isS && !isM && isL ){ 1701 moveAreaTopbarButtons( 'l' ) 1702 } 1703 } 1704 function adjustEmptyTopbarContents(){ 1705 var buttons = Array.from( document.querySelectorAll( '.topbar-button > .topbar-content > .topbar-content-wrapper' ) ); 1706 // we have to reverse order to make sure to handle innermost areas first 1707 buttons.reverse().forEach( function( wrapper ){ 1708 var button = getTopbarButtonParent( wrapper ); 1709 if( button ){ 1710 var isEmpty = true; 1711 var area = wrapper.querySelector( ':scope > .topbar-area'); 1712 if( area ){ 1713 // if it's an area, we have to check each contained button 1714 // manually for its display property 1715 var areabuttons = area.querySelectorAll( ':scope > .topbar-button' ); 1716 isEmpty = true; 1717 areabuttons.forEach( function( ab ){ 1718 if( ab.style.display != 'none' ){ 1719 isEmpty = false; 1720 } 1721 }) 1722 } 1723 else{ 1724 var clone = wrapper.cloneNode( true ); 1725 var irrelevant = clone.querySelectorAll( "div.ps__rail-x, div.ps__rail-y" ); 1726 irrelevant.forEach(function( e ) { 1727 e.parentNode.removeChild( e ); 1728 }); 1729 isEmpty = !clone.innerHTML.trim(); 1730 } 1731 button.querySelector( 'button' ).disabled = isEmpty; 1732 button.style.display = isEmpty && button.dataset.contentEmpty == 'hide' ? 'none' : 'inline-block'; 1733 } 1734 }) 1735 } 1736 function setWidthS(e){ body.classList[ e.matches ? "add" : "remove" ]( 'menu-width-s' ); } 1737 function setWidthM(e){ body.classList[ e.matches ? "add" : "remove" ]( 'menu-width-m' ); } 1738 function setWidthL(e){ body.classList[ e.matches ? "add" : "remove" ]( 'menu-width-l' ); } 1739 function onWidthChange( setWidth, e ){ 1740 setWidth( e ); 1741 moveTopbarButtons(); 1742 adjustEmptyTopbarContents(); 1743 } 1744 var mqs = window.matchMedia( 'only screen and (max-width: 47.999rem)' ); 1745 mqs.addEventListener( 'change', onWidthChange.bind( null, setWidthS ) ); 1746 var mqm = window.matchMedia( 'only screen and (min-width: 48rem) and (max-width: 59.999rem)' ); 1747 mqm.addEventListener( 'change', onWidthChange.bind( null, setWidthM ) ); 1748 var mql = window.matchMedia( 'only screen and (min-width: 60rem)' ); 1749 mql.addEventListener( 'change', onWidthChange.bind( null, setWidthL ) ); 1750 1751 addTopbarButtonInfos(); 1752 setWidthS( mqs ); 1753 setWidthM( mqm ); 1754 setWidthL( mql ); 1755 moveTopbarButtons(); 1756 adjustEmptyTopbarContents(); 1757 })(); 1758 1759 (function(){ 1760 var body = document.querySelector( 'body' ); 1761 function setWidth(e){ body.classList[ e.matches ? "add" : "remove" ]( 'main-width-max' ); } 1762 function onWidthChange( setWidth, e ){ 1763 setWidth( e ); 1764 } 1765 var width = variants.getColorValue( 'MAIN-WIDTH-MAX' ); 1766 var mqm = window.matchMedia( 'screen and ( min-width: ' + width + ')' ); 1767 mqm.addEventListener( 'change', onWidthChange.bind( null, setWidth ) ); 1768 setWidth( mqm ); 1769 })();