Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tabpane: support persistence of all active tabs #1611

Merged
merged 8 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions layouts/shortcodes/tabpane.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 -}}

Expand All @@ -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 */ -}}
Expand Down Expand Up @@ -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" -}}">
Expand Down
124 changes: 105 additions & 19 deletions static/js/tabpane-persist.js
Original file line number Diff line number Diff line change
@@ -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();

const storedCount = localStorage.getItem(td_persistCounterStorageKeyName);
let numTabSelectEvents = parseInt(storedCount) || 0;
numTabSelectEvents++;
tdPersistKey(td_persistCounterStorageKeyName, numTabSelectEvents.toString());
return numTabSelectEvents;
}

// Main functions

// On page load, activate tabs
if (tdSupportsLocalStorage()) {
const activeTabKey = localStorage.getItem(storageKeyName);
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);
});