From 10dd0553bf0e31d3240ac0036281a0559067eca2 Mon Sep 17 00:00:00 2001 From: berezinant Date: Fri, 26 Apr 2024 11:27:29 +0200 Subject: [PATCH] fix: made tabs work when open via iframe (#3323) --- .../dokka/scripts/platform-content-handler.js | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/dokka-subprojects/plugin-base/src/main/resources/dokka/scripts/platform-content-handler.js b/dokka-subprojects/plugin-base/src/main/resources/dokka/scripts/platform-content-handler.js index 481244e6c6..fe1c57bc35 100644 --- a/dokka-subprojects/plugin-base/src/main/resources/dokka/scripts/platform-content-handler.js +++ b/dokka-subprojects/plugin-base/src/main/resources/dokka/scripts/platform-content-handler.js @@ -1,6 +1,35 @@ /* * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +/** 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); + } + }; +})(); filteringContext = { dependencies: {}, @@ -32,7 +61,7 @@ window.addEventListener('load', () => { const darkModeSwitch = () => { const localStorageKey = "dokka-dark-mode" - const storage = localStorage.getItem(localStorageKey) + const storage = safeLocalStorage.getItem(localStorageKey) const osDarkSchemePreferred = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches const darkModeEnabled = storage ? JSON.parse(storage) : osDarkSchemePreferred const element = document.getElementById("theme-toggle-button") @@ -49,7 +78,7 @@ const darkModeSwitch = () => { } else { initPlayground(samplesLightThemeName) } - localStorage.setItem(localStorageKey, JSON.stringify(darkModeEnabled)) + safeLocalStorage.setItem(localStorageKey, JSON.stringify(darkModeEnabled)) }) } @@ -139,13 +168,13 @@ function handleAnchor() { } let findAnyTab = function (target) { - let result = null + let result = null document.querySelectorAll('div[tabs-section] > button[data-togglable]') - .forEach(node => { - if(node.getAttribute("data-togglable").split(",").includes(target)) { - result = node - } - }) + .forEach(node => { + if (node.getAttribute("data-togglable").split(",").includes(target)) { + result = node + } + }) return result } @@ -157,11 +186,11 @@ function handleAnchor() { if (element) { const content = element.nextElementSibling const contentStyle = window.getComputedStyle(content) - if(contentStyle.display == 'none') { - let tab = findAnyTab(searchForContentTarget(content)) - if (tab) { - toggleSections(tab) - } + if (contentStyle.display == 'none') { + let tab = findAnyTab(searchForContentTarget(content)) + if (tab) { + toggleSections(tab) + } } if (content) { @@ -185,13 +214,18 @@ function initTabs() { const togglable = target ? target.getAttribute("data-togglable") : null; if (!togglable) return; - localStorage.setItem(localStorageKey, JSON.stringify(togglable)); + safeLocalStorage.setItem(localStorageKey, JSON.stringify(togglable)); toggleSections(target); }); }); - const cached = localStorage.getItem(localStorageKey); - if (!cached) return; + let cached = null; + try { + cached = safeLocalStorage.getItem(localStorageKey); + } catch (e) { + console.error('Error reading from local storage', e); + } + console.log('cached tab', cached); const tab = document.querySelector( 'div[tabs-section] > button[data-togglable="' + JSON.parse(cached) + '"]' @@ -227,7 +261,7 @@ function initializeFiltering() { filteringContext.dependencies[p] = filteringContext.dependencies[p] .filter(q => -1 !== filteringContext.restrictedDependencies.indexOf(q)) }) - let cached = window.localStorage.getItem('inactive-filters') + let cached = safeLocalStorage.getItem('inactive-filters') if (cached) { let parsed = JSON.parse(cached) filteringContext.activeFilters = filteringContext.restrictedDependencies @@ -258,20 +292,20 @@ function unfilterSourceset(sourceset) { } function addSourcesetFilterToCache(sourceset) { - let cached = localStorage.getItem('inactive-filters') + let cached = safeLocalStorage.getItem('inactive-filters') if (cached) { let parsed = JSON.parse(cached) - localStorage.setItem('inactive-filters', JSON.stringify(parsed.concat([sourceset]))) + safeLocalStorage.setItem('inactive-filters', JSON.stringify(parsed.concat([sourceset]))) } else { - localStorage.setItem('inactive-filters', JSON.stringify([sourceset])) + safeLocalStorage.setItem('inactive-filters', JSON.stringify([sourceset])) } } function removeSourcesetFilterFromCache(sourceset) { - let cached = localStorage.getItem('inactive-filters') + let cached = safeLocalStorage.getItem('inactive-filters') if (cached) { let parsed = JSON.parse(cached) - localStorage.setItem('inactive-filters', JSON.stringify(parsed.filter(p => p != sourceset))) + safeLocalStorage.setItem('inactive-filters', JSON.stringify(parsed.filter(p => p != sourceset))) } } @@ -291,11 +325,11 @@ function toggleSections(target) { const activateTabsBody = (containerClass) => { document.querySelectorAll("." + containerClass + " *[data-togglable]") .forEach(child => { - if (toggleTargets.includes(child.getAttribute("data-togglable"))) { - child.setAttribute("data-active", "") - } else if(!child.classList.contains("sourceset-dependent-content")) { // data-togglable is used to switch source set as well, ignore it - child.removeAttribute("data-active") - } + if (toggleTargets.includes(child.getAttribute("data-togglable"))) { + child.setAttribute("data-active", "") + } else if (!child.classList.contains("sourceset-dependent-content")) { // data-togglable is used to switch source set as well, ignore it + child.removeAttribute("data-active") + } }) } activateTabs("tabs-section") @@ -350,7 +384,7 @@ function refreshPlaygroundSamples() { function refreshNoContentNotification() { const element = document.getElementsByClassName("main-content")[0] - if(filteringContext.activeFilters.length === 0){ + if (filteringContext.activeFilters.length === 0) { element.style.display = "none"; const appended = document.createElement("div") @@ -359,7 +393,7 @@ function refreshNoContentNotification() { sourcesetNotification = appended element.parentNode.prepend(appended) } else { - if(sourcesetNotification) sourcesetNotification.remove() + if (sourcesetNotification) sourcesetNotification.remove() element.style.display = "block" } }