content on click.
- */
-function addCopyButtons () {
qsAll('pre:has(> code:first-child):not(:has(.copy-button))').forEach(pre => {
+ if (!buttonTemplate) {
+ const div = document.createElement('div')
+ div.innerHTML = buttonHtml
+ buttonTemplate = div.firstChild
+ }
+
const button = buttonTemplate.cloneNode(true)
pre.appendChild(button)
diff --git a/assets/js/entry/html.js b/assets/js/entry/html.js
index 3b22276d3..498554c4c 100644
--- a/assets/js/entry/html.js
+++ b/assets/js/entry/html.js
@@ -1,17 +1,14 @@
import { onDocumentReady } from '../helpers'
import { initialize as initTabsets } from '../tabsets'
import { initialize as initContent } from '../content'
-import { initialize as initSidebarDrawer, update as updateSidebarDrawer } from '../sidebar/sidebar-drawer'
-import { initialize as initSidebarContent, update as updateSidebarContent } from '../sidebar/sidebar-list'
+import { initialize as initSidebarDrawer } from '../sidebar/sidebar-drawer'
import { initialize as initSearch } from '../search-bar'
import { initialize as initVersions } from '../sidebar/sidebar-version-select'
import { initialize as initSearchPage } from '../search-page'
import { initialize as initTheme } from '../theme'
import { initialize as initMakeup } from '../makeup'
-import { initialize as initModal } from '../modal'
import { initialize as initKeyboardShortcuts } from '../keyboard-shortcuts'
import { initialize as initQuickSwitch } from '../quick-switch'
-import { initialize as initToast } from '../toast'
import { initialize as initTooltips } from '../tooltips/tooltips'
import { initialize as initHintsPage } from '../tooltips/hint-page'
import { initialize as initCopyButton } from '../copy-button'
@@ -58,8 +55,6 @@ onDocumentReady(() => {
initTooltips()
initCopyButton()
- updateSidebarDrawer()
- updateSidebarContent()
initSearch()
initSearchPage()
initSettings()
@@ -71,13 +66,10 @@ onDocumentReady(() => {
}
initVersions()
- initModal()
initKeyboardShortcuts()
initQuickSwitch()
- initToast()
initSidebarDrawer()
- initSidebarContent()
initSearch()
initSearchPage()
initSettings()
diff --git a/assets/js/handlebars/helpers.js b/assets/js/handlebars/helpers.js
index 17c355e4b..11bb0b748 100644
--- a/assets/js/handlebars/helpers.js
+++ b/assets/js/handlebars/helpers.js
@@ -1,45 +1,5 @@
import * as Handlebars from 'handlebars/runtime'
-Handlebars.registerHelper('groupChanged', function (context, nodeGroup, options) {
- const group = nodeGroup || ''
- if (context.group !== group) {
- // reset the nesting context for the #nestingChanged block helper
- delete context.nestedContext
- context.group = group
- return options.fn(this)
- }
-})
-
-Handlebars.registerHelper('nestingChanged', function (context, node, options) {
- // context.nestedContext is also reset each time a new group
- // is encountered (the value is reset within the #groupChanged
- // block helper)
- if (node.nested_context && node.nested_context !== context.nestedContext) {
- context.nestedContext = node.nested_context
-
- if (context.lastModuleSeenInGroup !== node.nested_context) {
- return options.fn(this)
- }
- } else {
- // track the most recently seen module
- // prevents emitting a duplicate entry for nesting when
- // the nesting prefix matches an existing module
- context.lastModuleSeenInGroup = node.title
- }
-})
-
-Handlebars.registerHelper('showSections', function (node, options) {
- if (node.sections.length > 0) {
- return options.fn(this)
- }
-})
-
-Handlebars.registerHelper('showSummary', function (node, options) {
- if (node.nodeGroups) {
- return options.fn(this)
- }
-})
-
Handlebars.registerHelper('isArray', function (entry, options) {
if (Array.isArray(entry)) {
return options.fn(this)
@@ -55,21 +15,3 @@ Handlebars.registerHelper('isNonEmptyArray', function (entry, options) {
return options.inverse(this)
}
})
-
-Handlebars.registerHelper('isEmptyArray', function (entry, options) {
- if (Array.isArray(entry) && entry.length === 0) {
- return options.fn(this)
- } else {
- return options.inverse(this)
- }
-})
-
-Handlebars.registerHelper('isLocal', function (nodeId, options) {
- const pathSuffix = window.location.pathname.split('/').pop()
-
- if (pathSuffix === nodeId + '.html' || pathSuffix === nodeId) {
- return options.fn(this)
- } else {
- return options.inverse(this)
- }
-})
diff --git a/assets/js/handlebars/templates/copy-button.html b/assets/js/handlebars/templates/copy-button.html
new file mode 100644
index 000000000..8196242df
--- /dev/null
+++ b/assets/js/handlebars/templates/copy-button.html
@@ -0,0 +1,7 @@
+
diff --git a/assets/js/handlebars/templates/modal-layout.handlebars b/assets/js/handlebars/templates/modal-layout.html
similarity index 100%
rename from assets/js/handlebars/templates/modal-layout.handlebars
rename to assets/js/handlebars/templates/modal-layout.html
diff --git a/assets/js/handlebars/templates/quick-switch-modal-body.handlebars b/assets/js/handlebars/templates/quick-switch-modal-body.html
similarity index 100%
rename from assets/js/handlebars/templates/quick-switch-modal-body.handlebars
rename to assets/js/handlebars/templates/quick-switch-modal-body.html
diff --git a/assets/js/handlebars/templates/quick-switch-results.handlebars b/assets/js/handlebars/templates/quick-switch-results.handlebars
deleted file mode 100644
index 671a503cd..000000000
--- a/assets/js/handlebars/templates/quick-switch-results.handlebars
+++ /dev/null
@@ -1,5 +0,0 @@
-{{#each results}}
-
- {{name}}
-
-{{/each}}
diff --git a/assets/js/handlebars/templates/sidebar-items.handlebars b/assets/js/handlebars/templates/sidebar-items.handlebars
deleted file mode 100644
index ce2364244..000000000
--- a/assets/js/handlebars/templates/sidebar-items.handlebars
+++ /dev/null
@@ -1,76 +0,0 @@
-{{#each nodes as |node nodeId|}}
- {{#groupChanged ../this node.group}}
-
- {{node.group}}
-
- {{/groupChanged}}
-
- {{#nestingChanged ../this node}}
-
- {{/nestingChanged}}
-
-
-
- {{#if node.nested_title}}
- {{{node.nested_title}}}
- {{else}}
- {{{node.title}}}
- {{/if}}
-
-
- {{#isEmptyArray node.headers}}
- {{else}}
-
- {{/isEmptyArray}}
-
- {{#isArray node.headers}}
- {{#isNonEmptyArray node.headers}}
-
- {{#each node.headers}}
- -
- {{{id}}}
-
- {{/each}}
-
- {{/isNonEmptyArray}}
- {{else}}
-
- {{#showSections node}}
- -
-
- Sections
-
-
-
- {{#each sections}}
- -
- {{{id}}}
-
- {{/each}}
-
-
- {{/showSections}}
- {{#showSummary node}}
- -
- Summary
-
- {{/showSummary}}
- {{#each node.nodeGroups as |group|}}
- -
-
- {{group.name}}
-
-
-
- {{#each group.nodes}}
- -
- {{id}}
-
- {{/each}}
-
-
- {{/each}}
-
- {{/isArray}}
-
-{{/each}}
diff --git a/assets/js/helpers.js b/assets/js/helpers.js
index 748799991..c56c2db8a 100644
--- a/assets/js/helpers.js
+++ b/assets/js/helpers.js
@@ -46,28 +46,6 @@ export function getCurrentPageSidebarType () {
return document.getElementById('main').dataset.type
}
-/**
- * Looks up a nested node having the specified anchor
- * and returns the corresponding category.
- *
- * @param {Array} nodes A list of sidebar nodes.
- * @param {String|null} anchor The anchor to look for.
- * @returns {String} The relevant node group key, like 'functions', 'types', etc.
- */
-export function findSidebarCategory (nodes, anchor) {
- if (!nodes) return
-
- for (const node of nodes) {
- const nodeGroup = node.nodeGroups && node.nodeGroups.find(nodeGroup =>
- nodeGroup.nodes.some(subnode => subnode.anchor === anchor)
- )
-
- if (nodeGroup) return nodeGroup.key
- }
-
- return null
-}
-
/**
* Finds an element by a URL hash (e.g. a function section).
*
@@ -221,3 +199,24 @@ export function isAppleOS () {
// Set in inline_html.js
return document.documentElement.classList.contains('apple-os')
}
+
+/**
+ * Create element from tag, attributes and children.
+ *
+ * @param {string} tagName
+ * @param {Record} attributes
+ * @param {(HTMLElement | string)[]} [children]
+ * @returns {HTMLElement}
+ */
+export function el (tagName, attributes, children) {
+ const element = document.createElement(tagName)
+ for (const key in attributes) {
+ if (attributes[key] != null) {
+ element.setAttribute(key, attributes[key])
+ }
+ }
+ if (children) {
+ element.replaceChildren(...children)
+ }
+ return element
+}
diff --git a/assets/js/keyboard-shortcuts.js b/assets/js/keyboard-shortcuts.js
index ba5a6157b..a70e8a088 100644
--- a/assets/js/keyboard-shortcuts.js
+++ b/assets/js/keyboard-shortcuts.js
@@ -64,10 +64,6 @@ const state = {
* listing all available options.
*/
export function initialize () {
- addEventListeners()
-}
-
-function addEventListeners () {
document.addEventListener('keydown', handleKeyDown)
document.addEventListener('keyup', handleKeyUp)
}
diff --git a/assets/js/modal.js b/assets/js/modal.js
index 1bc62dc29..f4889aa14 100644
--- a/assets/js/modal.js
+++ b/assets/js/modal.js
@@ -1,45 +1,38 @@
import { qs } from './helpers'
-import modalLayoutTemplate from './handlebars/templates/modal-layout.handlebars'
+import modalLayoutHtml from './handlebars/templates/modal-layout.html'
-const MODAL_SELECTOR = '.modal'
-const MODAL_CLOSE_BUTTON_SELECTOR = '.modal .modal-close'
-const MODAL_TITLE_SELECTOR = '.modal .modal-title'
-const MODAL_BODY_SELECTOR = '.modal .modal-body'
const FOCUSABLE_SELECTOR = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
-const state = {
- prevFocus: null,
- lastFocus: null,
- ignoreFocusChanges: false
-}
-/**
- * Initializes modal layout.
- */
-export function initialize () {
- renderModal()
-}
+// State
+
+/** @type {HTMLDivElement | null} */
+let modal = null
+/** @type {HTMLElement | null} */
+let prevFocus = null
+/** @type {HTMLElement | null} */
+let lastFocus = null
+let ignoreFocusChanges = false
/**
* Adds the modal to DOM, initially it's hidden.
*/
function renderModal () {
- const modalLayoutHtml = modalLayoutTemplate()
+ if (modal) return
+
document.body.insertAdjacentHTML('beforeend', modalLayoutHtml)
+ modal = qs('.modal')
- qs(MODAL_SELECTOR).addEventListener('keydown', event => {
+ modal.addEventListener('keydown', event => {
if (event.key === 'Escape') {
closeModal()
}
})
- qs(MODAL_CLOSE_BUTTON_SELECTOR).addEventListener('click', event => {
- closeModal()
- })
+ modal.querySelector('.modal-close').addEventListener('click', closeModal)
- qs(MODAL_SELECTOR).addEventListener('click', event => {
- const classList = event.target.classList
+ modal.addEventListener('click', event => {
// if we clicked on the modal overlay/parent but not the modal content
- if (classList.contains('modal')) {
+ if (event.target === modal) {
closeModal()
}
})
@@ -49,63 +42,57 @@ function renderModal () {
* Trap focus in modal
* Only called on open modals
*/
-function trapFocus (event) {
- if (state.ignoreFocusChanges) return
- const modal = qs(MODAL_SELECTOR)
+function handleFocus (event) {
+ if (ignoreFocusChanges) return
+
if (modal.contains(event.target)) {
- state.lastFocus = event.target
+ lastFocus = event.target
} else {
- state.ignoreFocusChanges = true
- const firstFocusable = firstFocusableDescendant(modal)
- if (state.lastFocus === firstFocusable) {
- lastFocusableDescendant(modal).focus()
+ ignoreFocusChanges = true
+ const focusable = modal.querySelectorAll(FOCUSABLE_SELECTOR)
+ if (lastFocus === focusable[0]) {
+ // Focus last
+ focusable[focusable.length - 1].focus()
} else {
- firstFocusable.focus()
+ // Focus first
+ focusable[0].focus()
}
- state.ignoreFocusChanges = false
- state.lastFocus = document.activeElement
+ ignoreFocusChanges = false
+ lastFocus = document.activeElement
}
}
-function firstFocusableDescendant (element) {
- return element.querySelector(FOCUSABLE_SELECTOR)
-}
-
-function lastFocusableDescendant (element) {
- const elements = element.querySelectorAll(FOCUSABLE_SELECTOR)
- return elements[elements.length - 1]
-}
-
/**
* Shows modal with the given content.
*
- * @param {{ title: String, body: String }} attrs
+ * @param {{ title: string, body: string }} attrs
*/
export function openModal ({ title, body }) {
- state.prevFocus = document.activeElement
- document.addEventListener('focus', trapFocus, true)
+ renderModal()
+ prevFocus = document.activeElement
+ document.addEventListener('focus', handleFocus, true)
- qs(MODAL_TITLE_SELECTOR).innerHTML = title
- qs(MODAL_BODY_SELECTOR).innerHTML = body
+ modal.querySelector('.modal-title').innerHTML = title
+ modal.querySelector('.modal-body').innerHTML = body
- qs(MODAL_SELECTOR).classList.add('shown')
- qs(MODAL_SELECTOR).focus()
+ modal.classList.add('shown')
+ modal.focus()
}
/**
* Closes the modal.
*/
export function closeModal () {
- qs(MODAL_SELECTOR).classList.remove('shown')
+ modal?.classList.remove('shown')
- document.addEventListener('focus', trapFocus, true)
- state.prevFocus && state.prevFocus.focus()
- state.prevFocus = null
+ document.removeEventListener('focus', handleFocus, true)
+ prevFocus?.focus()
+ prevFocus = null
}
/**
* Checks whether a modal is open.
*/
export function isModalOpen () {
- return qs(MODAL_SELECTOR).classList.contains('shown')
+ return Boolean(modal?.classList.contains('shown'))
}
diff --git a/assets/js/quick-switch.js b/assets/js/quick-switch.js
index 8871c8e14..cbc45e43f 100644
--- a/assets/js/quick-switch.js
+++ b/assets/js/quick-switch.js
@@ -1,7 +1,6 @@
-import { debounce, qs, qsAll } from './helpers'
+import { debounce, el, qs, qsAll } from './helpers'
import { openModal } from './modal'
-import quickSwitchModalBodyTemplate from './handlebars/templates/quick-switch-modal-body.handlebars'
-import quickSwitchResultsTemplate from './handlebars/templates/quick-switch-results.handlebars'
+import quickSwitchModalBodyHtml from './handlebars/templates/quick-switch-modal-body.html'
const HEX_DOCS_ENDPOINT = 'https://hexdocs.pm/%%'
const OTP_DOCS_ENDPOINT = 'https://www.erlang.org/doc/apps/%%'
@@ -9,7 +8,6 @@ const HEX_SEARCH_ENDPOINT = 'https://hex.pm/api/packages?search=name:%%*'
const QUICK_SWITCH_LINK_SELECTOR = '.display-quick-switch'
const QUICK_SWITCH_INPUT_SELECTOR = '#quick-switch-input'
const QUICK_SWITCH_RESULTS_SELECTOR = '#quick-switch-results'
-const QUICK_SWITCH_RESULT_SELECTOR = '.quick-switch-result'
const DEBOUNCE_KEYPRESS_TIMEOUT = 300
const NUMBER_OF_SUGGESTIONS = 9
@@ -74,14 +72,8 @@ const state = {
* Initializes the quick switch modal.
*/
export function initialize () {
- addEventListeners()
-}
-
-function addEventListeners () {
qsAll(QUICK_SWITCH_LINK_SELECTOR).forEach(element => {
- element.addEventListener('click', event => {
- openQuickSwitchModal()
- })
+ element.addEventListener('click', openQuickSwitchModal)
})
}
@@ -117,12 +109,11 @@ function handleInput (event) {
export function openQuickSwitchModal () {
openModal({
title: 'Go to package docs',
- body: quickSwitchModalBodyTemplate()
+ body: quickSwitchModalBodyHtml
})
- qs(QUICK_SWITCH_INPUT_SELECTOR).focus()
-
const quickSwitchInput = qs(QUICK_SWITCH_INPUT_SELECTOR)
+ quickSwitchInput.focus()
quickSwitchInput.addEventListener('keydown', handleKeyDown)
quickSwitchInput.addEventListener('input', handleInput)
@@ -179,24 +170,18 @@ function queryForAutocomplete (packageSlug) {
// Only render results if the search string is still long enough
const currentTerm = qs(QUICK_SWITCH_INPUT_SELECTOR).value
if (currentTerm.length >= MIN_SEARCH_LENGTH) {
- renderResults({ results: state.autocompleteResults })
+ renderResults(state.autocompleteResults)
}
}
})
}
-function renderResults ({ results }) {
- const resultsContainer = qs(QUICK_SWITCH_RESULTS_SELECTOR)
- const resultsHtml = quickSwitchResultsTemplate({ results })
- resultsContainer.innerHTML = resultsHtml
-
- qsAll(QUICK_SWITCH_RESULT_SELECTOR).forEach(result => {
- result.addEventListener('click', event => {
- const index = result.getAttribute('data-index')
- const selectedResult = state.autocompleteResults[index]
- navigateToAppDocs(selectedResult.name)
- })
- })
+function renderResults (results) {
+ qs(QUICK_SWITCH_RESULTS_SELECTOR).replaceChildren(...results.map(({name}, index) => {
+ const resultEl = el('div', {class: 'quick-switch-result', 'data-index': index}, [name])
+ resultEl.addEventListener('click', () => navigateToAppDocs(name))
+ return resultEl
+ }))
}
/**
diff --git a/assets/js/sidebar/sidebar-drawer.js b/assets/js/sidebar/sidebar-drawer.js
index 254c0c7f3..65961d7c5 100644
--- a/assets/js/sidebar/sidebar-drawer.js
+++ b/assets/js/sidebar/sidebar-drawer.js
@@ -1,6 +1,7 @@
import throttle from 'lodash.throttle'
import { qs } from '../helpers'
import { SIDEBAR_CLASS_OPEN, SIDEBAR_CLASS_TRANSITION, SIDEBAR_PREF_CLOSED, SIDEBAR_PREF_OPEN, SIDEBAR_STATE_KEY, SIDEBAR_WIDTH_KEY, SMALL_SCREEN_BREAKPOINT } from './constants'
+import { initialize as initializeList } from './sidebar-list'
const ANIMATION_DURATION = 300
@@ -10,6 +11,8 @@ const SIDEBAR_TOGGLE_SELECTOR = '.sidebar-toggle'
export function initialize () {
update()
+ window.addEventListener('swup:page:view', update)
+
qs(SIDEBAR_TOGGLE_SELECTOR).addEventListener('click', toggleSidebar)
// Clicks outside small screen open sidebar should close it.
@@ -45,6 +48,7 @@ export function initialize () {
export function update () {
const pref = sessionStorage.getItem(SIDEBAR_STATE_KEY)
const open = pref !== SIDEBAR_PREF_CLOSED && !isScreenSmall()
+ if (open) initializeList()
updateSidebar(open)
}
diff --git a/assets/js/sidebar/sidebar-list.js b/assets/js/sidebar/sidebar-list.js
index 397cf5a57..d7a6a7172 100644
--- a/assets/js/sidebar/sidebar-list.js
+++ b/assets/js/sidebar/sidebar-list.js
@@ -1,237 +1,249 @@
-import { qs, getCurrentPageSidebarType, getLocationHash, findSidebarCategory } from '../helpers'
+import { el, getCurrentPageSidebarType, qs, qsAll } from '../helpers'
import { getSidebarNodes } from '../globals'
-import sidebarItemsTemplate from '../handlebars/templates/sidebar-items.handlebars'
-
-const SIDEBAR_TYPE = {
- search: 'search',
- extras: 'extras',
- modules: 'modules',
- tasks: 'tasks'
-}
-
-const SIDEBAR_TAB_TYPES = [SIDEBAR_TYPE.extras, SIDEBAR_TYPE.modules, SIDEBAR_TYPE.tasks]
-const sidebarNodeListSelector = type => `#${type}-full-list`
+let init = false
export function initialize () {
- update()
- addEventListeners()
-}
+ if (init) return
+ init = true
-export function update () {
- SIDEBAR_TAB_TYPES.forEach(type => {
- renderSidebarNodeList(getSidebarNodes(), type)
- })
+ const sidebarList = document.getElementById('sidebar-list-nav')
- markActiveSidebarTab(getCurrentPageSidebarType())
- markCurrentHashInSidebar()
- scrollNodeListToCurrentCategory()
-}
+ if (!sidebarList) return
-/**
- * Fill the sidebar with links to different nodes
- *
- * This function replaces an empty unordered list with an
- * unordered list full of links to the different tasks, exceptions
- * and modules mentioned in the documentation.
- *
- * @param {Object} nodesByType - Container of tasks, exceptions and modules.
- * @param {String} type - Filter of nodes, by default the type of the current page.
- */
-function renderSidebarNodeList (nodesByType, type) {
- const nodes = nodesByType[type] || []
-
- // Render the list
- const nodeList = qs(sidebarNodeListSelector(type))
- if (!nodeList) { return }
- const listContentHtml = sidebarItemsTemplate({ nodes, group: '' })
- nodeList.innerHTML = listContentHtml
-
- // Removes the "expand" class from links belonging to single-level sections
- nodeList.querySelectorAll('ul').forEach(list => {
- if (list.innerHTML.trim() === '') {
- const emptyExpand = list.previousElementSibling
- if (emptyExpand.classList.contains('expand')) {
- emptyExpand.classList.remove('expand')
- }
- list.remove()
- }
- })
+ const defaultTab = getCurrentPageSidebarType()
+ const tabs = {
+ extras: sidebarList.dataset.extras || 'Pages',
+ modules: 'Modules',
+ tasks: 'Mix Tasks'
+ }
- // Register event listeners
- nodeList.querySelectorAll('li a + button').forEach(button => {
- button.addEventListener('click', event => {
- const target = event.target
- const listItem = target.closest('li')
- toggleListItem(listItem)
- })
- })
+ Object.entries(tabs).forEach(([type, titleHtml]) => {
+ const nodes = getSidebarNodes()[type]
- nodeList.querySelectorAll('li a').forEach(anchor => {
- anchor.addEventListener('click', event => {
- const target = event.target
- const listItem = target.closest('li')
- const previousSection = nodeList.querySelector('.current-section')
+ if (!nodes?.length) return
- // Clear the previous current section
- if (previousSection) {
- clearCurrentSectionElement(previousSection)
+ const tabId = `${type}-list-tab-button`
+ const tabpanelId = `${type}-tab-panel`
+ const selected = type === defaultTab
+
+ const tab = el('button', {
+ id: tabId,
+ role: 'tab',
+ tabindex: selected ? 0 : -1,
+ 'aria-selected': selected || undefined,
+ 'aria-controls': tabpanelId
+ })
+ tab.innerHTML = titleHtml
+ tab.addEventListener('keydown', handleTabKeydown)
+ tab.addEventListener('click', handleTabClick)
+ sidebarList.appendChild(el('li', {}, [tab]))
+
+ const nodeList = el('ul', {class: 'full-list'})
+ nodeList.addEventListener('click', handleNodeListClick)
+
+ const tabpanel = el('div', {
+ id: tabpanelId,
+ class: 'sidebar-tabpanel',
+ role: 'tabpanel',
+ 'aria-labelledby': tabId,
+ hidden: selected ? undefined : ''
+ }, [nodeList])
+ document.getElementById('sidebar').appendChild(tabpanel)
+
+ let group = ''
+ let nestedContext
+ let lastModule
+ nodeList.replaceChildren(...nodes.flatMap(node => {
+ const items = []
+ const hasHeaders = Array.isArray(node.headers)
+ const translate = hasHeaders ? undefined : 'no'
+
+ // Group header.
+ if (node.group !== group) {
+ items.push(el('li', {class: 'group', translate}, [node.group]))
+ group = node.group
+ nestedContext = undefined
}
- if (anchor.matches('.expand') &&
- (anchor.pathname === window.location.pathname ||
- anchor.pathname === window.location.pathname + '.html')) {
- openListItem(listItem)
+ // Nesting context.
+ if (node.nested_context && node.nested_context !== nestedContext) {
+ nestedContext = node.nested_context
+ if (lastModule !== nestedContext) {
+ items.push(el('li', {class: 'nesting-context', translate: 'no', 'aria-hidden': true}, [nestedContext]))
+ }
+ } else {
+ lastModule = node.title
}
- })
+
+ items.push(el('li', {}, [
+ el('a', {href: `${node.id}.html`, translate}, [node.nested_title || node.title]),
+ ...childList(`node-${node.id}-headers`,
+ hasHeaders
+ ? renderHeaders(node)
+ : renderSectionsAndGroups(node)
+ )
+ ]))
+
+ return items
+ }))
})
+
+ window.addEventListener('hashchange', markCurrentHashInSidebar)
+ window.addEventListener('swup:page:view', markCurrentHashInSidebar)
+
+ markCurrentHashInSidebar()
+ // Triggers layout, defer.
+ requestAnimationFrame(scrollNodeListToCurrentCategory)
}
-function openListItem (listItem) {
- listItem.classList.add('open')
- listItem.querySelector('button[aria-controls]').setAttribute('aria-expanded', 'true')
+/**
+ * @param {string} id
+ * @param {HTMLElement[]} childItems
+ */
+function childList (id, childItems) {
+ if (!childItems.length) return []
+
+ return [
+ el('button', {'aria-label': 'expand', 'aria-expanded': false, 'aria-controls': id}),
+ el('ul', {id}, childItems)
+ ]
}
-function closeListItem (listItem) {
- listItem.classList.remove('open')
- listItem.querySelector('button[aria-controls]').setAttribute('aria-expanded', 'false')
+function renderHeaders (node) {
+ return node.headers
+ .map(({id, anchor}) =>
+ el('li', {}, [
+ el('a', {href: `${node.id}.html#${anchor}`}, [id])
+ ])
+ )
}
-function toggleListItem (listItem) {
- if (listItem.classList.contains('open')) {
- closeListItem(listItem)
- } else {
- openListItem(listItem)
+function renderSectionsAndGroups (node) {
+ const items = []
+
+ if (node.sections?.length) {
+ items.push(el('li', {}, [
+ el('a', {href: `${node.id}.html#content`}, ['Sections']),
+ ...childList(`${node.id}-sections-list`,
+ node.sections
+ .map(({id, anchor}) =>
+ el('li', {}, [
+ el('a', {href: `${node.id}.html#${anchor}`}, [id])
+ ])
+ )
+ )
+ ]))
}
-}
-function markElementAsCurrentSection (section) {
- section.classList.add('current-section')
- section.querySelector('a').setAttribute('aria-current', 'true')
-}
+ if (node.nodeGroups) {
+ items.push(el('li', {}, [
+ el('a', {href: `${node.id}.html#summary`}, ['Summary'])
+ ]))
+
+ items.push(...node.nodeGroups.map(({key, name, nodes}) =>
+ el('li', {}, [
+ el('a', {href: `${node.id}.html#${key}`}, [name]),
+ ...childList(`node-${node.id}-group-${key}-list`,
+ nodes
+ .map(({anchor, title, id}) =>
+ el('li', {}, [
+ el('a', {href: `${node.id}.html#${anchor}`, title, translate: 'no'}, [id])
+ ])
+ )
+ )
+ ])
+ ))
+ }
-function clearCurrentSectionElement (section) {
- section.classList.remove('current-section')
- section.querySelector('a').setAttribute('aria-current', 'false')
+ return items
}
-function markElementAsCurrentHash (listItem) {
- listItem.classList.add('current-hash')
- listItem.querySelector('a').setAttribute('aria-current', 'true')
-}
+/** @param {HTMLButtonElement} */
+function activateTab (next) {
+ const prev = document.getElementById('sidebar-list-nav').querySelector('[aria-selected]')
-function clearCurrentHashElement (listItem) {
- listItem.classList.remove('current-hash')
- listItem.querySelector('a').setAttribute('aria-current', 'false')
-}
+ if (prev === next) return
-function markActiveSidebarTab (activeType) {
- SIDEBAR_TAB_TYPES.forEach(type => {
- const button = qs(`#${type}-list-tab-button`)
- if (button) {
- const tabpanel = qs(`#${button.getAttribute('aria-controls')}`)
- if (type === activeType) {
- button.parentElement.classList.add('selected')
- button.setAttribute('aria-selected', 'true')
- button.setAttribute('tabindex', '0')
- tabpanel.removeAttribute('hidden')
- } else {
- button.parentElement.classList.remove('selected')
- button.setAttribute('aria-selected', 'false')
- button.setAttribute('tabindex', '-1')
- tabpanel.setAttribute('hidden', 'hidden')
- }
- }
- })
+ if (prev) {
+ prev.removeAttribute('aria-selected')
+ prev.setAttribute('tabindex', '-1')
+ document.getElementById(prev.getAttribute('aria-controls')).setAttribute('hidden', 'hidden')
+ }
+
+ next.setAttribute('aria-selected', 'true')
+ next.setAttribute('tabindex', '0')
+ document.getElementById(next.getAttribute('aria-controls')).removeAttribute('hidden')
}
function scrollNodeListToCurrentCategory () {
- const nodeList = qs(sidebarNodeListSelector(getCurrentPageSidebarType()))
- if (!nodeList) { return }
-
- const currentPage = nodeList.querySelector('li.current-page')
- if (currentPage) {
- currentPage.scrollIntoView()
- nodeList.scrollTop -= 40
- }
+ qs('#sidebar [role=tabpanel]:not([hidden]) a[aria-selected]')?.scrollIntoView()
}
function markCurrentHashInSidebar () {
- const hash = getLocationHash() || 'content'
+ const sidebar = document.getElementById('sidebar')
+ const {pathname, hash} = window.location
- const sidebarNodes = getSidebarNodes()
- const nodes = sidebarNodes[getCurrentPageSidebarType()] || []
- const category = findSidebarCategory(nodes, hash)
- const nodeList = qs(sidebarNodeListSelector(getCurrentPageSidebarType()))
- if (!nodeList) { return }
+ // All sidebar links are relative and end in .html.
+ const page = pathname.split('/').pop().replace(/\.html$/, '') + '.html'
- const categoryEl = nodeList.querySelector(`li.current-page a.expand[href$="#${category}"]`)
- if (categoryEl) {
- openListItem(categoryEl.closest('li'))
- }
+ // Try find exact link with hash, fall back to page.
+ const current = sidebar.querySelector(`li a[href="${page + hash}"]`) || sidebar.querySelector(`li a[href="${page}"]`)
- const hashEl = nodeList.querySelector(`li.current-page a[href$="#${hash}"]`)
- if (hashEl) {
- const deflist = hashEl.closest('ul')
- if (deflist.classList.contains('deflist')) {
- markElementAsCurrentSection(deflist.closest('li'))
- }
- markElementAsCurrentHash(hashEl.closest('li'))
- }
-}
+ if (!current) return
-function addEventListeners () {
- // Bind the navigation links ("Pages", "Modules", "Tasks")
- // so that they render a list of all relevant nodes when clicked.
- SIDEBAR_TAB_TYPES.forEach(type => {
- const button = qs(`#${type}-list-tab-button`)
- if (button) {
- button.addEventListener('click', event => {
- markActiveSidebarTab(type)
- scrollNodeListToCurrentCategory()
- })
- }
+ // Unset previous.
+ sidebar.querySelectorAll('li a[aria-selected]').forEach(element => {
+ element.removeAttribute('aria-selected')
})
- // provide left/right arrow navigation for tablist, as required by ARIA authoring practices guide
- const tabList = qs('#sidebar-list-nav')
- tabList.addEventListener('keydown', (e) => {
- if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') { return }
-
- // SIDEBAR_TAB_TYPES cannot be used here as it always contains all possible types, not only types that the specific project has
- const tabTypes = Array.from(tabList.querySelectorAll('[role="tab"]')).map(tab => tab.dataset.type)
- // getCurrentPageSidebarType() cannot be used here as it's assigned once, on page render
- const currentTabType = tabList.querySelector('[role="tab"][aria-selected="true"]').dataset.type
-
- if (e.key === 'ArrowRight') {
- let nextTabTypeIndex = tabTypes.indexOf(currentTabType) + 1
- if (nextTabTypeIndex >= tabTypes.length) {
- nextTabTypeIndex = 0
+ // Walk up parents, updating link, button and tab attributes.
+ let element = current.parentElement
+ while (element) {
+ if (element.tagName === 'LI') {
+ const link = element.firstChild
+ link.setAttribute('aria-selected', link.getAttribute('href') === page ? 'page' : 'true')
+ const button = link.nextSibling
+ if (button?.tagName === 'BUTTON') {
+ button.setAttribute('aria-expanded', true)
}
-
- const nextType = tabTypes[nextTabTypeIndex]
- markActiveSidebarTab(nextType)
- qs(`#${nextType}-list-tab-button`).focus()
- } else if (e.key === 'ArrowLeft') {
- let previousTabTypeIndex = tabTypes.indexOf(currentTabType) - 1
- if (previousTabTypeIndex < 0) {
- previousTabTypeIndex = tabTypes.length - 1
+ } else if (element.role === 'tabpanel') {
+ if (element.hasAttribute('hidden')) {
+ activateTab(document.getElementById(element.getAttribute('aria-labelledby')))
}
-
- const previousType = tabTypes[previousTabTypeIndex]
- markActiveSidebarTab(previousType)
- qs(`#${previousType}-list-tab-button`).focus()
+ break
}
- })
+ element = element.parentElement
+ }
+}
+
+/**
+ * Provide left/right arrow navigation for tablist, as required by ARIA authoring practices guide.
+ *
+ * @param {KeyboardEvent}
+ **/
+function handleTabKeydown (event) {
+ if (!['ArrowRight', 'ArrowLeft'].includes(event.key)) { return }
+
+ const tabs = Array.from(qsAll('#sidebar-list-nav [role="tab"]'))
+ const currentIndex = tabs.indexOf(event.currentTarget)
+ const nextIndex = currentIndex + (event.key === 'ArrowRight' ? 1 : -1)
+ const nextTab = tabs.at(nextIndex % tabs.length)
+
+ activateTab(nextTab)
+ nextTab.focus()
+}
+
+/** @param {MouseEvent} */
+function handleTabClick (event) {
+ activateTab(event.currentTarget)
+ scrollNodeListToCurrentCategory()
+}
- // Keep .current-hash item in sync with the hash, regardless how the change takes place
- window.addEventListener('hashchange', event => {
- const nodeList = qs(sidebarNodeListSelector(getCurrentPageSidebarType()))
- if (!nodeList) { return }
+/** @param {MouseEvent} */
+function handleNodeListClick (event) {
+ const target = event.target
- const currentListItem = nodeList.querySelector('li.current-page li.current-hash')
- if (currentListItem) {
- clearCurrentHashElement(currentListItem)
- }
- markCurrentHashInSidebar()
- })
+ if (target.tagName === 'BUTTON') {
+ target.setAttribute('aria-expanded', target.getAttribute('aria-expanded') === 'false')
+ }
}
diff --git a/assets/js/tabsets.js b/assets/js/tabsets.js
index fdbd22a36..082f08da9 100644
--- a/assets/js/tabsets.js
+++ b/assets/js/tabsets.js
@@ -1,3 +1,5 @@
+import { el } from './helpers'
+
const CONTENT_CONTAINER_ID = 'content'
const TABSET_OPEN_COMMENT = 'tabs-open'
const TABSET_CLOSE_COMMENT = 'tabs-close'
@@ -5,11 +7,7 @@ const TABPANEL_HEADING_NODENAME = 'H3'
const TABSET_CONTAINER_CLASS = 'tabset'
export function initialize () {
- // Done in read and mutate parts to avoid layout thrashing.
- // Reading inner text requires layout so we want to read
- // all headings before we start mutating the DOM.
-
- /** @type {[Node, [string, HTMLElement[]][]][]} */
+ /** @type {[Node, [NodeList, HTMLElement[]][]][]} */
const sets = []
/** @type {Node[]} */
const toRemove = []
@@ -36,7 +34,9 @@ export function initialize () {
if (node.nodeName === TABPANEL_HEADING_NODENAME) {
// Tab heading.
tabContent = []
- set.push([node.innerText, tabContent])
+ // Extract heading text nodes (faster than using .textContent which requires layout).
+ const headingContent = node.querySelector('.text')?.childNodes || node.childNodes
+ set.push([headingContent, tabContent])
toRemove.push(node)
} else if (node.nodeName === '#comment' && node.nodeValue.trim() === TABSET_CLOSE_COMMENT) {
// Closer comment.
@@ -49,7 +49,6 @@ export function initialize () {
}
}
- // Now we can mutate DOM.
sets.forEach(([opener, set], setIndex) => {
const tabset = el('div', {
class: TABSET_CONTAINER_CLASS
@@ -62,7 +61,7 @@ export function initialize () {
})
tabset.appendChild(tablist)
- set.forEach(([text, content], index) => {
+ set.forEach(([headingContent, content], index) => {
const selected = index === 0
const tabId = `tab-${setIndex}-${index}`
const tabPanelId = `tabpanel-${setIndex}-${index}`
@@ -74,8 +73,7 @@ export function initialize () {
tabindex: selected ? 0 : -1,
'aria-selected': selected,
'aria-controls': tabPanelId
- })
- tab.innerText = text
+ }, headingContent)
tab.addEventListener('click', handleTabClick)
tab.addEventListener('keydown', handleTabKeydown)
tablist.appendChild(tab)
@@ -87,8 +85,7 @@ export function initialize () {
hidden: !selected ? '' : undefined,
tabindex: selected ? 0 : -1,
'aria-labelledby': tabId
- })
- tabPanel.replaceChildren(...content)
+ }, content)
tabset.appendChild(tabPanel)
})
})
@@ -98,21 +95,6 @@ export function initialize () {
})
}
-/**
- * @param {string} tagName
- * @param {Record} attributes
- * @returns {HTMLElement}
- */
-function el (tagName, attributes) {
- const element = document.createElement(tagName)
- for (const key in attributes) {
- if (attributes[key] != null) {
- element.setAttribute(key, attributes[key])
- }
- }
- return element
-}
-
/** @param {MouseEvent} event */
function handleTabClick (event) {
activateTab(event.currentTarget)
diff --git a/assets/js/toast.js b/assets/js/toast.js
index 24b15a0db..cc4948be5 100644
--- a/assets/js/toast.js
+++ b/assets/js/toast.js
@@ -1,16 +1,17 @@
+let init = false
let toastTimer = null
let toast = null
-export function initialize () {
- toast = document.getElementById('toast')
-
- toast.addEventListener('click', (event) => {
- clearTimeout(toastTimer)
- event.target.classList.remove('show')
- })
-}
-
export function showToast (message) {
+ if (!init) {
+ init = true
+ toast = document.getElementById('toast')
+ toast?.addEventListener('click', () => {
+ clearTimeout(toastTimer)
+ toast.classList.remove('show')
+ })
+ }
+
if (toast) {
clearTimeout(toastTimer)
toast.innerText = message
diff --git a/assets/js/tooltips/tooltips.js b/assets/js/tooltips/tooltips.js
index 3652d3ec7..6657432f4 100644
--- a/assets/js/tooltips/tooltips.js
+++ b/assets/js/tooltips/tooltips.js
@@ -40,8 +40,6 @@ const state = {
* Initializes tooltips handling.
*/
export function initialize () {
- qs(CONTENT_INNER_SELECTOR).insertAdjacentHTML('beforeend', TOOLTIP_HTML)
-
qsAll(TOOLTIP_ACTIVATORS_SELECTOR).forEach(element => {
if (!linkElementEligibleForTooltip(element)) { return }
@@ -99,7 +97,12 @@ function renderTooltip (hint) {
hint
})
- qs(TOOLTIP_BODY_SELECTOR).innerHTML = tooltipBodyHtml
+ let tooltipBody = qs(TOOLTIP_BODY_SELECTOR)
+ if (!tooltipBody) {
+ qs(CONTENT_INNER_SELECTOR).insertAdjacentHTML('beforeend', TOOLTIP_HTML)
+ tooltipBody = qs(TOOLTIP_BODY_SELECTOR)
+ }
+ tooltipBody.innerHTML = tooltipBodyHtml
updateTooltipPosition()
@@ -113,7 +116,7 @@ function handleHoverEnd () {
clearTimeout(state.hoverDelayTimeout)
cancelHintFetchingIfAny()
state.currentLinkElement = null
- qs(TOOLTIP_SELECTOR).classList.remove(TOOLTIP_SHOWN_CLASS)
+ qs(TOOLTIP_SELECTOR)?.classList.remove(TOOLTIP_SHOWN_CLASS)
}
/**
diff --git a/assets/test/helpers.spec.js b/assets/test/helpers.spec.js
index 2170f509f..ff70f223e 100644
--- a/assets/test/helpers.spec.js
+++ b/assets/test/helpers.spec.js
@@ -1,4 +1,4 @@
-import { escapeRegexModifiers, findSidebarCategory } from '../js/helpers'
+import { escapeRegexModifiers } from '../js/helpers'
describe('helpers', () => {
describe('escapeRegexModifiers', () => {
@@ -6,23 +6,4 @@ describe('helpers', () => {
expect(escapeRegexModifiers('hello-world')).to.be.equal('hello\\-world')
})
})
-
- describe('findSidebarCategory', () => {
- it('finds the correct category', () => {
- const nodes = [{
- nodeGroups: [
- {key: 'callbacks', nodes: [{anchor: 'hello'}]},
- {key: 'functions', nodes: [{anchor: 'world'}]}
- ]
- }, {
- nodeGroups: [
- {key: 'callbacks', nodes: [{anchor: 'one'}]},
- {key: 'examples', nodes: [{anchor: 'two'}]}
- ]
- }]
-
- expect(findSidebarCategory(nodes, 'world')).to.be.eql('functions')
- expect(findSidebarCategory(nodes, 'something')).to.be.eql(null)
- })
- })
})
diff --git a/lib/ex_doc/formatter/html.ex b/lib/ex_doc/formatter/html.ex
index 1f07edef0..4f9d56d6c 100644
--- a/lib/ex_doc/formatter/html.ex
+++ b/lib/ex_doc/formatter/html.ex
@@ -42,12 +42,12 @@ defmodule ExDoc.Formatter.HTML do
search_data ++
static_files ++
generate_sidebar_items(nodes_map, extras, config) ++
- generate_extras(nodes_map, extras, config) ++
+ generate_extras(extras, config) ++
generate_logo(@assets_dir, config) ++
- generate_search(nodes_map, config) ++
- generate_not_found(nodes_map, config) ++
- generate_list(nodes_map.modules, nodes_map, config) ++
- generate_list(nodes_map.tasks, nodes_map, config) ++
+ generate_search(config) ++
+ generate_not_found(config) ++
+ generate_list(nodes_map.modules, config) ++
+ generate_list(nodes_map.tasks, config) ++
generate_redirects(config, ".html")
generate_build(Enum.sort(all_files), build)
@@ -166,18 +166,18 @@ defmodule ExDoc.Formatter.HTML do
File.write!(build, entries)
end
- defp generate_not_found(nodes_map, config) do
+ defp generate_not_found(config) do
filename = "404.html"
config = set_canonical_url(config, filename)
- content = Templates.not_found_template(config, nodes_map)
+ content = Templates.not_found_template(config)
File.write!("#{config.output}/#{filename}", content)
[filename]
end
- defp generate_search(nodes_map, config) do
+ defp generate_search(config) do
filename = "search.html"
config = set_canonical_url(config, filename)
- content = Templates.search_template(config, nodes_map)
+ content = Templates.search_template(config)
File.write!("#{config.output}/#{filename}", content)
[filename]
end
@@ -203,7 +203,7 @@ defmodule ExDoc.Formatter.HTML do
|> binary_part(0, 8)
end
- defp generate_extras(nodes_map, extras, config) do
+ defp generate_extras(extras, config) do
generated_extras =
extras
|> with_prev_next()
@@ -218,7 +218,7 @@ defmodule ExDoc.Formatter.HTML do
}
extension = node.source_path && Path.extname(node.source_path)
- html = Templates.extra_template(config, node, extra_type(extension), nodes_map, refs)
+ html = Templates.extra_template(config, node, extra_type(extension), refs)
if File.regular?(output) do
Utils.warn("file #{Path.relative_to_cwd(output)} already exists", [])
@@ -530,16 +530,16 @@ defmodule ExDoc.Formatter.HTML do
Enum.filter(nodes, &(&1.type == type))
end
- defp generate_list(nodes, nodes_map, config) do
+ defp generate_list(nodes, config) do
nodes
- |> Task.async_stream(&generate_module_page(&1, nodes_map, config), timeout: :infinity)
+ |> Task.async_stream(&generate_module_page(&1, config), timeout: :infinity)
|> Enum.map(&elem(&1, 1))
end
- defp generate_module_page(module_node, nodes_map, config) do
+ defp generate_module_page(module_node, config) do
filename = "#{module_node.id}.html"
config = set_canonical_url(config, filename)
- content = Templates.module_page(module_node, nodes_map, config)
+ content = Templates.module_page(module_node, config)
File.write!("#{config.output}/#{filename}", content)
filename
end
diff --git a/lib/ex_doc/formatter/html/templates.ex b/lib/ex_doc/formatter/html/templates.ex
index 9e7d81da6..a4ab93a9a 100644
--- a/lib/ex_doc/formatter/html/templates.ex
+++ b/lib/ex_doc/formatter/html/templates.ex
@@ -15,9 +15,9 @@ defmodule ExDoc.Formatter.HTML.Templates do
@doc """
Generate content from the module template for a given `node`
"""
- def module_page(module_node, nodes_map, config) do
+ def module_page(module_node, config) do
summary = module_summary(module_node)
- module_template(config, module_node, summary, nodes_map)
+ module_template(config, module_node, summary)
end
@doc """
@@ -291,13 +291,13 @@ defmodule ExDoc.Formatter.HTML.Templates do
detail_template: [:node, :module],
footer_template: [:config, :node],
head_template: [:config, :title, :noindex],
- module_template: [:config, :module, :summary, :nodes_map],
- not_found_template: [:config, :nodes_map],
+ module_template: [:config, :module, :summary],
+ not_found_template: [:config],
api_reference_entry_template: [:module_node],
api_reference_template: [:nodes_map],
- extra_template: [:config, :node, :type, :nodes_map, :refs],
- search_template: [:config, :nodes_map],
- sidebar_template: [:config, :type, :nodes_map],
+ extra_template: [:config, :node, :type, :refs],
+ search_template: [:config],
+ sidebar_template: [:config, :type],
summary_template: [:name, :nodes],
redirect_template: [:config, :redirect_to]
]
diff --git a/lib/ex_doc/formatter/html/templates/extra_template.eex b/lib/ex_doc/formatter/html/templates/extra_template.eex
index 1b835f034..43f9a287c 100644
--- a/lib/ex_doc/formatter/html/templates/extra_template.eex
+++ b/lib/ex_doc/formatter/html/templates/extra_template.eex
@@ -1,5 +1,5 @@
<%= head_template(config, node.title, false) %>
-<%= sidebar_template(config, type, nodes_map) %>
+<%= sidebar_template(config, type) %>
diff --git a/lib/ex_doc/formatter/html/templates/module_template.eex b/lib/ex_doc/formatter/html/templates/module_template.eex
index 3c2394e37..e14af45bf 100644
--- a/lib/ex_doc/formatter/html/templates/module_template.eex
+++ b/lib/ex_doc/formatter/html/templates/module_template.eex
@@ -1,5 +1,5 @@
<%= head_template(config, module.title, false) %>
-<%= sidebar_template(config, module.type, nodes_map) %>
+<%= sidebar_template(config, module.type) %>
diff --git a/lib/ex_doc/formatter/html/templates/not_found_template.eex b/lib/ex_doc/formatter/html/templates/not_found_template.eex
index e16bc2de1..d4503d1f6 100644
--- a/lib/ex_doc/formatter/html/templates/not_found_template.eex
+++ b/lib/ex_doc/formatter/html/templates/not_found_template.eex
@@ -1,5 +1,5 @@
<%= head_template(config, "404", true) %>
-<%= sidebar_template(config, :extra, nodes_map) %>
+<%= sidebar_template(config, :extra) %>
Page not found
diff --git a/lib/ex_doc/formatter/html/templates/search_template.eex b/lib/ex_doc/formatter/html/templates/search_template.eex
index 3f471adf2..732b3b5fd 100644
--- a/lib/ex_doc/formatter/html/templates/search_template.eex
+++ b/lib/ex_doc/formatter/html/templates/search_template.eex
@@ -1,5 +1,5 @@
<%= head_template(config, "Search", true) %>
-<%= sidebar_template(config, :search, nodes_map) %>
+<%= sidebar_template(config, :search) %>
diff --git a/lib/ex_doc/formatter/html/templates/sidebar_template.eex b/lib/ex_doc/formatter/html/templates/sidebar_template.eex
index 62c8aa111..999e0574b 100644
--- a/lib/ex_doc/formatter/html/templates/sidebar_template.eex
+++ b/lib/ex_doc/formatter/html/templates/sidebar_template.eex
@@ -22,46 +22,8 @@
-
+
-
-
-
- <%= if nodes_map.modules != [] do %>
-
- <% end %>
-
- <%= if nodes_map.tasks != [] do %>
-
- <% end %>
diff --git a/test/ex_doc/formatter/html/templates_test.exs b/test/ex_doc/formatter/html/templates_test.exs
index 970e8431a..7cc749bee 100644
--- a/test/ex_doc/formatter/html/templates_test.exs
+++ b/test/ex_doc/formatter/html/templates_test.exs
@@ -6,8 +6,6 @@ defmodule ExDoc.Formatter.HTML.TemplatesTest do
@moduletag :tmp_dir
- @empty_nodes_map %{modules: [], exceptions: [], protocols: [], tasks: []}
-
defp source_url do
"https://github.com/elixir-lang/elixir"
end
@@ -33,7 +31,7 @@ defmodule ExDoc.Formatter.HTML.TemplatesTest do
config = doc_config(context, config)
{mods, []} = ExDoc.Retriever.docs_from_modules(names, config)
[mod | _] = HTML.render_all(mods, [], ".html", config, [])
- Templates.module_page(mod, @empty_nodes_map, config)
+ Templates.module_page(mod, config)
end
setup %{tmp_dir: tmp_dir} do
@@ -202,7 +200,7 @@ defmodule ExDoc.Formatter.HTML.TemplatesTest do
describe "sidebar" do
test "text links to homepage_url when set", context do
- content = Templates.sidebar_template(doc_config(context), :extra, @empty_nodes_map)
+ content = Templates.sidebar_template(doc_config(context), :extra)
assert content =~
~r"""
@@ -221,7 +219,7 @@ defmodule ExDoc.Formatter.HTML.TemplatesTest do
main: "hello"
}
- content = Templates.sidebar_template(config, :extra, @empty_nodes_map)
+ content = Templates.sidebar_template(config, :extra)
assert content =~
~r"""
@@ -233,27 +231,6 @@ defmodule ExDoc.Formatter.HTML.TemplatesTest do
"""
end
- test "enables nav link when module type have at least one element", context do
- names = [CompiledWithDocs, CompiledWithDocs.Nested]
- modules = ExDoc.Retriever.docs_from_modules(names, doc_config(context))
-
- content =
- Templates.sidebar_template(doc_config(context), :extra, %{
- modules: modules,
- exceptions: [],
- tasks: []
- })
-
- assert content =~
- ~r{[\s\n]*[\s\n]* }
-
- assert content =~
- ~r{ }
-
- refute content =~ ~r{id="tasks-list-tab-button"}
- refute content =~ ~r{id="tasks-full-list"}
- end
-
test "display built with footer by proglang option", context do
content = Templates.footer_template(doc_config(context, proglang: :erlang), nil)