2023-11-05 06:58:16 +00:00
/ *
2024-12-25 05:24:48 +00:00
* Copyright 2014 - 2024 JetBrains s . r . o . Use of this source code is governed by the Apache 2.0 license .
2023-11-05 06:58:16 +00:00
* /
2024-12-25 05:24:48 +00:00
/** When Dokka is viewed via iframe, local storage could be inaccessible (see https:/ / github . com / Kotlin / dokka / issues / 3323 )
* This is a wrapper around local storage to prevent errors in such cases
* * /
const safeLocalStorage = ( ( ) => {
let isLocalStorageAvailable = false ;
try {
const testKey = '__testLocalStorageKey__' ;
localStorage . setItem ( testKey , testKey ) ;
localStorage . removeItem ( testKey ) ;
isLocalStorageAvailable = true ;
} catch ( e ) {
console . error ( 'Local storage is not available' , e ) ;
}
return {
getItem : ( key ) => {
if ( ! isLocalStorageAvailable ) {
return null ;
}
return localStorage . getItem ( key ) ;
} ,
setItem : ( key , value ) => {
if ( ! isLocalStorageAvailable ) {
return ;
}
localStorage . setItem ( key , value ) ;
}
} ;
} ) ( ) ;
2023-11-05 06:58:16 +00:00
2021-03-21 17:26:45 +00:00
filteringContext = {
dependencies : { } ,
restrictedDependencies : [ ] ,
activeFilters : [ ]
}
let highlightedAnchor ;
let topNavbarOffset ;
2022-01-01 21:40:22 +00:00
let instances = [ ] ;
let sourcesetNotification ;
const samplesDarkThemeName = 'darcula'
const samplesLightThemeName = 'idea'
2021-03-21 17:26:45 +00:00
window . addEventListener ( 'load' , ( ) => {
document . querySelectorAll ( "div[data-platform-hinted]" )
2022-01-01 21:40:22 +00:00
. forEach ( elem => elem . addEventListener ( 'click' , ( event ) => togglePlatformDependent ( event , elem ) ) )
2021-03-21 17:26:45 +00:00
const filterSection = document . getElementById ( 'filter-section' )
if ( filterSection ) {
filterSection . addEventListener ( 'click' , ( event ) => filterButtonHandler ( event ) )
initializeFiltering ( )
}
2024-12-25 05:24:48 +00:00
if ( typeof initTabs === 'function' ) {
initTabs ( ) // initTabs comes from ui-kit/tabs
}
2021-03-21 17:26:45 +00:00
handleAnchor ( )
topNavbarOffset = document . getElementById ( 'navigation-wrapper' )
2022-01-01 21:40:22 +00:00
darkModeSwitch ( )
2021-03-21 17:26:45 +00:00
} )
2021-09-21 17:22:52 +00:00
const darkModeSwitch = ( ) => {
const localStorageKey = "dokka-dark-mode"
2024-12-25 05:24:48 +00:00
const storage = safeLocalStorage . getItem ( localStorageKey )
2022-10-22 12:06:20 +00:00
const osDarkSchemePreferred = window . matchMedia && window . matchMedia ( '(prefers-color-scheme: dark)' ) . matches
const darkModeEnabled = storage ? JSON . parse ( storage ) : osDarkSchemePreferred
2021-09-21 17:22:52 +00:00
const element = document . getElementById ( "theme-toggle-button" )
2022-10-22 12:06:20 +00:00
initPlayground ( darkModeEnabled ? samplesDarkThemeName : samplesLightThemeName )
2021-09-21 17:22:52 +00:00
element . addEventListener ( 'click' , ( ) => {
const enabledClasses = document . getElementsByTagName ( "html" ) [ 0 ] . classList
enabledClasses . toggle ( "theme-dark" )
//if previously we had saved dark theme then we set it to light as this is what we save in local storage
const darkModeEnabled = enabledClasses . contains ( "theme-dark" )
if ( darkModeEnabled ) {
initPlayground ( samplesDarkThemeName )
2022-01-01 21:40:22 +00:00
} else {
2021-09-21 17:22:52 +00:00
initPlayground ( samplesLightThemeName )
}
2024-12-25 05:24:48 +00:00
safeLocalStorage . setItem ( localStorageKey , JSON . stringify ( darkModeEnabled ) )
2021-09-21 17:22:52 +00:00
} )
}
const initPlayground = ( theme ) => {
2022-01-01 21:40:22 +00:00
if ( ! samplesAreEnabled ( ) ) return
2021-09-21 17:22:52 +00:00
instances . forEach ( instance => instance . destroy ( ) )
instances = [ ]
// Manually tag code fragments as not processed by playground since we also manually destroy all of its instances
document . querySelectorAll ( 'code.runnablesample' ) . forEach ( node => {
node . removeAttribute ( "data-kotlin-playground-initialized" ) ;
} )
KotlinPlayground ( 'code.runnablesample' , {
getInstance : playgroundInstance => {
instances . push ( playgroundInstance )
} ,
theme : theme
} ) ;
}
// We check if type is accessible from the current scope to determine if samples script is present
// As an alternative we could extract this samples-specific script to new js file but then we would handle dark mode in 2 separate files which is not ideal
const samplesAreEnabled = ( ) => {
try {
2024-12-25 05:24:48 +00:00
if ( typeof KotlinPlayground === 'undefined' ) {
// KotlinPlayground is exported universally as a global variable or as a module
// Due to possible interaction with other js scripts KotlinPlayground may not be accessible directly from `window`, so we need an additional check
KotlinPlayground = exports . KotlinPlayground ;
}
return typeof KotlinPlayground === 'function' ;
2021-09-21 17:22:52 +00:00
} catch ( e ) {
return false
}
}
2021-03-21 17:26:45 +00:00
// Hash change is needed in order to allow for linking inside the same page with anchors
// If this is not present user is forced to refresh the site in order to use an anchor
window . onhashchange = handleAnchor
2022-01-01 21:40:22 +00:00
function scrollToElementInContent ( element ) {
const scrollToElement = ( ) => document . getElementById ( 'main' ) . scrollTo ( {
top : element . offsetTop - topNavbarOffset . offsetHeight ,
behavior : "smooth"
} )
2021-03-21 17:26:45 +00:00
const waitAndScroll = ( ) => {
setTimeout ( ( ) => {
2022-01-01 21:40:22 +00:00
if ( topNavbarOffset ) {
2021-03-21 17:26:45 +00:00
scrollToElement ( )
} else {
waitForScroll ( )
}
} , 50 )
}
2022-01-01 21:40:22 +00:00
if ( topNavbarOffset ) {
2021-03-21 17:26:45 +00:00
scrollToElement ( )
} else {
waitAndScroll ( )
}
}
function handleAnchor ( ) {
2022-01-01 21:40:22 +00:00
if ( highlightedAnchor ) {
2021-03-21 17:26:45 +00:00
highlightedAnchor . classList . remove ( 'anchor-highlight' )
highlightedAnchor = null ;
}
2023-03-04 05:05:39 +00:00
let searchForContentTarget = function ( element ) {
2022-01-01 21:40:22 +00:00
if ( element && element . hasAttribute ) {
2023-03-04 05:05:39 +00:00
if ( element . hasAttribute ( "data-togglable" ) ) return element . getAttribute ( "data-togglable" ) ;
else return searchForContentTarget ( element . parentNode )
2021-03-21 17:26:45 +00:00
} else return null
}
2023-03-04 05:05:39 +00:00
let findAnyTab = function ( target ) {
let result = null
document . querySelectorAll ( 'div[tabs-section] > button[data-togglable]' )
. forEach ( node => {
if ( node . getAttribute ( "data-togglable" ) . split ( "," ) . includes ( target ) ) {
result = node
}
} )
return result
}
2021-03-21 17:26:45 +00:00
let anchor = window . location . hash
2024-12-25 05:24:48 +00:00
if ( anchor !== "" ) {
2021-03-21 17:26:45 +00:00
anchor = anchor . substring ( 1 )
2022-01-01 21:40:22 +00:00
let element = document . querySelector ( 'a[data-name="' + anchor + '"]' )
2023-03-04 05:05:39 +00:00
2021-03-21 17:26:45 +00:00
if ( element ) {
const content = element . nextElementSibling
2023-03-04 05:05:39 +00:00
const contentStyle = window . getComputedStyle ( content )
2024-12-25 05:24:48 +00:00
if ( contentStyle . display === 'none' ) {
2023-03-04 05:05:39 +00:00
let tab = findAnyTab ( searchForContentTarget ( content ) )
if ( tab ) {
2024-12-25 05:24:48 +00:00
toggleSections ( tab ) // toggleSections comes from ui-kit/tabs
2023-03-04 05:05:39 +00:00
}
}
2022-01-01 21:40:22 +00:00
if ( content ) {
2021-03-21 17:26:45 +00:00
content . classList . add ( 'anchor-highlight' )
highlightedAnchor = content
}
scrollToElementInContent ( element )
}
}
}
function filterButtonHandler ( event ) {
2024-12-25 05:24:48 +00:00
if ( event . target . tagName === "BUTTON" && event . target . hasAttribute ( "data-filter" ) ) {
2022-01-01 21:40:22 +00:00
let sourceset = event . target . getAttribute ( "data-filter" )
2024-12-25 05:24:48 +00:00
if ( filteringContext . activeFilters . indexOf ( sourceset ) !== - 1 ) {
2022-01-01 21:40:22 +00:00
filterSourceset ( sourceset )
} else {
unfilterSourceset ( sourceset )
2021-03-21 17:26:45 +00:00
}
2022-01-01 21:40:22 +00:00
}
2021-03-21 17:26:45 +00:00
}
function initializeFiltering ( ) {
filteringContext . dependencies = JSON . parse ( sourceset _dependencies )
document . querySelectorAll ( "#filter-section > button" )
. forEach ( p => filteringContext . restrictedDependencies . push ( p . getAttribute ( "data-filter" ) ) )
Object . keys ( filteringContext . dependencies ) . forEach ( p => {
filteringContext . dependencies [ p ] = filteringContext . dependencies [ p ]
. filter ( q => - 1 !== filteringContext . restrictedDependencies . indexOf ( q ) )
} )
2024-12-25 05:24:48 +00:00
let cached = safeLocalStorage . getItem ( 'inactive-filters' )
2021-03-21 17:26:45 +00:00
if ( cached ) {
let parsed = JSON . parse ( cached )
filteringContext . activeFilters = filteringContext . restrictedDependencies
2024-12-25 05:24:48 +00:00
. filter ( q => parsed . indexOf ( q ) === - 1 )
2021-03-21 17:26:45 +00:00
} else {
filteringContext . activeFilters = filteringContext . restrictedDependencies
}
refreshFiltering ( )
}
function filterSourceset ( sourceset ) {
2024-12-25 05:24:48 +00:00
filteringContext . activeFilters = filteringContext . activeFilters . filter ( p => p !== sourceset )
2021-03-21 17:26:45 +00:00
refreshFiltering ( )
addSourcesetFilterToCache ( sourceset )
}
function unfilterSourceset ( sourceset ) {
2024-12-25 05:24:48 +00:00
if ( filteringContext . activeFilters . length === 0 ) {
2021-03-21 17:26:45 +00:00
filteringContext . activeFilters = filteringContext . dependencies [ sourceset ] . concat ( [ sourceset ] )
refreshFiltering ( )
filteringContext . dependencies [ sourceset ] . concat ( [ sourceset ] ) . forEach ( p => removeSourcesetFilterFromCache ( p ) )
} else {
filteringContext . activeFilters . push ( sourceset )
refreshFiltering ( )
removeSourcesetFilterFromCache ( sourceset )
}
}
function addSourcesetFilterToCache ( sourceset ) {
2024-12-25 05:24:48 +00:00
let cached = safeLocalStorage . getItem ( 'inactive-filters' )
2021-03-21 17:26:45 +00:00
if ( cached ) {
let parsed = JSON . parse ( cached )
2024-12-25 05:24:48 +00:00
safeLocalStorage . setItem ( 'inactive-filters' , JSON . stringify ( parsed . concat ( [ sourceset ] ) ) )
2021-03-21 17:26:45 +00:00
} else {
2024-12-25 05:24:48 +00:00
safeLocalStorage . setItem ( 'inactive-filters' , JSON . stringify ( [ sourceset ] ) )
2021-03-21 17:26:45 +00:00
}
}
function removeSourcesetFilterFromCache ( sourceset ) {
2024-12-25 05:24:48 +00:00
let cached = safeLocalStorage . getItem ( 'inactive-filters' )
2021-03-21 17:26:45 +00:00
if ( cached ) {
let parsed = JSON . parse ( cached )
2024-12-25 05:24:48 +00:00
safeLocalStorage . setItem ( 'inactive-filters' , JSON . stringify ( parsed . filter ( p => p !== sourceset ) ) )
2021-03-21 17:26:45 +00:00
}
}
2024-12-25 05:24:48 +00:00
function refreshSourcesetsCache ( ) {
safeLocalStorage . setItem ( 'inactive-filters' , JSON . stringify ( filteringContext . restrictedDependencies . filter ( p => - 1 === filteringContext . activeFilters . indexOf ( p ) ) ) )
2021-03-21 17:26:45 +00:00
}
2024-12-25 05:24:48 +00:00
2021-03-21 17:26:45 +00:00
function togglePlatformDependent ( e , container ) {
let target = e . target
2024-12-25 05:24:48 +00:00
if ( target . tagName !== 'BUTTON' ) return ;
2021-03-21 17:26:45 +00:00
let index = target . getAttribute ( 'data-toggle' )
2022-01-01 21:40:22 +00:00
for ( let child of container . children ) {
if ( child . hasAttribute ( 'data-toggle-list' ) ) {
for ( let bm of child . children ) {
2024-12-25 05:24:48 +00:00
if ( bm === target ) {
2022-01-01 21:40:22 +00:00
bm . setAttribute ( 'data-active' , "" )
2024-12-25 05:24:48 +00:00
} else if ( bm !== target ) {
2021-03-21 17:26:45 +00:00
bm . removeAttribute ( 'data-active' )
}
}
2024-12-25 05:24:48 +00:00
} else if ( child . getAttribute ( 'data-togglable' ) === index ) {
2022-01-01 21:40:22 +00:00
child . setAttribute ( 'data-active' , "" )
} else {
2021-03-21 17:26:45 +00:00
child . removeAttribute ( 'data-active' )
}
}
}
function refreshFiltering ( ) {
let sourcesetList = filteringContext . activeFilters
document . querySelectorAll ( "[data-filterable-set]" )
. forEach (
elem => {
2023-06-09 06:14:58 +00:00
let platformList = elem . getAttribute ( "data-filterable-set" ) . split ( ',' ) . filter ( v => - 1 !== sourcesetList . indexOf ( v ) )
elem . setAttribute ( "data-filterable-current" , platformList . join ( ',' ) )
2021-03-21 17:26:45 +00:00
}
)
refreshFilterButtons ( )
refreshPlatformTabs ( )
2022-01-01 21:40:22 +00:00
refreshNoContentNotification ( )
2023-11-05 06:58:16 +00:00
refreshPlaygroundSamples ( )
}
function refreshPlaygroundSamples ( ) {
document . querySelectorAll ( 'code.runnablesample' ) . forEach ( node => {
const playground = node . KotlinPlayground ;
/ * S o m e s a m p l e s m a y b e h i d d e n b y f i l t e r , t h e y h a v e 0 p x h e i g h t f o r v i s i b l e c o d e a r e a
* after rendering . Call this method for re - calculate code area height * /
playground && playground . view . codemirror . refresh ( ) ;
} ) ;
2022-01-01 21:40:22 +00:00
}
function refreshNoContentNotification ( ) {
const element = document . getElementsByClassName ( "main-content" ) [ 0 ]
2024-12-25 05:24:48 +00:00
const filteredMessage = document . querySelector ( ".filtered-message" )
2022-01-01 21:40:22 +00:00
if ( filteringContext . activeFilters . length === 0 ) {
element . style . display = "none" ;
2024-12-25 05:24:48 +00:00
if ( ! filteredMessage ) {
const appended = document . createElement ( "div" )
appended . className = "filtered-message"
appended . innerText = "All documentation is filtered, please adjust your source set filters in top-right corner of the screen"
sourcesetNotification = appended
element . parentNode . prepend ( appended )
}
2022-01-01 21:40:22 +00:00
} else {
if ( sourcesetNotification ) sourcesetNotification . remove ( )
element . style . display = "block"
}
2021-03-21 17:26:45 +00:00
}
function refreshPlatformTabs ( ) {
document . querySelectorAll ( ".platform-hinted > .platform-bookmarks-row" ) . forEach (
p => {
let active = false ;
let firstAvailable = null
p . childNodes . forEach (
element => {
2024-12-25 05:24:48 +00:00
if ( element . getAttribute ( "data-filterable-current" ) !== '' ) {
if ( firstAvailable === null ) {
2021-03-21 17:26:45 +00:00
firstAvailable = element
}
2022-01-01 21:40:22 +00:00
if ( element . hasAttribute ( "data-active" ) ) {
2021-03-21 17:26:45 +00:00
active = true ;
}
}
}
)
2024-12-25 05:24:48 +00:00
if ( active === false && firstAvailable ) {
2021-03-21 17:26:45 +00:00
firstAvailable . click ( )
}
}
)
}
function refreshFilterButtons ( ) {
document . querySelectorAll ( "#filter-section > button" )
. forEach ( f => {
2024-12-25 05:24:48 +00:00
if ( filteringContext . activeFilters . indexOf ( f . getAttribute ( "data-filter" ) ) !== - 1 ) {
2022-01-01 21:40:22 +00:00
f . setAttribute ( "data-active" , "" )
2021-03-21 17:26:45 +00:00
} else {
f . removeAttribute ( "data-active" )
}
} )
2024-12-25 05:24:48 +00:00
document . querySelectorAll ( "#filter-section .checkbox--input" )
. forEach ( f => {
f . checked = filteringContext . activeFilters . indexOf ( f . getAttribute ( "data-filter" ) ) !== - 1 ;
} )
2021-03-21 17:26:45 +00:00
}