From 65e2ec1ab2b54097648b4af58d6bcb339b0c45c4 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 10:25:21 -0400 Subject: [PATCH 1/8] tabpane-persist.js: tdPersistCssSelector() --- static/js/tabpane-persist.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index cdddfc5aee..d4c00c3ba1 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -1,10 +1,14 @@ -// Storage key name also used as a data-* attribute suffix: -const storageKeyName = 'td-tp-persist'; +const td_persistStorageKeyName = 'td-tp-persist'; +const td_persistDataAttrName = `data-${td_persistStorageKeyName}`; + +const tdPersistCssSelector = (attrValue) => attrValue + ? `[${td_persistDataAttrName}="${attrValue}"]` + : `[${td_persistDataAttrName}]`; function tdActivateTabsWithKey(key) { if (!key) return; document - .querySelectorAll(`[data-${storageKeyName}="${key}"]`) + .querySelectorAll(tdPersistCssSelector(key)) .forEach((element) => { new bootstrap.Tab(element).show(); }); @@ -14,7 +18,7 @@ function tdPersistActiveTab(activeTabKey) { if (!tdSupportsLocalStorage()) return; try { - localStorage.setItem(storageKeyName, activeTabKey); + localStorage.setItem(td_persistStorageKeyName, activeTabKey); tdActivateTabsWithKey(activeTabKey); } catch (error) { console.error(`Unable to save active tab '${activeTabKey}' to localStorage:`, error); @@ -25,6 +29,6 @@ const tdSupportsLocalStorage = () => typeof Storage !== 'undefined'; // On page load, activate tabs if (tdSupportsLocalStorage()) { - const activeTabKey = localStorage.getItem(storageKeyName); + const activeTabKey = localStorage.getItem(td_persistStorageKeyName); tdActivateTabsWithKey(activeTabKey); } From 67c282182ff9a15e1dfad34c5b75431c8d0f56c1 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 10:30:26 -0400 Subject: [PATCH 2/8] Use DOMContentLoaded --- static/js/tabpane-persist.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index d4c00c3ba1..07b0ea89b3 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -1,9 +1,14 @@ const td_persistStorageKeyName = 'td-tp-persist'; const td_persistDataAttrName = `data-${td_persistStorageKeyName}`; +// Helpers + const tdPersistCssSelector = (attrValue) => attrValue ? `[${td_persistDataAttrName}="${attrValue}"]` : `[${td_persistDataAttrName}]`; +const tdSupportsLocalStorage = () => typeof Storage !== 'undefined'; + +// Main functions function tdActivateTabsWithKey(key) { if (!key) return; @@ -25,10 +30,10 @@ function tdPersistActiveTab(activeTabKey) { } } -const tdSupportsLocalStorage = () => typeof Storage !== 'undefined'; +// Register listeners -// On page load, activate tabs -if (tdSupportsLocalStorage()) { +window.addEventListener('DOMContentLoaded', () => { + if (!tdSupportsLocalStorage()) return; const activeTabKey = localStorage.getItem(td_persistStorageKeyName); tdActivateTabsWithKey(activeTabKey); -} +}); From 4573eeaf1b6954a08bf573aa5476dcba97579a10 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 11:58:53 -0400 Subject: [PATCH 3/8] Support persistence of multiple active tabs --- static/js/tabpane-persist.js | 90 +++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 16 deletions(-) diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index 07b0ea89b3..25f77a33dd 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -1,39 +1,97 @@ -const td_persistStorageKeyName = 'td-tp-persist'; -const td_persistDataAttrName = `data-${td_persistStorageKeyName}`; +// Storage key names and data attribute name: +const td_persistStorageKeyNameBase = 'td-tp-persist'; +const td_persistCounterStorageKeyName = `${td_persistStorageKeyNameBase}-count`; +const td_persistDataAttrName = `data-${td_persistStorageKeyNameBase}`; -// Helpers +// Utilities -const tdPersistCssSelector = (attrValue) => attrValue +const _tdPersistCssSelector = (attrValue) => attrValue ? `[${td_persistDataAttrName}="${attrValue}"]` : `[${td_persistDataAttrName}]`; -const tdSupportsLocalStorage = () => typeof Storage !== 'undefined'; + +const _tdStoragePersistKey = (tabKey) => + td_persistStorageKeyNameBase + ':' + (tabKey || ''); + +const _tdSupportsLocalStorage = () => typeof Storage !== 'undefined'; + +// Helpers + +function tdPersistKey(key, value) { + // @requires: tdSupportsLocalStorage(); + + try { + if (value) { + localStorage.setItem(key, value); + } else { + localStorage.removeItem(key); + } + } catch (error) { + const action = value ? 'add' : 'remove'; + console.error(`Docsy tabpane: unable to ${action} localStorage key '${key}': `, error); + } +} + +// Retrieve, increment, and store tab-select event count, then returns it. +function tdGetTabSelectEventCount() { + // @requires: tdSupportsLocalStorage(); + + const storedCount = localStorage.getItem(td_persistCounterStorageKeyName); + let numTabSelectEvents = parseInt(storedCount) || 0; + numTabSelectEvents++; + tdPersistKey(td_persistCounterStorageKeyName, numTabSelectEvents.toString()); + return numTabSelectEvents; +} // Main functions function tdActivateTabsWithKey(key) { if (!key) return; + document - .querySelectorAll(tdPersistCssSelector(key)) + .querySelectorAll(_tdPersistCssSelector(key)) .forEach((element) => { new bootstrap.Tab(element).show(); }); } function tdPersistActiveTab(activeTabKey) { - if (!tdSupportsLocalStorage()) return; + if (!_tdSupportsLocalStorage()) return; - try { - localStorage.setItem(td_persistStorageKeyName, activeTabKey); - tdActivateTabsWithKey(activeTabKey); - } catch (error) { - console.error(`Unable to save active tab '${activeTabKey}' to localStorage:`, error); - } + tdPersistKey(_tdStoragePersistKey(activeTabKey), tdGetTabSelectEventCount()); + tdActivateTabsWithKey(activeTabKey); +} + +function tdGetAndActivatePersistedTabsInThisPage() { + // Get keys of tabs in this page + var keyOfTabsInThisPage = [...new Set( + Array.from(document.querySelectorAll(_tdPersistCssSelector())) + .map(el => el.getAttribute(td_persistDataAttrName)) + )]; + + // Create a list of active tabs with their age: + let key_ageList = keyOfTabsInThisPage + // Map to [tab-key, last-activated-age] + .map(k => [ + k, + parseInt(localStorage.getItem(_tdStoragePersistKey(k))) || 0 + ]) + // Exclude tabs that have never been activated + .filter(([k, v]) => v) + // Sort from oldest selected to most recently selected + .sort((a, b) => a[1] - b[1]); + + // Activate tabs from the oldest to the newest + key_ageList.forEach(([key]) => { + tdActivateTabsWithKey(key); + }); + + return key_ageList; } // Register listeners window.addEventListener('DOMContentLoaded', () => { - if (!tdSupportsLocalStorage()) return; - const activeTabKey = localStorage.getItem(td_persistStorageKeyName); - tdActivateTabsWithKey(activeTabKey); + if (!_tdSupportsLocalStorage()) return; + tdGetAndActivatePersistedTabsInThisPage(); + // TODO: ... tabElement.addEventListener('click', f); }); From 39f8e044790b187d86aed51e0d9021a0d18b3a4b Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 12:01:02 -0400 Subject: [PATCH 4/8] Prettify --- static/js/tabpane-persist.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index 25f77a33dd..a499da18b5 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -5,9 +5,10 @@ const td_persistDataAttrName = `data-${td_persistStorageKeyNameBase}`; // Utilities -const _tdPersistCssSelector = (attrValue) => attrValue - ? `[${td_persistDataAttrName}="${attrValue}"]` - : `[${td_persistDataAttrName}]`; +const _tdPersistCssSelector = (attrValue) => + attrValue + ? `[${td_persistDataAttrName}="${attrValue}"]` + : `[${td_persistDataAttrName}]`; const _tdStoragePersistKey = (tabKey) => td_persistStorageKeyNameBase + ':' + (tabKey || ''); @@ -27,7 +28,10 @@ function tdPersistKey(key, value) { } } catch (error) { const action = value ? 'add' : 'remove'; - console.error(`Docsy tabpane: unable to ${action} localStorage key '${key}': `, error); + console.error( + `Docsy tabpane: unable to ${action} localStorage key '${key}': `, + error + ); } } @@ -47,11 +51,9 @@ function tdGetTabSelectEventCount() { function tdActivateTabsWithKey(key) { if (!key) return; - document - .querySelectorAll(_tdPersistCssSelector(key)) - .forEach((element) => { - new bootstrap.Tab(element).show(); - }); + document.querySelectorAll(_tdPersistCssSelector(key)).forEach((element) => { + new bootstrap.Tab(element).show(); + }); } function tdPersistActiveTab(activeTabKey) { @@ -63,17 +65,20 @@ function tdPersistActiveTab(activeTabKey) { function tdGetAndActivatePersistedTabsInThisPage() { // Get keys of tabs in this page - var keyOfTabsInThisPage = [...new Set( - Array.from(document.querySelectorAll(_tdPersistCssSelector())) - .map(el => el.getAttribute(td_persistDataAttrName)) - )]; + var keyOfTabsInThisPage = [ + ...new Set( + Array.from(document.querySelectorAll(_tdPersistCssSelector())).map((el) => + el.getAttribute(td_persistDataAttrName) + ) + ), + ]; // Create a list of active tabs with their age: let key_ageList = keyOfTabsInThisPage // Map to [tab-key, last-activated-age] - .map(k => [ + .map((k) => [ k, - parseInt(localStorage.getItem(_tdStoragePersistKey(k))) || 0 + parseInt(localStorage.getItem(_tdStoragePersistKey(k))) || 0, ]) // Exclude tabs that have never been activated .filter(([k, v]) => v) From ec334bc9bfe45abe71d72941b9095234efd63416 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 12:14:28 -0400 Subject: [PATCH 5/8] Register click handlers rather than using onclick --- layouts/shortcodes/tabpane.html | 1 - static/js/tabpane-persist.js | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/layouts/shortcodes/tabpane.html b/layouts/shortcodes/tabpane.html index d67567b08a..6d16bce494 100644 --- a/layouts/shortcodes/tabpane.html +++ b/layouts/shortcodes/tabpane.html @@ -106,7 +106,6 @@ {{ if $disabled }} disabled{{ end -}}" id="{{ $tabid }}" data-bs-toggle="tab" data-bs-target="#{{ $entryid }}" role="tab" {{ if and $persistTab $persistKey -}} - onclick="tdPersistActiveTab({{ $persistKey }});" {{/* */ -}} {{ printf "%s=%q " $tpPersistAttrName $persistKey | safeHTMLAttr -}} {{ end -}} aria-controls="{{- $entryid -}}" aria-selected="{{- cond ( and ( not $activeSet ) ( not $disabled ) ) "true" "false" -}}"> diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index a499da18b5..25b2216d07 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -64,7 +64,7 @@ function tdPersistActiveTab(activeTabKey) { } function tdGetAndActivatePersistedTabsInThisPage() { - // Get keys of tabs in this page + // Get unique persistence keys of tabs in this page var keyOfTabsInThisPage = [ ...new Set( Array.from(document.querySelectorAll(_tdPersistCssSelector())).map((el) => @@ -93,10 +93,22 @@ function tdGetAndActivatePersistedTabsInThisPage() { return key_ageList; } +function tdRegisterTabClickHandlers() { + // Tabs in this page + var tabs = document.querySelectorAll(_tdPersistCssSelector()); + + tabs.forEach((tab) => { + tab.addEventListener('click', () => { + const activeTabKey = tab.getAttribute(td_persistDataAttrName); + tdPersistActiveTab(activeTabKey); + }); + }); +} + // Register listeners window.addEventListener('DOMContentLoaded', () => { if (!_tdSupportsLocalStorage()) return; tdGetAndActivatePersistedTabsInThisPage(); - // TODO: ... tabElement.addEventListener('click', f); + tdRegisterTabClickHandlers(); }); From 96b41d392a69dfa3f1a20cf212146dfe7771243c Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 12:21:09 -0400 Subject: [PATCH 6/8] Compute list of tabs only once --- static/js/tabpane-persist.js | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index 25b2216d07..80fb9fc5ba 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -63,13 +63,11 @@ function tdPersistActiveTab(activeTabKey) { tdActivateTabsWithKey(activeTabKey); } -function tdGetAndActivatePersistedTabsInThisPage() { +function tdGetAndActivatePersistedTabs(tabs) { // Get unique persistence keys of tabs in this page var keyOfTabsInThisPage = [ ...new Set( - Array.from(document.querySelectorAll(_tdPersistCssSelector())).map((el) => - el.getAttribute(td_persistDataAttrName) - ) + Array.from(tabs).map((el) => el.getAttribute(td_persistDataAttrName)) ), ]; @@ -93,10 +91,7 @@ function tdGetAndActivatePersistedTabsInThisPage() { return key_ageList; } -function tdRegisterTabClickHandlers() { - // Tabs in this page - var tabs = document.querySelectorAll(_tdPersistCssSelector()); - +function tdRegisterTabClickHandler(tabs) { tabs.forEach((tab) => { tab.addEventListener('click', () => { const activeTabKey = tab.getAttribute(td_persistDataAttrName); @@ -105,10 +100,12 @@ function tdRegisterTabClickHandlers() { }); } -// Register listeners +// Register listeners and activate tabs window.addEventListener('DOMContentLoaded', () => { if (!_tdSupportsLocalStorage()) return; - tdGetAndActivatePersistedTabsInThisPage(); - tdRegisterTabClickHandlers(); + + var allTabsInThisPage = document.querySelectorAll(_tdPersistCssSelector()); + tdRegisterTabClickHandler(allTabsInThisPage); + tdGetAndActivatePersistedTabs(allTabsInThisPage); }); From 28f91225fd1a2ae5625c63544674fa2b832e2c38 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 12:30:15 -0400 Subject: [PATCH 7/8] Rename persist param value `none` to `disabled` --- layouts/shortcodes/tabpane.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layouts/shortcodes/tabpane.html b/layouts/shortcodes/tabpane.html index 6d16bce494..ad58f413c7 100644 --- a/layouts/shortcodes/tabpane.html +++ b/layouts/shortcodes/tabpane.html @@ -25,9 +25,9 @@ {{ $_persist := .Get "persist" -}} {{ with $_persist -}} - {{ $matched := findRE "^(header|lang|none)$" . -}} + {{ $matched := findRE "^(header|lang|disabled)$" . -}} {{ if not $matched -}} - {{ errorf "Shortcode %q: parameter %q should be one of 'header', 'lang', or 'none'; but got %s. Error position: %s" $.Name "persist" $_persist $.Position -}} + {{ errorf "Shortcode %q: parameter %q should be one of 'header', 'lang', or 'disabled'; but got %s. Error position: %s" $.Name "persist" $_persist $.Position -}} {{ end -}} {{ end -}} @@ -44,7 +44,7 @@ {{ $langEqualsHeader := default false ($.Get "langEqualsHeader") -}} {{ $deprecatedPersistLang := $_persistLang | default true -}} {{ $persistKeyKind := $_persist | default (cond (eq $langPane "") "lang" "header") -}} -{{ $persistTab := and $deprecatedPersistLang (ne $persistKeyKind "none") -}} +{{ $persistTab := and $deprecatedPersistLang (ne $persistKeyKind "disabled") -}} {{ $rightPane := default false ($.Get "right") -}} {{ $activeSet := false -}} {{/* Scratchpad gets populated through call to .Inner */ -}} From efffaf03f2c7db98de63b3a61d71ac5361d3db87 Mon Sep 17 00:00:00 2001 From: Patrice Chalin Date: Thu, 6 Jul 2023 12:57:09 -0400 Subject: [PATCH 8/8] Function name tweak --- static/js/tabpane-persist.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/static/js/tabpane-persist.js b/static/js/tabpane-persist.js index 80fb9fc5ba..2362130754 100644 --- a/static/js/tabpane-persist.js +++ b/static/js/tabpane-persist.js @@ -36,7 +36,7 @@ function tdPersistKey(key, value) { } // Retrieve, increment, and store tab-select event count, then returns it. -function tdGetTabSelectEventCount() { +function tdGetTabSelectEventCountAndInc() { // @requires: tdSupportsLocalStorage(); const storedCount = localStorage.getItem(td_persistCounterStorageKeyName); @@ -59,10 +59,15 @@ function tdActivateTabsWithKey(key) { function tdPersistActiveTab(activeTabKey) { if (!_tdSupportsLocalStorage()) return; - tdPersistKey(_tdStoragePersistKey(activeTabKey), tdGetTabSelectEventCount()); + tdPersistKey( + _tdStoragePersistKey(activeTabKey), + tdGetTabSelectEventCountAndInc() + ); tdActivateTabsWithKey(activeTabKey); } +// Handlers + function tdGetAndActivatePersistedTabs(tabs) { // Get unique persistence keys of tabs in this page var keyOfTabsInThisPage = [