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 07425b5b0b8..8c2cef3cd33 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/helpers'; +import { setIsTranslating, isMobile, toggleBooleanClass } from '../main/helpers/index'; (function () { if (!window.turbolinksLoaded) { @@ -7,10 +7,6 @@ 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 deleted file mode 100644 index e70e9e94706..00000000000 --- a/_vendor/github.com/linode/linode-docs-theme/assets/js/early/index.js +++ /dev/null @@ -1,5 +0,0 @@ -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 ea14c97a9d1..81e8327a352 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/helpers'; +import { isDesktop, isMobile } from '../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 9a8a8e5cd26..32c1b364f02 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/helpers'; +import { getScrollLeft } from '../helpers/index'; 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 b99b847dbb8..f6bc392e514 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/helpers'; +import { getCookie } from '../helpers'; var debug = 0 ? console.log.bind(console, '[trustarc]') : function () {}; @@ -23,7 +23,7 @@ export function initConsentManager(trustarcDomain, trustecm, callback) { }, }; - const onConsent = function (e) { + 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 b340cb1eb69..3f617cd4fee 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,6 +19,21 @@ 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) { @@ -32,6 +47,11 @@ 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; @@ -48,7 +68,7 @@ export function toDateString(date) { } // https://gist.github.com/rmariuzzo/8761698 -function sprintf(format) { +export function sprintf(format) { var args = Array.prototype.slice.call(arguments, 1); var i = 0; return format.replace(/%s/g, function () { @@ -56,6 +76,18 @@ 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(); @@ -69,33 +101,20 @@ export function getScrollLeft(parent, child) { return childRect.left - parentRect.left; } -// 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]; +// 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; + } } } - - if (target) { - let explorer = document.getElementById('explorer'); - if (explorer) { - return [explorer, target.offsetTop]; - } - } - - return [null, -1]; + return distance < 0 ? 0 : distance; } export function setIsTranslating(el, timeout = 1000) { @@ -147,7 +166,7 @@ export function getIntParamFromLocation(param) { return 0; } -function isIterable(obj) { +export function isIterable(obj) { return Symbol.iterator in Object(obj); } @@ -159,7 +178,7 @@ export function isDesktop() { return isScreenLargerThan(1279); // xl in Tailwind config. } -function isScreenLargerThan(px) { +export function isScreenLargerThan(px) { return document.documentElement.clientWidth > px; } @@ -172,6 +191,10 @@ 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 = ''; @@ -183,6 +206,21 @@ 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 new file mode 100644 index 00000000000..1bbf0a6c93d --- /dev/null +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/index.js @@ -0,0 +1,5 @@ +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 new file mode 100644 index 00000000000..8672c720ea4 --- /dev/null +++ b/_vendor/github.com/linode/linode-docs-theme/assets/js/main/helpers/lru.d.ts @@ -0,0 +1,83 @@ +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 edc7007a4e3..93a7dc94649 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,18 +11,16 @@ import { newDropdownsController, newTabsController, } from './components/index'; -import { scrollToActiveExplorerNode, setIsTranslating, getCurrentLang } from './helpers/helpers'; -import { leackChecker } from './helpers/leak-checker'; +import { isMobile, setIsTranslating, getCurrentLang, leackChecker } from './helpers/index'; import { addLangToLinks, newBreadcrumbsController, newLanguageSwitcherController, newNavController, newPromoCodesController, + newSearchExplorerController, newToCController, newPaginatorController, - newSearchExplorerRoot, - newSearchExplorerNode, } from './navigation/index'; import { newNavStore } from './navigation/nav-store'; // AlpineJS controllers and helpers. @@ -44,7 +42,6 @@ const searchConfig = getSearchConfig(params); return false; }); } - __stopWatch('index.js.start'); // Register AlpineJS plugins. @@ -80,8 +77,7 @@ const searchConfig = getSearchConfig(params); Alpine.data('lncLanguageSwitcher', newLanguageSwitcherController(params.weglot_api_key)); Alpine.data('lncSearchFilters', () => newSearchFiltersController(searchConfig)); Alpine.data('lncSearchInput', newSearchInputController); - Alpine.data('lncSearchExplorerRoot', (pageInfo) => newSearchExplorerRoot(pageInfo)); - Alpine.data('lncSearchExplorerNode', (node = {}) => newSearchExplorerNode(searchConfig, node)); + Alpine.data('lncSearchExplorer', () => newSearchExplorerController(searchConfig)); Alpine.data('lncToc', newToCController); Alpine.data('lncBreadcrumbs', () => newBreadcrumbsController(searchConfig)); Alpine.data('lncDropdowns', newDropdownsController); @@ -147,6 +143,9 @@ 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') { @@ -189,40 +188,4 @@ 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 0159689bc5b..ad194893126 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/helpers'; +import { sanitizeHTML } from '../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 d9be3c45ddb..668181e46c4 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,109 +1,299 @@ 'use strict'; -import { isMobile } from '../helpers/helpers'; +import { getOffsetTop, isElementInViewport, isMobile } from '../helpers/index'; 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-node]') : function () {}; +var debug = 0 ? console.log.bind(console, '[explorer]') : function () {}; -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); +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'; } - pageInfo.sectionKey = sectionKey; - return { - // Explorer state. - explorer: { - // The current page. - pageInfo: pageInfo, + 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; + }; - // Sorted by href. - facets: [], + 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}`, + }; + }; - // Callbacks called when facets are loaded. - onFacetsLoaded: [], + 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. - // Callbacks called on render after a navigation with Turbo. - onTurboRender: [], + var isDone = false; + var container = self.$el.parentNode; - searchActivated: false, - onSearchActivated: [], - }, + const scroll = function (what, to) { + if (to >= 0 && container.scrollTop !== to) { + container.scrollTop = to; + } - 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, - }; + debug('scroll', what, to); + + isDone = true; + }; + + if (!nodeInfo.branchKey) { + scroll('home', 0); + return; + } + + let li = document.getElementById(nodeKeyToID(nodeInfo.branchKey)); + if (!li) { + scroll('li not found: ' + nodeInfo.branchKey, -1); + return; + } + + // Select the active branch or leaf node. + let selector = nodeInfo.isBranch ? 'div > div a' : '.is-active-page'; + + 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]; + + setTimeout(function () { + if (isDone) { + return; } - }); - 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; + + 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); } - updateFacetState(this.explorer.facets, value); - }); - }, createExplorerNodeRequest); - }, + }, 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 = {}; - getFacetsFor: function (node) { - return findChildren(node, this.explorer.facets); + 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: [], }, - findNode: function (href) { - let index = this.explorer.facets.findIndex((n) => n.href === href); - if (index === -1) { - return null; + // 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); } - return this.explorer.facets[index]; - }, + }; - // Open/close state and transitions. + for (let n of rootNode.sections) { + if (shouldSkip(n)) { + continue; + } + walkRecursive(n); + } + }; - onTurboRender: function () { - if (document.documentElement.hasAttribute('data-turbo-preview')) { - // Turbolinks is displaying a preview - return; + // May be set on initial load to expand to a given node. + var onLoadNodeInfo = null; + + return { + data: data, + isMenuNavigation: false, + scrollTop: 0, + open: true, + noTransitions: true, + + 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; } - 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; - } - let currentNode = this.findNode(hrefSection); - if (currentNode) { - currentNode.open = currentNode.count > 0; - openNodeAndCloseTheOthers(currentNode, this.explorer.facets); + + 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); + } } + loadInitialData(sectionKeys); } - this.explorer.onTurboRender.forEach((cb) => cb()); + + this.$watch('$store.nav.open.explorer', (value) => { + this.noTransitions = false; + if (value && !this.data.loaded) { + loadInitialData(); + } + }); + }, + + isOpen: function () { + return this.$store.nav.open.explorer && this.data.loaded; }, closeIfMobile: function () { @@ -112,318 +302,578 @@ export function newSearchExplorerRoot(pageInfo) { } }, - isOpen: function () { - return this.$store.nav.open.explorer; - }, + load: function () { + if (!this.getResult()) { + return; + } - // 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', - }; - }, - }; -} + if (this.data.loaded) { + this.filterNodes(); + return; + } -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, - }, - 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); + let shouldScroll = onLoadNodeInfo; + + this.buildNodes(); + this.render(); + this.data.loaded = true; + + if (shouldScroll) { + var onLoadNodeInfoCopy = onLoadNodeInfo; + this.$nextTick(() => { + scrollToActive(this, onLoadNodeInfoCopy); + }); } - this.activatePageSearch(); - }, - isActive: function (href) { - return href === this.state.activeHref; + // Only needed on inital page load, + onLoadNodeInfo = null; + __stopWatch('explorer.loaded'); }, - showStaticLeafNodes: function () { - // Keep it static until it isn't. - return !this.state.pagesLoaded; + onTurbolinksBeforeVisit: function (data) { + this.scrollTop = this.$el.parentNode.scrollTop; }, - onClickStaticLeafNode: function (href, objectID) { - window.explorerNodeClicked = true; - this.state.activeHref = href; - let hit = { - objectID: objectID, - }; - this.$store.nav.analytics.handler.clickHit(hit, 'DOCS: Explorer'); + // 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(); }, - onClick: function (e, p = null) { - // To avoid unnecessary scrolling on navigation. - window.explorerNodeClicked = true; + onTurbolinksRender: function (data) { + if (document.documentElement.hasAttribute('data-turbolinks-preview')) { + return; + } + + let isMenuNavigation = this.isMenuNavigation; + this.isMenuNavigation = false; - let hit = null; - if (p) { - hit = p.hit; + 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 { - hit = this.node.hit; + this.$el.parentNode.scrollTop = this.scrollTop; } + }, - if (hit) { - this.state.activeHref = hit.href; - // Send click events to Algolia insights. - this.$store.nav.analytics.handler.clickHit(hit, 'DOCS: Explorer'); - } + setActiveBranch: function (nodeInfo) { + let nodeKey = nodeInfo.branchKey; + for (let k in this.data.nodes) { + let n = this.data.nodes[k]; - // 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; + 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; + } } + }, - if (this.node.key === 'community > question') { - e.preventDefault(); - this.$store.nav.openSearchPanelWithQuery((query) => { - query.addFilter('docType', this.node.key); - }); + render: function () { + let fragment = new DocumentFragment(); + this.renderNodesRecursive(fragment, 1); + this.target.innerHTML = ''; + this.target.appendChild(fragment); + }, + + renderNodesRecursive: function (ulEl, level) { + let templ = + level === 1 + ? document.importNode(templates.templateLoopRoot.content.querySelector('template'), true) + : document.importNode(templates.templateLoopNested.content.querySelector('template'), true); + + let li = document.importNode(templates.templateTree.content.querySelector('.explorer__node'), true); + + let mainLoop = templ.content.querySelector('template'); + mainLoop.content.appendChild(li); + ulEl.appendChild(templ); + + ulEl = li.querySelector('.node-tree'); + + if (level >= 5) { + // configure return; } + + level++; + + this.renderNodesRecursive(ulEl, level); }, - init: function init() { - templates = { - templateNode: this.$refs['templateNode'], - templateNodePages: this.$refs['templateNodePages'], + 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, }; - this.explorer.onFacetsLoaded.push(() => { - // Replace the temporary static nodes with a reactive one. - let n = this.findNode(this.node.href); + let self = this; + + n.id = function () { + return nodeKeyToID(this.key); + }; - if (n) { - n.open = this.node.open && n.count; - this.node = n; + n.children = function () { + if (this.pages.length === 0) { + // The sections slice is sorted and ready to use. + return this.sections; } - }); - this.explorer.onTurboRender.push(() => { - // Reattach the proxy on navigation. - let n = this.findNode(this.node.href); - if (n) { - this.node = n; + 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.explorer.onSearchActivated.push(() => { - this.activatePageSearch(); - }); - }, + this.parent.pages.forEach((p) => { + p.active = p.key === n.key; + }); + }; - getNodesSection: function () { - return this.getFacetsFor(this.node); - }, + n.onClick = function (e) { + if (e.metaKey) { + return; + } + if (!this.isLeaf() && !this.isDisabled() && !this.open) { + this.toggleOpen(); + } - 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; - } + if (this.isLeaf()) { + this.setAsActive(); + } - // 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); - } + if (this.isGhostSection) { + e.preventDefault(); - // 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, - } - ); - }, + 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; }; - this.$store.search.addSearches(newRequestCallbackFactoryTarget(factory, SearchGroupIdentifier.Main)); + + n.loadPages = function () {}; + + return n; }, - hydrateIfNeeded: function () { - if (!this.state.hydrated) { - this.hydrate(); + 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, + }) + ); } + + n.pages = pages; }, - hydrate: function () { - let self = this; - this.state.hydrated = true; + // loadPages loads any leaf pages into node if not already loaded. + loadPages: function (node) { + let nodes = []; + + this.data.walk(this.data.rootNode, function (nn) { + if (nn.open) { + nodes.push(nn); + } + + // If it's not open we skip any descendants. + return !nn.open; + }); + + if (!node.open) { + // Preload + nodes.push(node); + } - const nodeElement = this.$refs['node-tree']; + if (nodes.length === 0) { + return; + } - let nodeTemplate = document.importNode(templates.templateNode.content.querySelector('template'), true); - let nodePagesTemplate = document.importNode(templates.templateNodePages.content.querySelector('template'), true); + var self = this; - // First append the leaf nodes. - nodeElement.appendChild(nodePagesTemplate); - // Then append the branch nodes. - nodeElement.appendChild(nodeTemplate); + nodes.forEach((node) => { + if (node.level < startLevelRegularPages) { + return; + } + + 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)); + }); }, - }; - return ctrl; -} + buildNodes: function () { + var result = this.getResult(); + var openSections = {}; + if (!this.data.loaded && this.data.initial) { + openSections = this.data.initial.sections; + } -const findChildren = function (node, nodes) { - let children = []; + var self = this; - // 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; - } + this.data.parseKey = function (key) { + let parts = key.split(sectionDelimm); + let level = parts.length; + let name = parts[parts.length - 1]; - // 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; -}; + let href = router.hrefSection(key); -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); - } -}; + let parentKey = false; + if (parts.length > 1) { + parentKey = parts.slice(0, parts.length - 1).join(sectionDelimm); + } -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; - } - } -}; + 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; + }; -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]; + 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; + } + 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; + } + } + + for (let s of n.sections) { + if (s.hasOpenDescendants()) { + return true; + } + } + 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; + } + } + }; + + // 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); } - if (!fromNode) { - toNode.count = 0; - toNode.open = false; - continue; + + debug('nodes', this.data.nodes); + + for (let k in this.data.nodes) { + let n = this.data.nodes[k]; + + if (n.level !== 1) { + continue; + } + n.parent = this.data.rootNode; + this.data.rootNode.sections.push(n); } - } else { - fromNode = from[fromIndex]; - } + }, - if (toNode.href === fromNode.href) { - toNode.count = fromNode.count; - fromIndex++; - continue; - } + getResult: function () { + if (this.data.loaded) { + return this.$store.search.results.main.result; + } + return this.data.initial.blank.result; + }, - toNode.count = 0; - toNode.open = false; - } -}; + // Update hidden state and facet counts based on a updated search result. + filterNodes: function () { + debug('filterNodes'); + let result = this.getResult(); -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 = ''; + let seen = new Set(); + let resultSections = result.sections(); - return { - indexName: searchConfig.indexName(searchConfig.sections_merged.index), - filters: filters, - facetFilters: facetFilters.concat(facetFilters, sectionFilter), - distinct: 0, - params: `query=${encodeURIComponent(query.lndq)}&hitsPerPage=${maxLeafNodes}`, - }; -}; + 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; + } -// 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; - */ -}; + n.count = resultSection.count; + seen.add(kp.key); + } + + for (let k in this.data.nodes) { + let n = this.data.nodes[k]; + + if (!seen.has(n.key)) { + if (n.level === 1) { + n.count = 0; + n.open = false; + } else { + // Hide it in the DOM. + n.hidden = true; + } + + continue; + } else { + n.hidden = false; + } + } + }, + + // 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 {}; + } + + 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', + }; + }, + }; +} 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 8fe237aef09..c801b8c55cc 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 } from '../helpers/helpers'; +import { getCurrentLangFromLocation, setIsTranslating } from '../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); - } else { - Weglot.switchTo(lang); + return; } }, 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 903f1e1abe8..7b151592581 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,5 +1,4 @@ -import { getCookie, setCookie, supportsCookies, createUUID } from '../helpers/helpers'; -import { smartQueue } from '../helpers/smartqueue'; +import { smartQueue, getCookie, setCookie, supportsCookies, createUUID } from '../helpers'; 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 5799b7e17a9..0d9c6c2a378 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/helpers'; +import { isMobile } from '../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 a20b1c6c05f..0ad8c795e77 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/helpers'; +import { isMobile, toggleBooleanClass } from '../helpers/index'; 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 cca47907d22..3a59b5e4346 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/helpers'; +import { getIntParamFromLocation, updatePaginationParamInLocation } from '../helpers/index'; 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 14b0493a937..f586e4aa95f 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/helpers'; +import { isMobile } from '../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 5fc32a918bc..4f82d128c80 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/helpers'; +import { isDesktop, isMobile } from '../helpers/index'; 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 6638ecf3b17..1298d874d3b 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/helpers'; +import { toggleBooleanClass } from '../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 fe8ec907f81..bb0ea35bc33 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/helpers'; +import { normalizeSpace } from '../helpers/index'; 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 0257bf9176c..8509dea78c7 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,6 +1,5 @@ import { newQuery, QueryHandler } from './query'; -import { getCurrentLang, toDateString } from '../helpers/helpers'; -import { LRUMap } from '../helpers/lru'; +import { getCurrentLang, LRUMap, toDateString } from '../helpers'; import { newCreateHref, addLangToHref } from '../navigation/index'; import { newRequestCallback, @@ -13,63 +12,17 @@ import { const debug = 0 ? console.log.bind(console, '[search-store]') : function () {}; const debugFetch = 0 ? console.log.bind(console, '[search-fetch]') : function () {}; -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 const searchGroupIdentifiers = { + MAIN: 1, + AD_HOC: 2, }; 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, set: setResult }, - main: { loaded: false, set: setResult }, + blank: { loaded: false }, + main: { loaded: false }, explorerData: { loaded: false }, // Holds the last Algolia queryID. lastQueryID: '', @@ -152,7 +105,7 @@ export function newSearchStore(searchConfig, params, Alpine) { return section.name === key.toLocaleLowerCase(); }); - let m = this.metaResult.get(key); + m = this.metaResult.get(key); if (!m && sectionConfigIdx !== -1) { let index = searchConfig.sectionsSorted[sectionConfigIdx]; m = { title: index.title, linkTitle: index.title, excerpt: '' }; @@ -168,7 +121,7 @@ export function newSearchStore(searchConfig, params, Alpine) { return m; }; - const searchEffectAdHoc = Alpine.effect(() => { + searchEffectAdHoc = Alpine.effect(() => { debug('searchEffectAdHoc', this.searchGroupAdHoc.length); searcher.searchFactories(this.searchGroupAdHoc, null); }); @@ -197,7 +150,8 @@ export function newSearchStore(searchConfig, params, Alpine) { return newRequestCallback( createSectionRequest(query), (result) => { - this.results.main.set(result); + this.results.main.result = result; + this.results.main.loaded = true; }, { query: query, @@ -314,7 +268,7 @@ export function newSearchStore(searchConfig, params, Alpine) { throw `invalid state: ${result.index}`; } debug('withBlank.blank.result:', result); - this.results.blank.set(result, false); + this.results.blank.result = result; markLoaded(); }, { @@ -633,7 +587,6 @@ 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 00bb80772c0..59d6ff7e979 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,8 +6,7 @@ import { SearchGroupIdentifier, RequestCallBackStatus, } from '../../search/request'; -import { isMobile, isTouchDevice } from '../../helpers/helpers'; -import { newSwiper } from '../../helpers/swipe'; +import { isMobile, isTouchDevice, newSwiper } from '../../helpers/index'; 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 c11fc6065b3..08d3aeb257b 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/helpers'; +import { getIntParamFromLocation, setDocumentMeta, updatePaginationParamInLocation } from '../../helpers/index'; 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 df0325ec8dc..bb844ed0482 100644 --- a/_vendor/github.com/linode/linode-docs-theme/config.toml +++ b/_vendor/github.com/linode/linode-docs-theme/config.toml @@ -22,6 +22,8 @@ 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 4c32078c745..6885562be98 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,9 +143,8 @@ {{/* 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 c36ac8b5d72..2ed9618160e 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,2 +1,33 @@ -{{ $index := partialCached "sections/search/get-search-data-api.html" . }} +{{- $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 | 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 eac05813b10..8a50c2a0d25 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 := partialCached "sections/search/get-search-data.html" . -}} +{{- $index := partial "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 ceb4f182404..cd5747bbef9 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,11 +24,14 @@ {{ 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 396da97230a..8b70e76b61f 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,7 +1,3 @@ - {{/* 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 0d660a9fedd..fcfd08a8045 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 67d1d38e1fa..9690839fc10 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,21 +1,18 @@ {{ partial "sections/navigation/explorer-icons.html" . }} -{{ $pageInfo := dict - "href" .RelPermalink - "isPage" .IsPage - "isHome" .IsHome - "section" .Section - "hrefSection" $.CurrentSection.RelPermalink | jsonify -}}
+ @turbo:before-visit.document="onTurbolinksBeforeVisit($event.detail);" + @turbo:before-render.document="onTurbolinksBeforeRender($event.detail)" + @turbo:render.document="onTurbolinksRender($event.detail);" + data-turbo-permanent>
-
    - {{ template "explorer-static" . }} -
+
    -