diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/body-start/index.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/body-start/index.js index 8c2cef3cd33..07425b5b0b8 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/body-start/index.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/body-start/index.js @@ -1,4 +1,4 @@ -import { setIsTranslating, isMobile, toggleBooleanClass } from '../main/helpers/index'; +import { setIsTranslating, isMobile, toggleBooleanClass } from '../main/helpers/helpers'; (function () { if (!window.turbolinksLoaded) { @@ -7,6 +7,10 @@ import { setIsTranslating, isMobile, toggleBooleanClass } from '../main/helpers/ setIsTranslating(document.body); } + if (!window.truste) { + window.truste = {}; + } + if (isMobile()) { // This body class is default open, toggle off if on mobile. toggleBooleanClass('explorer-open', document.body, false); diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/early/index.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/early/index.js new file mode 100644 index 00000000000..e70e9e94706 --- /dev/null +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/early/index.js @@ -0,0 +1,5 @@ +import { scrollToActiveExplorerNode } from '../main/helpers/helpers.js'; + +(function () { + scrollToActiveExplorerNode(); +})(); diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/magic-helpers.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/magic-helpers.js index 81e8327a352..ea14c97a9d1 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/magic-helpers.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/magic-helpers.js @@ -1,6 +1,6 @@ 'use strict'; -import { isDesktop, isMobile } from '../helpers'; +import { isDesktop, isMobile } from '../helpers/helpers'; export function alpineRegisterMagicHelpers(Alpine) { // $copy is a magic helper that copys the content of the current or the supplied element to the clipboard. diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/tabs.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/tabs.js index 32c1b364f02..9a8a8e5cd26 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/tabs.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/tabs.js @@ -1,6 +1,6 @@ 'use strict'; -import { getScrollLeft } from '../helpers/index'; +import { getScrollLeft } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[tabs]') : function () {}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/trustarc.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/trustarc.js index f6bc392e514..b99b847dbb8 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/trustarc.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/components/trustarc.js @@ -1,4 +1,4 @@ -import { getCookie } from '../helpers'; +import { getCookie } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[trustarc]') : function () {}; @@ -23,7 +23,7 @@ export function initConsentManager(trustarcDomain, trustecm, callback) { }, }; - onConsent = function (e) { + const onConsent = function (e) { if (typeof e.data != 'string') { return; } diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/helpers.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/helpers.js index 3f617cd4fee..b340cb1eb69 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/helpers.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/helpers.js @@ -19,21 +19,6 @@ export function toggleBooleanClass(baseClass, el, truthy) { } } -export function toggleClass(openClass, el, open) { - if (open) { - el.classList.add(openClass); - } else { - el.classList.remove(openClass); - } -} - -export function isObjectEmpty(object) { - for (key in object) { - return false; - } - return true; -} - // normalizeSpace replaces any whitespace character (spaces, tabs, newlines and Unicode space) with a space. // Multiple spaces are collapsed into one. export function normalizeSpace(text) { @@ -47,11 +32,6 @@ export function sanitizeHTML(text) { return element.innerHTML; } -export const capitalize = (s) => { - if (typeof s !== 'string') return ''; - return s.charAt(0).toUpperCase() + s.slice(1); -}; - export function toDateString(date) { var year = date.getFullYear().toString().substr(-2); var month = date.getMonth() + 1; @@ -68,7 +48,7 @@ export function toDateString(date) { } // https://gist.github.com/rmariuzzo/8761698 -export function sprintf(format) { +function sprintf(format) { var args = Array.prototype.slice.call(arguments, 1); var i = 0; return format.replace(/%s/g, function () { @@ -76,18 +56,6 @@ export function sprintf(format) { }); } -// Function borrowed from https://stackoverflow.com/questions/123999/how-can-i-tell-if-a-dom-element-is-visible-in-the-current-viewport/7557433#7557433 -export function isElementInViewport(el) { - var rect = el.getBoundingClientRect(); - - return ( - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && - rect.right <= (window.innerWidth || document.documentElement.clientWidth) - ); -} - // getScrollLeft returns the scrollLeft value needed to make the child element visible. export function getScrollLeft(parent, child) { const parentRect = parent.getBoundingClientRect(); @@ -101,20 +69,33 @@ export function getScrollLeft(parent, child) { return childRect.left - parentRect.left; } -// getOffsetTop returns the distance from container down to el. -export function getOffsetTop(container, el) { - var distance = 0; - - if (el.offsetParent) { - while (true) { - distance += el.offsetTop; - el = el.offsetParent; - if (!el || el === container) { - break; - } +// scrollToActiveExplorerNode scrolls the explorer to the active node. +export function scrollToActiveExplorerNode() { + let [explorer, offsetTop] = activeExplorerNodeOffsetTop(); + if (offsetTop == -1) { + return; + } + explorer.scroll({ top: offsetTop - 20, behavior: 'smooth' }); +} + +function activeExplorerNodeOffsetTop() { + let target = document.querySelector('.is-active-page'); + if (!target) { + // If there is no active page, look for the last open node. + let explorerNodes = document.querySelectorAll('.explorer-node-open:last-child'); + if (explorerNodes.length > 0) { + target = explorerNodes[explorerNodes.length - 1]; } } - return distance < 0 ? 0 : distance; + + if (target) { + let explorer = document.getElementById('explorer'); + if (explorer) { + return [explorer, target.offsetTop]; + } + } + + return [null, -1]; } export function setIsTranslating(el, timeout = 1000) { @@ -166,7 +147,7 @@ export function getIntParamFromLocation(param) { return 0; } -export function isIterable(obj) { +function isIterable(obj) { return Symbol.iterator in Object(obj); } @@ -178,7 +159,7 @@ export function isDesktop() { return isScreenLargerThan(1279); // xl in Tailwind config. } -export function isScreenLargerThan(px) { +function isScreenLargerThan(px) { return document.documentElement.clientWidth > px; } @@ -191,10 +172,6 @@ export function isTouchDevice() { } } -export function isTopBarPinned() { - return document.body.classList.contains('is-topbar-pinned'); -} - export function updatePaginationParamInLocation(pageKey, pageNum, firstPage = 1) { let url = new URL(window.location); url.hash = ''; @@ -206,21 +183,6 @@ export function updatePaginationParamInLocation(pageKey, pageNum, firstPage = 1) window.history.replaceState({ turbo: {} }, '', url); } -export function walk(el, callback) { - if (typeof ShadowRoot === 'function' && el instanceof ShadowRoot) { - Array.from(el.childNodes).forEach((el2) => walk(el2, callback)); - return; - } - let skip = false; - callback(el, () => (skip = true)); - if (skip) return; - let node = el.firstElementChild; - while (node) { - walk(node, callback, false); - node = node.nextElementSibling; - } -} - const month = 30 * 24 * 60 * 60 * 1000; export function setCookie(name, value, duration = month) { diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/index.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/index.js deleted file mode 100644 index 1bbf0a6c93d..00000000000 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export * from './helpers'; -export * from './leak-checker'; -export * from './lru'; -export * from './smartqueue'; -export * from './swipe'; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/lru.d.ts b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/lru.d.ts deleted file mode 100644 index 8672c720ea4..00000000000 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/lru.d.ts +++ /dev/null @@ -1,83 +0,0 @@ -export class LRUMap { - // Construct a new cache object which will hold up to limit entries. - // When the size == limit, a `put` operation will evict the oldest entry. - // - // If `entries` is provided, all entries are added to the new map. - // `entries` should be an Array or other iterable object whose elements are - // key-value pairs (2-element Arrays). Each key-value pair is added to the new Map. - // null is treated as undefined. - constructor(limit :number, entries? :Iterable<[K,V]>); - - // Convenience constructor equivalent to `new LRUMap(count(entries), entries)` - constructor(entries :Iterable<[K,V]>); - - // Current number of items - size :number; - - // Maximum number of items this map can hold - limit :number; - - // Least recently-used entry. Invalidated when map is modified. - oldest :Entry; - - // Most recently-used entry. Invalidated when map is modified. - newest :Entry; - - // Replace all values in this map with key-value pairs (2-element Arrays) from - // provided iterable. - assign(entries :Iterable<[K,V]>) : void; - - // Put into the cache associated with . Replaces any existing entry - // with the same key. Returns `this`. - set(key :K, value :V) : LRUMap; - - // Purge the least recently used (oldest) entry from the cache. - // Returns the removed entry or undefined if the cache was empty. - shift() : [K,V] | undefined; - - // Get and register recent use of . - // Returns the value associated with or undefined if not in cache. - get(key :K) : V | undefined; - - // Check if there's a value for key in the cache without registering recent use. - has(key :K) : boolean; - - // Access value for without registering recent use. Useful if you do not - // want to chage the state of the map, but only "peek" at it. - // Returns the value associated with if found, or undefined if not found. - find(key :K) : V | undefined; - - // Remove entry from cache and return its value. - // Returns the removed value, or undefined if not found. - delete(key :K) : V | undefined; - - // Removes all entries - clear() : void; - - // Returns an iterator over all keys, starting with the oldest. - keys() : Iterator; - - // Returns an iterator over all values, starting with the oldest. - values() : Iterator; - - // Returns an iterator over all entries, starting with the oldest. - entries() : Iterator<[K,V]>; - - // Returns an iterator over all entries, starting with the oldest. - [Symbol.iterator]() : Iterator<[K,V]>; - - // Call `fun` for each entry, starting with the oldest entry. - forEach(fun :(value :V, key :K, m :LRUMap)=>void, thisArg? :any) : void; - - // Returns an object suitable for JSON encoding - toJSON() : Array<{key :K, value :V}>; - - // Returns a human-readable text representation - toString() : string; -} - -// An entry holds the key and value, and pointers to any older and newer entries. -interface Entry { - key :K; - value :V; -} diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/index.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/index.js index 93a7dc94649..edc7007a4e3 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/index.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/index.js @@ -11,16 +11,18 @@ import { newDropdownsController, newTabsController, } from './components/index'; -import { isMobile, setIsTranslating, getCurrentLang, leackChecker } from './helpers/index'; +import { scrollToActiveExplorerNode, setIsTranslating, getCurrentLang } from './helpers/helpers'; +import { leackChecker } from './helpers/leak-checker'; import { addLangToLinks, newBreadcrumbsController, newLanguageSwitcherController, newNavController, newPromoCodesController, - newSearchExplorerController, newToCController, newPaginatorController, + newSearchExplorerRoot, + newSearchExplorerNode, } from './navigation/index'; import { newNavStore } from './navigation/nav-store'; // AlpineJS controllers and helpers. @@ -42,6 +44,7 @@ const searchConfig = getSearchConfig(params); return false; }); } + __stopWatch('index.js.start'); // Register AlpineJS plugins. @@ -77,7 +80,8 @@ const searchConfig = getSearchConfig(params); Alpine.data('lncLanguageSwitcher', newLanguageSwitcherController(params.weglot_api_key)); Alpine.data('lncSearchFilters', () => newSearchFiltersController(searchConfig)); Alpine.data('lncSearchInput', newSearchInputController); - Alpine.data('lncSearchExplorer', () => newSearchExplorerController(searchConfig)); + Alpine.data('lncSearchExplorerRoot', (pageInfo) => newSearchExplorerRoot(pageInfo)); + Alpine.data('lncSearchExplorerNode', (node = {}) => newSearchExplorerNode(searchConfig, node)); Alpine.data('lncToc', newToCController); Alpine.data('lncBreadcrumbs', () => newBreadcrumbsController(searchConfig)); Alpine.data('lncDropdowns', newDropdownsController); @@ -143,9 +147,6 @@ const searchConfig = getSearchConfig(params); }; document.addEventListener('turbo:load', function (event) { - // Hide JS-powered blocks on browsers with JavaScript disabled. - document.body.classList.remove('no-js'); - // Update any static links to the current language. let lang = getCurrentLang(); if (lang && lang !== 'en') { @@ -188,4 +189,40 @@ const searchConfig = getSearchConfig(params); pushGTag('docs_navigate'); }); + + // Preserve scroll position when navigating with Turbo on all elements with the data-preserve-scroll attribute. + if (!window.scrollPositions) { + window.scrollPositions = {}; + } + + function preserveScroll() { + document.querySelectorAll('[ data-preserve-scroll').forEach((element) => { + scrollPositions[element.id] = element.scrollTop; + }); + } + + function restoreScroll(event) { + const isFinalRender = event.type === 'turbo:render' && !document.documentElement.hasAttribute('data-turbo-preview'); + document.querySelectorAll('[ data-preserve-scroll').forEach((element) => { + let id = element.id; + if (isFinalRender && id === 'explorer' && !window.explorerNodeClicked) { + scrollToActiveExplorerNode(); + } else { + element.scrollTop = scrollPositions[id]; + } + }); + + if (isFinalRender) { + window.explorerNodeClicked = false; + } + + if (!event.detail || !event.detail.newBody) return; + event.detail.newBody.querySelectorAll('[ data-preserve-scroll').forEach((element) => { + element.scrollTop = scrollPositions[element.id]; + }); + } + + window.addEventListener('turbo:before-cache', preserveScroll); + window.addEventListener('turbo:before-render', restoreScroll); + window.addEventListener('turbo:render', restoreScroll); })(); diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/create-href.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/create-href.js index ad194893126..0159689bc5b 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/create-href.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/create-href.js @@ -1,6 +1,6 @@ 'use strict'; -import { sanitizeHTML } from '../helpers'; +import { sanitizeHTML } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[router]') : function () {}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/explorer.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/explorer.js index 668181e46c4..d9be3c45ddb 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/explorer.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/explorer.js @@ -1,299 +1,109 @@ 'use strict'; -import { getOffsetTop, isElementInViewport, isMobile } from '../helpers/index'; +import { isMobile } from '../helpers/helpers'; import { newRequestCallback, newRequestCallbackFactoryTarget, SearchGroupIdentifier, RequestCallBackStatus, } from '../search/request'; -import { isTopResultsPage } from '../search'; -import { newCreateHref } from './create-href'; -var debug = 0 ? console.log.bind(console, '[explorer]') : function () {}; +var debug = 0 ? console.log.bind(console, '[explorer-node]') : function () {}; -const sectionDelimm = ' > '; -const startLevelRegularPages = 2; - -// The main search result have some sections that does not fit into the left side explorer. -const shouldSkipSection = function (lvl0) { - return lvl0.endsWith('-branches') || lvl0 === 'taxonomies' || lvl0 === 'bundles'; -}; - -// Reports whether branch1 is the same or below branch2. -const isSameOrBelowBranch = function (branch1, branch2) { - return branch1 === branch2 || branch1.startsWith(branch2 + ' >'); -}; - -const nodeKeyToID = function (key) { - return 'node-' + key.replace(/ > /g, '-'); -}; - -export function newSearchExplorerController(searchConfig) { - if (!searchConfig) { - throw 'newSearchExplorerController: must provide searchConfig'; +export function newSearchExplorerRoot(pageInfo) { + let sectionKey = ''; + if (pageInfo.sectionsPath) { + // Create a section key that's on the same format as what we have in Algolia. + const parts = pageInfo.sectionsPath.split('/'); + const delim = ' > '; + sectionKey = parts.join(delim); } + pageInfo.sectionKey = sectionKey; - const router = newCreateHref(searchConfig); - - // This comparer is used to sort both sections - // and leaf nodes in the explorer tree. - // This functions orders by 1) Ordinal 2) PubDate or 3) Title. - const itemsComparer = function (a, b) { - if (a.ordinal === b.ordinal) { - if (a.firstPublishedTime === b.firstPublishedTime) { - let atitle = a.linkTitle || a.title; - let btitle = b.linkTitle || b.title; - return atitle < btitle ? -1 : 1; - } - // Sort newest first. - return a.firstPublishedTime > b.firstPublishedTime ? -1 : 1; - } - return a.ordinal - b.ordinal; - }; - - const createExplorerNodeRequest = function (query, key) { - let maxLeafNodes = searchConfig.explorer_max_leafnodes || 100; - let sectionFilter = `section:${key}`; - let facetFilters = query.toFacetFilters(); - let filters = ''; - - return { - indexName: searchConfig.indexName(searchConfig.sections_merged.index), - filters: filters, - facetFilters: facetFilters.concat(facetFilters, sectionFilter), - distinct: 0, - params: `query=${encodeURIComponent(query.lndq)}&hitsPerPage=${maxLeafNodes}`, - }; - }; - - const scrollToActive = function (self, nodeInfo) { - let offset = 30; // Add some space above. - let threshold = 200; // Avoid scrolling short distances if the item is in viewport. - - var isDone = false; - var container = self.$el.parentNode; - - const scroll = function (what, to) { - if (to >= 0 && container.scrollTop !== to) { - container.scrollTop = to; - } - - debug('scroll', what, to); - - isDone = true; - }; + return { + // Explorer state. + explorer: { + // The current page. + pageInfo: pageInfo, - if (!nodeInfo.branchKey) { - scroll('home', 0); - return; - } + // Sorted by href. + facets: [], - let li = document.getElementById(nodeKeyToID(nodeInfo.branchKey)); - if (!li) { - scroll('li not found: ' + nodeInfo.branchKey, -1); - return; - } + // Callbacks called when facets are loaded. + onFacetsLoaded: [], - // Select the active branch or leaf node. - let selector = nodeInfo.isBranch ? 'div > div a' : '.is-active-page'; + // Callbacks called on render after a navigation with Turbo. + onTurboRender: [], - setTimeout(function () { - // We have no simple way of knowing when the left menu is finished rendered, - // but we want to at least try to scroll down to the active node, - // and as soon as possible. - let retries = [0, 200, 500, 1000, 2000]; - for (let i = 0; i < retries.length; i++) { - let sleep = retries[i]; + searchActivated: false, + onSearchActivated: [], + }, - setTimeout(function () { - if (isDone) { - return; + init: async function () { + this.$store.search.withExplorerData((data) => { + let facets = data.blank.sectionFacets; + // Apply some metadata used for Algolia events and similar. + let position = 0; + facets.forEach((n) => { + // These are also indexed on its own. + if (n.href.startsWith('/docs/guides/') || n.href.startsWith('/docs/products/')) { + position++; + n.hit = { + objectID: n.href, + __position: position, + }; } - - let active = li.querySelector(selector); - - if (active && active.getBoundingClientRect().top) { - let container = self.$el.parentNode; - let scrollTop = getOffsetTop(container, active); - if (scrollTop) { - // If we're near the top, we might as well go all the way. - if (scrollTop < 100) { - scroll('near the top', 0); - return; - } - - scrollTop = scrollTop - offset; - - let oldScrollTop = self.scrollTop; - - // If the active element is in viewport and - // within the threshold distance, we just - // keep the current scrollTop. - if (oldScrollTop && threshold && isElementInViewport(active)) { - let change = oldScrollTop < scrollTop ? scrollTop - oldScrollTop : oldScrollTop - scrollTop; - - if (change < threshold) { - // Avoid minor scroll changes. - scroll('less than threshold', -1); - return; - } - } - - scroll('scroll', scrollTop); - } - } else if (i + 1 === retries.length) { - scroll('timed out', 0); + }); + this.explorer.facets = facets; + this.explorer.onFacetsLoaded.forEach((cb) => cb()); + this.$watch('$store.search.results.main.sectionFacets', (value) => { + // This will be called once a search starts. + // Notify the child nodes. + if (!this.explorer.searchActivated) { + this.explorer.onSearchActivated.forEach((cb) => cb()); + this.explorer.searchActivated = true; } - }, sleep); - } - }, 0); - }; - - const getActiveNodeInfo = function () { - let info = window.lnPageInfo; - if (!info) { - return null; - } - - let isBranch = info.kind !== 'page'; - - if (info.kind === 'home') { - return { - isBranch: isBranch, - branchKey: '', - }; - } else if (info.type === 'sections' || info.type === 'api') { - // Dynamic section, fetch the node key from the location. - return { - isBranch: isBranch, - branchKey: router.sectionsFromPath().join(' > '), - }; - } else if (info.sectionsEntries) { - return { - isBranch: isBranch, - branchKey: info.sectionsEntries.join(' > '), - }; - } else { - return { - isBranch: false, - branchKey: '', - }; - } - }; - - var templates = {}; - - var data = { - // Set after the initial data load. - loaded: false, - - // All nodes in the tree. - nodes: {}, - - // Contains all the root nodes (level === 1). - rootNode: { - sections: [], + updateFacetState(this.explorer.facets, value); + }); + }, createExplorerNodeRequest); }, - // The search results. - searchState: null, - - // Maps Algolia section to the Hugo section. - sectionMapping: {}, - }; - - data.walk = function (rootNode, shouldSkip) { - const walkRecursive = function (n) { - for (let nn of n.sections) { - if (shouldSkip(nn)) { - continue; - } - walkRecursive(nn); - } - }; + getFacetsFor: function (node) { + return findChildren(node, this.explorer.facets); + }, - for (let n of rootNode.sections) { - if (shouldSkip(n)) { - continue; + findNode: function (href) { + let index = this.explorer.facets.findIndex((n) => n.href === href); + if (index === -1) { + return null; } - walkRecursive(n); - } - }; - - // May be set on initial load to expand to a given node. - var onLoadNodeInfo = null; + return this.explorer.facets[index]; + }, - return { - data: data, - isMenuNavigation: false, - scrollTop: 0, - open: true, - noTransitions: true, + // Open/close state and transitions. - init: function () { - templates = { - templateLoopRoot: this.$refs['templateLoopRoot'], - templateLoopNested: this.$refs['templateLoopNested'], - templateTree: this.$refs['templateTree'], - }; - this.target = this.$refs['explorer']; - - // Open the explorer menu on load when not on mobile. - let shouldOpen = !isMobile(); - let isSearchPage = isTopResultsPage(); - if (isSearchPage) { - // Show it after we have the search results. - this.open = false; + onTurboRender: function () { + if (document.documentElement.hasAttribute('data-turbo-preview')) { + // Turbolinks is displaying a preview + return; } - - loadInitialData = (sectionKeys = []) => { - this.$store.search.withExplorerData( - (data) => { - this.data.initial = data; - this.load(); - - this.$watch('$store.search.results.main.result', (value) => { - debug('main result'); - this.load(); - this.open = true; - }); - }, - createExplorerNodeRequest, - sectionKeys - ); - }; - - sectionKeys = []; - - if (shouldOpen) { - onLoadNodeInfo = getActiveNodeInfo(); - if (onLoadNodeInfo.branchKey) { - let sections = onLoadNodeInfo.branchKey.split(' > '); - - let currentKey = []; - for (let i = 0; i < sections.length; i++) { - currentKey.push(sections[i]); - if (i < startLevelRegularPages - 1) { - continue; - } - let section = currentKey.join(' > '); - sectionKeys.push(section); - } + const pageInfo = this.explorer.pageInfo; + debug('root.onTurboRender', pageInfo); + if (pageInfo.isHome) { + closeLevel(1, this.explorer.facets); + } else { + let hrefSection = pageInfo.hrefSection; + if (pageInfo.section === 'api') { + // The API section is currently a little special. + hrefSection = pageInfo.href; } - loadInitialData(sectionKeys); - } - - this.$watch('$store.nav.open.explorer', (value) => { - this.noTransitions = false; - if (value && !this.data.loaded) { - loadInitialData(); + let currentNode = this.findNode(hrefSection); + if (currentNode) { + currentNode.open = currentNode.count > 0; + openNodeAndCloseTheOthers(currentNode, this.explorer.facets); } - }); - }, - - isOpen: function () { - return this.$store.nav.open.explorer && this.data.loaded; + } + this.explorer.onTurboRender.forEach((cb) => cb()); }, closeIfMobile: function () { @@ -302,578 +112,318 @@ export function newSearchExplorerController(searchConfig) { } }, - load: function () { - if (!this.getResult()) { - return; - } - - if (this.data.loaded) { - this.filterNodes(); - return; - } - - let shouldScroll = onLoadNodeInfo; - - this.buildNodes(); - this.render(); - this.data.loaded = true; - - if (shouldScroll) { - var onLoadNodeInfoCopy = onLoadNodeInfo; - this.$nextTick(() => { - scrollToActive(this, onLoadNodeInfoCopy); - }); - } - - // Only needed on inital page load, - onLoadNodeInfo = null; - __stopWatch('explorer.loaded'); + isOpen: function () { + return this.$store.nav.open.explorer; }, - onTurbolinksBeforeVisit: function (data) { - this.scrollTop = this.$el.parentNode.scrollTop; + // Tailwind transition classes for opening of the explorer. + // Note that this will not trigger on initial page load, which is a good thing. + transitions: function () { + return { + 'x-transition:enter': 'transition-transform transition-opacity ease-out duration-500 sm:duration-700', + 'x-transition:enter-start': 'opacity-0 transform mobile:-translate-x-8 sm:-translate-y-8', + 'x-transition:enter-end': 'opacity-100 transform mobile:translate-x-0 sm:translate-y-0', + }; }, + }; +} - // Turbolinks replaces the body that we may have changed, so restore the - // explorer state when navigating to a new page. - onTurbolinksBeforeRender: function (data) { - // Always close the left side tree menu when navigating away on mobile. - this.closeIfMobile(); +export function newSearchExplorerNode(searchConfig, node = {}) { + let templates = {}; + let ctrl = { + node: node, + state: { + childNodes: [], + pages: [], + pagesLoaded: false, + hydrated: node.open, + activeHref: window.location.pathname + window.location.hash, }, - - onTurbolinksRender: function (data) { - if (document.documentElement.hasAttribute('data-turbolinks-preview')) { - return; - } - - let isMenuNavigation = this.isMenuNavigation; - this.isMenuNavigation = false; - - if (!isMenuNavigation) { - // This is a navigation by link. - // The node was opened in the before-render event. - let activeNodeInfo = getActiveNodeInfo(); - this.setActiveBranch(activeNodeInfo); - scrollToActive(this, activeNodeInfo); - } else { - this.$el.parentNode.scrollTop = this.scrollTop; + toggleOpen: function () { + debug('toggleOpen', this.node.href, this.node.open, this.state.hydrated); + this.hydrateIfNeeded(); + this.node.open = !this.node.open && this.node.count > 0; + if (this.node.open) { + openNodeAndCloseTheOthers(this.node, this.explorer.facets); } + this.activatePageSearch(); }, - setActiveBranch: function (nodeInfo) { - let nodeKey = nodeInfo.branchKey; - for (let k in this.data.nodes) { - let n = this.data.nodes[k]; - - if (n.disabled) { - continue; - } - - if (isSameOrBelowBranch(nodeKey, n.key)) { - if (!n.open) { - n.toggleOpen(); - } else if (!nodeInfo.isBranch) { - n.pages.forEach((page) => { - page.active = page.href === window.location.pathname; - }); - } - } else if (n.open) { - n.open = false; - } - } + isActive: function (href) { + return href === this.state.activeHref; }, - render: function () { - let fragment = new DocumentFragment(); - this.renderNodesRecursive(fragment, 1); - this.target.innerHTML = ''; - this.target.appendChild(fragment); + showStaticLeafNodes: function () { + // Keep it static until it isn't. + return !this.state.pagesLoaded; }, - renderNodesRecursive: function (ulEl, level) { - let templ = - level === 1 - ? document.importNode(templates.templateLoopRoot.content.querySelector('template'), true) - : document.importNode(templates.templateLoopNested.content.querySelector('template'), true); + onClickStaticLeafNode: function (href, objectID) { + window.explorerNodeClicked = true; + this.state.activeHref = href; + let hit = { + objectID: objectID, + }; + this.$store.nav.analytics.handler.clickHit(hit, 'DOCS: Explorer'); + }, - let li = document.importNode(templates.templateTree.content.querySelector('.explorer__node'), true); + onClick: function (e, p = null) { + // To avoid unnecessary scrolling on navigation. + window.explorerNodeClicked = true; - let mainLoop = templ.content.querySelector('template'); - mainLoop.content.appendChild(li); - ulEl.appendChild(templ); + let hit = null; + if (p) { + hit = p.hit; + } else { + hit = this.node.hit; + } - ulEl = li.querySelector('.node-tree'); + if (hit) { + this.state.activeHref = hit.href; + // Send click events to Algolia insights. + this.$store.nav.analytics.handler.clickHit(hit, 'DOCS: Explorer'); + } - if (level >= 5) { - // configure + // We have no page/section listing for the QA section, so + // we make it navigate to the search result for the second level. + if (this.node.key === 'community') { + e.preventDefault(); + this.toggleOpen(); return; } - level++; - - this.renderNodesRecursive(ulEl, level); - }, - - createNode: function (opts) { - let title = opts.title ? opts.title : opts.name; - let ordinal = opts.ordinal ? opts.ordinal : 0; - - let n = { - section: opts.section, - parent: opts.parent, - level: opts.level, // Starting at 1 - ordinal: ordinal, - firstPublishedTime: opts.firstPublishedTime ? opts.firstPublishedTime : 0, - name: opts.name, - title: title, - key: opts.key, - href: opts.href, - hit: opts.hit, - active: opts.active, - count: opts.count, - icon: opts.level === 1 ? opts.section.config.explorer_icon : '', - sections: [], - pages: [], // Leaf nodes - open: false, - disabled: false, - kind: opts.kind ? opts.kind : 'section', - isGhostSection: opts.isGhostSection, - sectionLvl0: opts.sectionLvl0, - }; - - let self = this; - - n.id = function () { - return nodeKeyToID(this.key); - }; - - n.children = function () { - if (this.pages.length === 0) { - // The sections slice is sorted and ready to use. - return this.sections; - } - - const nodes = this.sections.concat(this.pages); - - nodes.sort(itemsComparer); - - return nodes; - }; - - n.isDisabled = function () { - return false; - }; - - n.isLeaf = function () { - return this.kind === 'page' || (this.level > 1 && (this.count === 0 || this.isGhostSection)); - }; - - n.isPage = function () { - return this.kind === 'page'; - }; - - n.setAsActive = function () { - if (!this.parent) { - n.active = true; - return; - } - - this.parent.pages.forEach((p) => { - p.active = p.key === n.key; + if (this.node.key === 'community > question') { + e.preventDefault(); + this.$store.nav.openSearchPanelWithQuery((query) => { + query.addFilter('docType', this.node.key); }); - }; - - n.onClick = function (e) { - if (e.metaKey) { - return; - } - if (!this.isLeaf() && !this.isDisabled() && !this.open) { - this.toggleOpen(); - } - - if (this.isLeaf()) { - this.setAsActive(); - } - - if (this.isGhostSection) { - e.preventDefault(); - - self.$store.nav.openSearchPanelWithQuery((query) => { - query.addFilter('docType', this.sectionLvl0); - }); - return; - } - - if (this.href) { - // Send click events to Algolia insights. - if (this.hit) { - self.$store.nav.analytics.handler.clickHit(this.hit, 'DOCS: Explorer'); - } - - let href = this.href; - if (this.isLeaf() && href.startsWith('http')) { - return; - } - self.isMenuNavigation = true; - e.preventDefault(); - - if (this.isLeaf()) { - Turbo.visit(href); - } else { - // Add a slight delay here to allow the menu - // to expand before we navigate. - // This looks more natural. - setTimeout(function () { - Turbo.visit(href); - }, 200); - } - } - }; - - // Detault implementations. - n.showBorder1 = function () { - return false; - }; - - n.showBorder2 = function () { - return false; - }; - - n.loadPages = function () {}; - - return n; - }, - - addPagesToNode: function (n, hits) { - let pages = []; - for (let item of hits) { - let href = item.href; - if (item.hierarchy && item.hierarchy.length) { - // This is the reference-section. - // All pages in a section shares the same href (the section), - // and the best match is selected while searching using Algolia's distinct keyword. - // This is the explorer, and we need to link to the detail page. - let last = item.hierarchy[item.hierarchy.length - 1]; - href = last.href; - } - let active = href === window.location.pathname; - - pages.push( - this.createNode({ - parent: n, - section: n.section, - key: href, - href: href, - hit: item, - active: active, - ordinal: item.ordinal, - firstPublishedTime: item.firstPublishedTime, - name: item.linkTitle, - level: n.level + 1, - kind: 'page', - count: 0, - }) - ); + return; } - - n.pages = pages; }, - // loadPages loads any leaf pages into node if not already loaded. - loadPages: function (node) { - let nodes = []; + init: function init() { + templates = { + templateNode: this.$refs['templateNode'], + templateNodePages: this.$refs['templateNodePages'], + }; + + this.explorer.onFacetsLoaded.push(() => { + // Replace the temporary static nodes with a reactive one. + let n = this.findNode(this.node.href); - this.data.walk(this.data.rootNode, function (nn) { - if (nn.open) { - nodes.push(nn); + if (n) { + n.open = this.node.open && n.count; + this.node = n; } - - // If it's not open we skip any descendants. - return !nn.open; }); - if (!node.open) { - // Preload - nodes.push(node); - } - - if (nodes.length === 0) { - return; - } - - var self = this; - - nodes.forEach((node) => { - if (node.level < startLevelRegularPages) { - return; + this.explorer.onTurboRender.push(() => { + // Reattach the proxy on navigation. + let n = this.findNode(this.node.href); + if (n) { + this.node = n; } + }); - let factory = { - status: function () { - return node.open ? RequestCallBackStatus.On : RequestCallBackStatus.Off; - }, - create: (query) => { - return newRequestCallback( - createExplorerNodeRequest(query, node.key), - (result) => { - this.addPagesToNode(node, result.hits); - }, - { - pronto: true, - query: query, - fileCacheID: node.key, - } - ); - }, - }; - - this.$store.search.addSearches(newRequestCallbackFactoryTarget(factory, SearchGroupIdentifier.Main)); + this.explorer.onSearchActivated.push(() => { + this.activatePageSearch(); }); }, - buildNodes: function () { - var result = this.getResult(); - var openSections = {}; - if (!this.data.loaded && this.data.initial) { - openSections = this.data.initial.sections; - } - - var self = this; - - this.data.parseKey = function (key) { - let parts = key.split(sectionDelimm); - let level = parts.length; - let name = parts[parts.length - 1]; - - let href = router.hrefSection(key); - - let parentKey = false; - if (parts.length > 1) { - parentKey = parts.slice(0, parts.length - 1).join(sectionDelimm); - } - - return { - parts: parts, - level: level, - key: key, - name: name, - href: href, - parentKey: parentKey, - }; - }; - - this.data.add = function (sectionResult) { - let kp = this.parseKey(sectionResult.key); - - let n = this.nodes[kp.key]; - let count = sectionResult.count; - let meta = sectionResult.meta; - - let title; - let ordinal = 0; - if (meta) { - title = meta.linkTitle; - ordinal = meta.ordinal; - } - - if (!n) { - let section; - if (kp.level === 1) { - section = { config: searchConfig.sections[kp.key.toLowerCase()] }; - title = section.config.title; - } - - let hit; - - if (sectionResult.hasObjectID) { - // Create a pseudo hit for event tracking. - hit = { - objectID: kp.href, - __position: sectionResult.position, - }; - } - - n = self.createNode({ - key: kp.key, - section: section, - href: kp.href, - name: kp.name, - title: title, - ordinal: ordinal, - level: kp.level, - count: count, - isGhostSection: sectionResult.isGhostSection, - sectionLvl0: sectionResult.sectionLvl0, - hit: hit, - }); - - if (!n.disabled && onLoadNodeInfo && isSameOrBelowBranch(onLoadNodeInfo.branchKey, n.key)) { - n.open = true; - } - - n.isDisabled = function () { - return this.disabled || this.count === 0; - }; + getNodesSection: function () { + return this.getFacetsFor(this.node); + }, - n.toggleOpen = function () { - if (!this.open) { - // Close open nodes on the same or lower level. - self.data.walk(this.parent, function (nn) { - if (nn.open && nn !== n) { - nn.open = false; + activatePageSearch: function () { + let self = this; + let factory = { + status: function () { + return self.node.open ? RequestCallBackStatus.On : RequestCallBackStatus.Off; + }, + create: (query) => { + return newRequestCallback( + createExplorerNodeRequest(searchConfig, query, self.node.key), + (result) => { + let loc = window.location; + let pages = result.hits.map((hit) => { + if (hit.hierarchy && hit.hierarchy.length) { + // This is the reference-section. + // All pages in a section shares the same href (the section), + // and the best match is selected while searching using Algolia's distinct keyword. + // This is the explorer, and we need to link to the detail page. + let last = item.hierarchy[item.hierarchy.length - 1]; + hit.href = last.href; } - return false; - }); - } - this.open = !this.open; - if (this.open) { - this.loadPages(); - } - }; - n.loadPages = function () { - self.loadPages(this); - }; - - n.hasOpenDescendants = function () { - for (let s of n.sections) { - if (s.open) { - return true; - } - } + // Split hit.ref into pathname and hash. + // This is needed to compare with the current location. + // Note that this is currently only relevant for the API pages. + let pathname = hit.href; + let hash = ''; + let hashIndex = hit.href.indexOf('#'); + if (hashIndex !== -1) { + pathname = hit.href.substring(0, hashIndex); + hash = hit.href.substring(hashIndex); + } - for (let s of n.sections) { - if (s.hasOpenDescendants()) { - return true; - } + // This page active is both the pathname and the hash is the same. + let active = pathname == loc.pathname && hash == loc.hash; + + return { + title: hit.linkTitle ? hit.linkTitle : hit.title, + linkTitle: hit.linkTitle ? hit.linkTitle : hit.title, + href: hit.href, + active: active, + // Store away the original hit for Algolia events. + hit: hit, + }; + }); + pages.sort(itemsComparer); + self.state.pages = pages; + self.state.pagesLoaded = true; + }, + { + pronto: true, + query: query, + fileCacheID: self.node.key, } - return false; - }; - - n.showBorder1 = function () { - return this.level > 1 && this.open && !this.hasOpenDescendants(); - }; - - n.showBorder2 = function () { - return this.level > 1 && this.open && this.hasOpenDescendants(); - }; - - let childResults = openSections[kp.key]; - if (childResults) { - self.addPagesToNode(n, childResults.hits); - } - - this.nodes[kp.key] = n; - } else { - // Update the existing node. - n.count = count; - n.sections.length = 0; - } - - if (kp.parentKey) { - if (kp.key == kp.parentKey) { - throw 'invalid state: ' + kp.key; - } - let parentNode = this.nodes[kp.parentKey]; - - if (parentNode) { - parentNode.sections.push(n); - n.parent = parentNode; - } - } + ); + }, }; + this.$store.search.addSearches(newRequestCallbackFactoryTarget(factory, SearchGroupIdentifier.Main)); + }, - // Build the nodes in the order configured. - let resultSections = result.sections(); - searchConfig.sectionsSorted.forEach((sectionCfg) => { - resultSections.forEach((section) => { - let key = section.key.toLowerCase(); - if (key.startsWith(sectionCfg.name) && !shouldSkipSection(section.sectionLvl0)) { - this.data.add(section); - } - }); - }); - - for (let k in this.data.nodes) { - let n = this.data.nodes[k]; - - n.sections.sort(itemsComparer); + hydrateIfNeeded: function () { + if (!this.state.hydrated) { + this.hydrate(); } + }, - debug('nodes', this.data.nodes); + hydrate: function () { + let self = this; + this.state.hydrated = true; - for (let k in this.data.nodes) { - let n = this.data.nodes[k]; + const nodeElement = this.$refs['node-tree']; - if (n.level !== 1) { - continue; - } - n.parent = this.data.rootNode; - this.data.rootNode.sections.push(n); - } - }, + let nodeTemplate = document.importNode(templates.templateNode.content.querySelector('template'), true); + let nodePagesTemplate = document.importNode(templates.templateNodePages.content.querySelector('template'), true); - getResult: function () { - if (this.data.loaded) { - return this.$store.search.results.main.result; - } - return this.data.initial.blank.result; + // First append the leaf nodes. + nodeElement.appendChild(nodePagesTemplate); + // Then append the branch nodes. + nodeElement.appendChild(nodeTemplate); }, + }; - // Update hidden state and facet counts based on a updated search result. - filterNodes: function () { - debug('filterNodes'); - let result = this.getResult(); + return ctrl; +} - let seen = new Set(); - let resultSections = result.sections(); +const findChildren = function (node, nodes) { + let children = []; - for (let resultSection of resultSections) { - if (shouldSkipSection(resultSection.sectionLvl0)) { - continue; - } - let kp = this.data.parseKey(resultSection.key); - let n = this.data.nodes[kp.key]; - if (!n) { - console.warn(`node with key ${kp.key} not set`); - continue; - } + // Nodes are sorted by href, so we can use a binary search. + let index = nodes.findIndex((n) => n.href === node.href); + if (index === -1) { + return children; + } - n.count = resultSection.count; - seen.add(kp.key); - } + // Now find all children one level down. + let level = nodes[index].level; + let child = nodes[index + 1]; + while (child && child.href.startsWith(node.href)) { + if (child.level === level + 1) { + children.push(child); + } + index++; + child = nodes[index + 1]; + } + return children; +}; - for (let k in this.data.nodes) { - let n = this.data.nodes[k]; +const openNodeAndCloseTheOthers = function (node, nodes) { + debug('openNodeAndCloseTheOthers', node.href); + for (let i = 0; i < nodes.length; i++) { + let n = nodes[i]; + n.open = node.href.startsWith(n.href); + } +}; - if (!seen.has(n.key)) { - if (n.level === 1) { - n.count = 0; - n.open = false; - } else { - // Hide it in the DOM. - n.hidden = true; - } +const closeLevel = function (level, nodes) { + debug('closeLevel', level); + for (let i = 0; i < nodes.length; i++) { + let n = nodes[i]; + if (n.open && n.level === level) { + n.open = false; + } + } +}; - continue; - } else { - n.hidden = false; - } +const updateFacetState = function (to, from) { + // from is a subset of to. + // If a node in from is not found in to, disable it. + // For matches, update count. + // For non-matches, set count to 0. + let fromIndex = 0; + for (let toIndex = 0; toIndex < to.length; toIndex++) { + let toNode = to[toIndex]; + let fromNode = null; + if (toIndex >= from.length) { + let idx = from.findIndex((n) => n.href === toNode.href); + if (idx !== -1) { + fromNode = from[idx]; } - }, - - // Tailwind transition classes for opening of the explorer. - transitions: function () { - if (this.noTransitions) { - // No transition on initial page load. It looks jittery and gives a penalty in Lightroom. - return {}; + if (!fromNode) { + toNode.count = 0; + toNode.open = false; + continue; } + } else { + fromNode = from[fromIndex]; + } - return { - 'x-transition:enter': 'transition-transform transition-opacity ease-out duration-500 sm:duration-700', - 'x-transition:enter-start': 'opacity-0 transform mobile:-translate-x-8 sm:-translate-y-8', - 'x-transition:enter-end': 'opacity-100 transform mobile:translate-x-0 sm:translate-y-0', - }; - }, + if (toNode.href === fromNode.href) { + toNode.count = fromNode.count; + fromIndex++; + continue; + } + + toNode.count = 0; + toNode.open = false; + } +}; + +const createExplorerNodeRequest = function (searchConfig, query, key) { + let maxLeafNodes = 200; // This needs to big enough to cover the biggest section. + let sectionFilter = `section:${key}`; + let facetFilters = query.toFacetFilters(); + let filters = ''; + + return { + indexName: searchConfig.indexName(searchConfig.sections_merged.index), + filters: filters, + facetFilters: facetFilters.concat(facetFilters, sectionFilter), + distinct: 0, + params: `query=${encodeURIComponent(query.lndq)}&hitsPerPage=${maxLeafNodes}`, }; -} +}; + +// This comparer is used to sort both sections +// and leaf nodes in the explorer tree. +// This functions orders by 1) Ordinal 2) PubDate or 3) Title. +// Note: The sort was made simpler to get it in line with the static render, +// but the old code is commented out if we want to revert it later. +const itemsComparer = function (a, b) { + return a.linkTitle.localeCompare(b.linkTitle); + /* + if (a.ordinal === b.ordinal) { + if (a.firstPublishedTime === b.firstPublishedTime) { + let atitle = a.linkTitle || a.title; + let btitle = b.linkTitle || b.title; + return atitle < btitle ? -1 : 1; + } + // Sort newest first. + return a.firstPublishedTime > b.firstPublishedTime ? -1 : 1; + } + return a.ordinal - b.ordinal; + */ +}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/language-switcher.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/language-switcher.js index c801b8c55cc..8fe237aef09 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/language-switcher.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/language-switcher.js @@ -1,6 +1,6 @@ 'use strict'; -import { getCurrentLangFromLocation, setIsTranslating } from '../helpers'; +import { getCurrentLangFromLocation } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[language-switcher]') : function () {}; @@ -17,10 +17,10 @@ export function newLanguageSwitcherController(weglot_api_key) { Weglot.switchTo(lang); }); initWeglot(weglot_api_key); - return; + } else { + Weglot.switchTo(lang); } }, 600); - Weglot.switchTo(lang); }; // This needs to be a function to get the $persist binded. diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-analytics.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-analytics.js index 7b151592581..903f1e1abe8 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-analytics.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-analytics.js @@ -1,4 +1,5 @@ -import { smartQueue, getCookie, setCookie, supportsCookies, createUUID } from '../helpers'; +import { getCookie, setCookie, supportsCookies, createUUID } from '../helpers/helpers'; +import { smartQueue } from '../helpers/smartqueue'; const unspecificedUserToken = 'unspecified'; const userTokenCookieName = 'linode_anonymous_usertoken'; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-store.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-store.js index 0d9c6c2a378..5799b7e17a9 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-store.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav-store.js @@ -1,4 +1,4 @@ -import { isMobile } from '../helpers'; +import { isMobile } from '../helpers/helpers'; import { getScrollPosNavbar } from './nav'; import { AnalyticsEventsCollector } from './nav-analytics'; import { initConsentManager } from '../components/index'; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav.js index 0ad8c795e77..a20b1c6c05f 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/nav.js @@ -1,6 +1,6 @@ 'use strict'; -import { isMobile, toggleBooleanClass } from '../helpers/index'; +import { isMobile, toggleBooleanClass } from '../helpers/helpers'; import { isTopResultsPage } from '../search'; import { newQuery, QueryHandler } from '../search/query'; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/paginator.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/paginator.js index 3a59b5e4346..cca47907d22 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/paginator.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/paginator.js @@ -1,6 +1,6 @@ 'use strict'; -import { getIntParamFromLocation, updatePaginationParamInLocation } from '../helpers/index'; +import { getIntParamFromLocation, updatePaginationParamInLocation } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[paginator]') : function () {}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/svg-viewer.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/svg-viewer.js index f586e4aa95f..14b0493a937 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/svg-viewer.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/svg-viewer.js @@ -1,6 +1,6 @@ 'use strict'; -import { isMobile } from '../helpers'; +import { isMobile } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[svg-viewer]') : function () {}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/toc.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/toc.js index 4f82d128c80..5fc32a918bc 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/toc.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/navigation/toc.js @@ -1,6 +1,6 @@ 'use strict'; -import { isDesktop, isMobile } from '../helpers/index'; +import { isDesktop, isMobile } from '../helpers/helpers'; var debug = 0 ? console.log.bind(console, '[toc]') : function () {}; var devMode = false; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/filters.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/filters.js index 1298d874d3b..6638ecf3b17 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/filters.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/filters.js @@ -1,6 +1,6 @@ 'use strict'; -import { toggleBooleanClass } from '../helpers'; +import { toggleBooleanClass } from '../helpers/helpers'; import { QueryHandler } from './query'; var debug = 0 ? console.log.bind(console, '[filters]') : function () {}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/query.ts b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/query.ts index bb0ea35bc33..fe8ec907f81 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/query.ts +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/query.ts @@ -1,5 +1,5 @@ import { isTopResultsPage } from '.'; -import { normalizeSpace } from '../helpers/index'; +import { normalizeSpace } from '../helpers/helpers'; export interface Query { // Holds the current page number. diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/search-store.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/search-store.js index 8509dea78c7..0257bf9176c 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/search-store.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/search/search-store.js @@ -1,5 +1,6 @@ import { newQuery, QueryHandler } from './query'; -import { getCurrentLang, LRUMap, toDateString } from '../helpers'; +import { getCurrentLang, toDateString } from '../helpers/helpers'; +import { LRUMap } from '../helpers/lru'; import { newCreateHref, addLangToHref } from '../navigation/index'; import { newRequestCallback, @@ -12,17 +13,63 @@ import { const debug = 0 ? console.log.bind(console, '[search-store]') : function () {}; const debugFetch = 0 ? console.log.bind(console, '[search-fetch]') : function () {}; -export const searchGroupIdentifiers = { - MAIN: 1, - AD_HOC: 2, +const createSectionFacetsSorted = function (result) { + if (!result.facets) { + return []; + } + let nodes = []; + + // Section facets are named section.lvln where n is 0 to 3. + for (let i = 0; ; i++) { + let key = `section.lvl${i}`; + let sectionFacet = result.facets[key]; + if (!sectionFacet) { + break; + } + + for (let k in sectionFacet) { + let parts = k.split(' > '); + let first = parts[0]; + if (first.endsWith('-branches')) { + continue; + } + let last = parts[parts.length - 1]; + let title = last.replace('-', ' '); + // First letter upper case. + title = title.charAt(0).toUpperCase() + title.slice(1); + let href = `/docs/${parts.join('/').toLowerCase()}/`; + let node = { + href: href, + key: k, + level: i + 1, + title: title, + count: sectionFacet[k], + open: false, + }; + nodes.push(node); + } + } + + // Sort by href. + nodes.sort((a, b) => { + return a.href < b.href ? -1 : 1; + }); + return nodes; }; export function newSearchStore(searchConfig, params, Alpine) { let cacheWarmerUrls = params.search_cachewarmer_urls; + let setResult = function (result, loaded = true) { + let facets = createSectionFacetsSorted(result); + this.sectionFacets = facets; + this.result = result; + this.loaded = loaded; + }; + let results = { - blank: { loaded: false }, - main: { loaded: false }, + blank: { loaded: false, set: setResult }, + main: { loaded: false, set: setResult }, explorerData: { loaded: false }, // Holds the last Algolia queryID. lastQueryID: '', @@ -105,7 +152,7 @@ export function newSearchStore(searchConfig, params, Alpine) { return section.name === key.toLocaleLowerCase(); }); - m = this.metaResult.get(key); + let m = this.metaResult.get(key); if (!m && sectionConfigIdx !== -1) { let index = searchConfig.sectionsSorted[sectionConfigIdx]; m = { title: index.title, linkTitle: index.title, excerpt: '' }; @@ -121,7 +168,7 @@ export function newSearchStore(searchConfig, params, Alpine) { return m; }; - searchEffectAdHoc = Alpine.effect(() => { + const searchEffectAdHoc = Alpine.effect(() => { debug('searchEffectAdHoc', this.searchGroupAdHoc.length); searcher.searchFactories(this.searchGroupAdHoc, null); }); @@ -150,8 +197,7 @@ export function newSearchStore(searchConfig, params, Alpine) { return newRequestCallback( createSectionRequest(query), (result) => { - this.results.main.result = result; - this.results.main.loaded = true; + this.results.main.set(result); }, { query: query, @@ -268,7 +314,7 @@ export function newSearchStore(searchConfig, params, Alpine) { throw `invalid state: ${result.index}`; } debug('withBlank.blank.result:', result); - this.results.blank.result = result; + this.results.blank.set(result, false); markLoaded(); }, { @@ -587,6 +633,7 @@ class SearchBatcher { if (fileCacheUrl) { debug('fetch data from file cache:', fileCacheUrl); const response = await fetch(fileCacheUrl, { credentials: 'same-origin' }); + if (response.ok) { let data = await response.json(); if (Array.isArray(data)) { diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/home/home.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/home/home.js index 59d6ff7e979..00bb80772c0 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/home/home.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/home/home.js @@ -6,7 +6,8 @@ import { SearchGroupIdentifier, RequestCallBackStatus, } from '../../search/request'; -import { isMobile, isTouchDevice, newSwiper } from '../../helpers/index'; +import { isMobile, isTouchDevice } from '../../helpers/helpers'; +import { newSwiper } from '../../helpers/swipe'; var debug = 0 ? console.log.bind(console, '[home]') : function () {}; diff --git a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/sections/list.js b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/sections/list.js index 08d3aeb257b..c11fc6065b3 100644 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/sections/list.js +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/sections/sections/list.js @@ -1,6 +1,6 @@ 'use strict'; -import { getIntParamFromLocation, setDocumentMeta, updatePaginationParamInLocation } from '../../helpers/index'; +import { getIntParamFromLocation, setDocumentMeta, updatePaginationParamInLocation } from '../../helpers/helpers'; import { newCreateHref } from '../../navigation/index'; import { newRequestCallback, diff --git a/_vendor/github.com/linode/linode-docs-theme/config.toml b/_vendor/github.com/linode/linode-docs-theme/config.toml index bb844ed0482..df0325ec8dc 100644 --- a/_vendor/github.com/linode/linode-docs-theme/config.toml +++ b/_vendor/github.com/linode/linode-docs-theme/config.toml @@ -22,8 +22,6 @@ api_key = "d364d8e601808b96178d0776ed43b2c9" meta_index = "linode-documentation-sections" index_prefix = "" click_analytics = true - -explorer_max_leafnodes = 50 hits_per_page = 10 # Holds configuration for the index with all of the sections merged in. diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/_default/baseof.html b/_vendor/github.com/linode/linode-docs-theme/layouts/_default/baseof.html index 6885562be98..4c32078c745 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/_default/baseof.html +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/_default/baseof.html @@ -143,8 +143,9 @@ {{/* Left side explorer menu. */}} {{/* Main content area. */}} diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/api/list.json b/_vendor/github.com/linode/linode-docs-theme/layouts/api/list.json index 2ed9618160e..c36ac8b5d72 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/api/list.json +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/api/list.json @@ -1,33 +1,2 @@ -{{- $api := resources.Get "api/openapi.yaml" | openapi3.Unmarshal -}} -{{ $ordinal := 0 }} -{{ $content := "" }} - -{{- $index := slice -}} -{{- range $k, $v := $api.Paths -}} -{{ $ops := slice (dict "o" $v.Connect "m" "Connect") (dict "o" $v.Delete "m" "Delete") (dict "o" $v.Get "m" "Get") }} -{{ $ops = $ops | append (dict "o" $v.Patch "m" "Patch") (dict "o" $v.Post "m" "Post") (dict "o" $v.Put "m" "Put") (dict "o" $v.Trace "m" "Trace")}} -{{- range $ops -}} -{{- if .o -}} -{{ $ctx := newScratch }} -{{ $ctx.Set "dot" . }} -{{ $ctx.Set "page" $ }} -{{ $ctx.Set "pathKey" $k }} -{{ $ctx.Set "path" $v }} -{{ $ctx.Set "api" $api }} -{{ $content = trim (partial "openapi3/algolia-operation-content-plain" $ctx) " \n" | htmlUnescape | safeHTML }} -{{ $ordinal = add $ordinal 1 }} -{{ $tag := partial "openapi3/get-tags-title-op" .o }} -{{- $baseSection := $tag | anchorize -}} -{{- $parts := split $k "/" -}} -{{- $section := printf "api > %s" $baseSection -}} -{{- $op := .o -}} -{{- $excerpt := $op.Description | $.RenderString | plainify -}} -{{- $httpMethod := .m -}} -{{- $title := $op.Summary }} -{{- $href := printf "/docs/api/%s/#%s" $baseSection ($op.Summary | anchorize) -}} -{{- $objectID := printf "%s: %s" $httpMethod $k }} -{{- $index = $index | append (dict "objectID" $objectID "ordinal" $ordinal "href" $href "title" $title "section" $section "section.lvl0" "api" "section.lvl1" $section "excerpt" $excerpt "content" $content "httpMethod" $httpMethod) -}} -{{- end -}} -{{- end -}} -{{- end -}} +{{ $index := partialCached "sections/search/get-search-data-api.html" . }} {{- $index | jsonify (dict "indent" " ") -}} \ No newline at end of file diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/index.json b/_vendor/github.com/linode/linode-docs-theme/layouts/index.json index 8a50c2a0d25..eac05813b10 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/index.json +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/index.json @@ -1,2 +1,2 @@ -{{- $index := partial "sections/search/get-search-data.html" . -}} +{{- $index := partialCached "sections/search/get-search-data.html" . -}} {{- $index | jsonify (dict "indent" " ") -}} \ No newline at end of file diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/components/dropdowns.html b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/components/dropdowns.html index cd5747bbef9..ceb4f182404 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/components/dropdowns.html +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/components/dropdowns.html @@ -9,7 +9,7 @@ x-data="lncDropdowns({{ $dropdowns | jsonify }})" @click.away="closeAll()"> {{ range $i, $e := .dropdowns }} -
+
@@ -24,14 +24,11 @@ {{ end }} -
+
- - -
-
- - +
diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/after-body-start.html b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/after-body-start.html index 8b70e76b61f..396da97230a 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/after-body-start.html +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/after-body-start.html @@ -1,3 +1,7 @@ + {{/* Scripts to be executed as soon as possible. */}} {{- $js := resources.Get "js/body-start/index.js" -}} {{ partial "helpers/script-src.html" (dict "js" $js "nodefer" true "params" (dict "is_production" (ne hugo.Environment "development") ) ) }} diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/content/promo_code.html b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/content/promo_code.html index fcfd08a8045..0d660a9fedd 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/content/promo_code.html +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/content/promo_code.html @@ -5,7 +5,7 @@ - - +
diff --git a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/navigation/explorer.html b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/navigation/explorer.html index 9690839fc10..67d1d38e1fa 100644 --- a/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/navigation/explorer.html +++ b/_vendor/github.com/linode/linode-docs-theme/layouts/partials/sections/navigation/explorer.html @@ -1,18 +1,21 @@ {{ partial "sections/navigation/explorer-icons.html" . }} +{{ $pageInfo := dict + "href" .RelPermalink + "isPage" .IsPage + "isHome" .IsHome + "section" .Section + "hrefSection" $.CurrentSection.RelPermalink | jsonify +}}
+ @turbo:render.window="onTurboRender($event.detail)">
-
    +
      + {{ template "explorer-static" . }} +
    - -