Tabpane: support persistence of all active tabs (#1611)

This commit is contained in:
Patrice Chalin 2023-07-13 13:11:07 -04:00 committed by GitHub
parent 7f39b8280b
commit b53f5f167c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 23 deletions

View File

@ -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 */ -}}
@ -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" -}}">

View File

@ -1,30 +1,116 @@
// Storage key name also used as a data-* attribute suffix:
const storageKeyName = 'td-tp-persist';
// 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}`;
function tdActivateTabsWithKey(key) {
if (!key) return;
document
.querySelectorAll(`[data-${storageKeyName}="${key}"]`)
.forEach((element) => {
new bootstrap.Tab(element).show();
});
}
// Utilities
function tdPersistActiveTab(activeTabKey) {
if (!tdSupportsLocalStorage()) return;
const _tdPersistCssSelector = (attrValue) =>
attrValue
? `[${td_persistDataAttrName}="${attrValue}"]`
: `[${td_persistDataAttrName}]`;
const _tdStoragePersistKey = (tabKey) =>
td_persistStorageKeyNameBase + ':' + (tabKey || '');
const _tdSupportsLocalStorage = () => typeof Storage !== 'undefined';
// Helpers
function tdPersistKey(key, value) {
// @requires: tdSupportsLocalStorage();
try {
localStorage.setItem(storageKeyName, activeTabKey);
tdActivateTabsWithKey(activeTabKey);
if (value) {
localStorage.setItem(key, value);
} else {
localStorage.removeItem(key);
}
} catch (error) {
console.error(`Unable to save active tab '${activeTabKey}' to localStorage:`, error);
const action = value ? 'add' : 'remove';
console.error(
`Docsy tabpane: unable to ${action} localStorage key '${key}': `,
error
);
}
}
const tdSupportsLocalStorage = () => typeof Storage !== 'undefined';
// Retrieve, increment, and store tab-select event count, then returns it.
function tdGetTabSelectEventCountAndInc() {
// @requires: tdSupportsLocalStorage();
// On page load, activate tabs
if (tdSupportsLocalStorage()) {
const activeTabKey = localStorage.getItem(storageKeyName);
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)).forEach((element) => {
new bootstrap.Tab(element).show();
});
}
function tdPersistActiveTab(activeTabKey) {
if (!_tdSupportsLocalStorage()) return;
tdPersistKey(
_tdStoragePersistKey(activeTabKey),
tdGetTabSelectEventCountAndInc()
);
tdActivateTabsWithKey(activeTabKey);
}
// Handlers
function tdGetAndActivatePersistedTabs(tabs) {
// Get unique persistence keys of tabs in this page
var keyOfTabsInThisPage = [
...new Set(
Array.from(tabs).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;
}
function tdRegisterTabClickHandler(tabs) {
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const activeTabKey = tab.getAttribute(td_persistDataAttrName);
tdPersistActiveTab(activeTabKey);
});
});
}
// Register listeners and activate tabs
window.addEventListener('DOMContentLoaded', () => {
if (!_tdSupportsLocalStorage()) return;
var allTabsInThisPage = document.querySelectorAll(_tdPersistCssSelector());
tdRegisterTabClickHandler(allTabsInThisPage);
tdGetAndActivatePersistedTabs(allTabsInThisPage);
});