Skip to content

Refactor module init #2015

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion assets/js/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { settingsStore } from './settings-store'
* Updates "Run in Livebook" badges to link to a notebook
* corresponding to the current documentation page.
*/
export function initialize () {

window.addEventListener('swup:page:view', initialize)
initialize()

function initialize () {
const notebookPath = window.location.pathname.replace(/(\.html)?$/, '.livemd')
const notebookUrl = encodeURIComponent(new URL(notebookPath, window.location.href).toString())

Expand Down
6 changes: 5 additions & 1 deletion assets/js/copy-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ let buttonTemplate
/**
* Initializes copy buttons.
*/
export function initialize () {

window.addEventListener('swup:page:view', initialize)
initialize()

function initialize () {
if (!('clipboard' in navigator)) return

qsAll('pre:has(> code:first-child):not(:has(.copy-button))').forEach(pre => {
Expand Down
96 changes: 19 additions & 77 deletions assets/js/entry/html.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,19 @@
import { onDocumentReady } from '../helpers'
import { initialize as initTabsets } from '../tabsets'
import { initialize as initContent } from '../content'
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 initKeyboardShortcuts } from '../keyboard-shortcuts'
import { initialize as initQuickSwitch } from '../quick-switch'
import { initialize as initTooltips } from '../tooltips/tooltips'
import { initialize as initHintsPage } from '../tooltips/hint-page'
import { initialize as initCopyButton } from '../copy-button'
import { initialize as initSettings } from '../settings'
import { initialize as initPreview} from '../preview'

import Swup from 'swup'
import SwupA11yPlugin from '@swup/a11y-plugin'
import SwupProgressPlugin from '@swup/progress-plugin'

onDocumentReady(() => {
const params = new URLSearchParams(window.location.search)
const isEmbedded = window.self !== window.parent
const isPreview = params.has('preview')
const isHint = params.has('hint')

initTheme()

initTabsets()
initContent()
initMakeup()
initTooltips()
initCopyButton()

if (isPreview && isEmbedded) {
initPreview()
} if (isHint && isEmbedded) {
initHintsPage()
} else {
if (window.location.protocol !== 'file:') {
new Swup({
animationSelector: false,
containers: ['#main'],
ignoreVisit: (url) => {
const path = url.split('#')[0]
return path === window.location.pathname ||
path === window.location.pathname + '.html'
},
hooks: {
'page:view': () => {
initTabsets()
initContent()
initMakeup()
initTooltips()
initCopyButton()

initSearch()
initSearchPage()
initSettings()
}
},
linkSelector: 'a[href]:not([href^="/"]):not([href^="http"])',
plugins: [new SwupA11yPlugin(), new SwupProgressPlugin({delay: 500})]
})
}

initVersions()
initKeyboardShortcuts()
initQuickSwitch()

initSidebarDrawer()
initSearch()
initSearchPage()
initSettings()
}
})
// Load preview & hint-page first because they could remove DOM.
// This prevents later modules doing unnecessary work.
import '../preview'
import '../tooltips/hint-page'
// The remaining modules are loaded in order of visible impact.
import '../theme'
import '../sidebar/sidebar-drawer'
import '../sidebar/sidebar-version-select'
import '../tabsets'
import '../content'
import '../makeup'
import '../search-bar'
import '../tooltips/tooltips'
import '../copy-button'
import '../search-page'
import '../settings'
import '../keyboard-shortcuts'
import '../quick-switch'
import '../swup'
8 changes: 8 additions & 0 deletions assets/js/globals.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
const params = new URLSearchParams(window.location.search)

export const isEmbedded = window.self !== window.parent

export const isPreview = params.has('preview')

export const isHint = params.has('hint')

// These variables are set by other scripts (e.g. generated by the docs task).

export function getSidebarNodes () {
Expand Down
54 changes: 21 additions & 33 deletions assets/js/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@ export function getCurrentPageSidebarType () {
return document.getElementById('main').dataset.type
}

const headingTagNames = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']

/**
* Finds an element by a URL hash (e.g. a function section).
*
* @param {String} hash The hash part of a URL.
* @param {Boolean} anything Whether or not to support any link to any header.
* @returns {HTMLElement|null} The relevant element.
*/
export function descriptionElementFromHash (hash, anything = false) {
export function descriptionElementFromHash (anything = false) {
const hash = window.location.hash.replace(/^#/, '')

if (!hash) {
if (!anything) {
return null
Expand All @@ -71,40 +74,25 @@ export function descriptionElementFromHash (hash, anything = false) {
}

// Matches a subheader in particular
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName.toLowerCase())) {
return toNextHeader(element)
}

return null
}

function toNextHeader (element) {
const elements = [element]
let nextElement = element.nextElementSibling
const tagName = element.tagName.toLowerCase()

while (nextElement) {
const nextElementTagName = nextElement.tagName.toLowerCase()
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(nextElementTagName) && nextElementTagName <= tagName) {
nextElement = null
} else {
elements.push(nextElement)
nextElement = nextElement.nextElementSibling
if (headingTagNames.includes(element.tagName)) {
const div = document.createElement('div')
const nodes = [element]

// Capture all nodes under the current heading level.
let node = element
while ((node = node.nextSibling)) {
if (headingTagNames.includes(node.tagName) && node.tagName <= element.tagName) {
break
} else {
nodes.push(node)
}
}
}

const div = document.createElement('div')
div.append(...elements)
return div
}
div.append(...nodes)
return div
}

/**
* Returns current location hash without the leading hash character.
*
* @returns {String}
*/
export function getLocationHash () {
return window.location.hash.replace(/^#/, '')
return null
}

/**
Expand Down
6 changes: 3 additions & 3 deletions assets/js/keyboard-shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { cycleTheme } from './theme'
import { openQuickSwitchModal } from './quick-switch'
import { closeModal, isModalOpen } from './modal'
import { openSettingsModal } from './settings'
import { isEmbedded } from './globals'

const HELP_MODAL_BODY_SELECTOR = '#settings-modal-content'

Expand Down Expand Up @@ -60,10 +61,9 @@ const state = {
}

/**
* Registers keyboard shortcuts and sets up a help modal
* listing all available options.
* Registers keyboard shortcuts.
*/
export function initialize () {
if (!isEmbedded) {
document.addEventListener('keydown', handleKeyDown)
document.addEventListener('keyup', handleKeyUp)
}
Expand Down
4 changes: 4 additions & 0 deletions assets/js/makeup.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const HIGHLIGHT_CLASS = 'hll'
/**
* Sets up dynamic behaviour for code blocks processed with *makeup*.
*/

window.addEventListener('swup:page:view', initialize)
initialize()

export function initialize () {
// Hovering over a delimiter (bracket, parenthesis, do/end)
// highlights the relevant pair of delimiters.
Expand Down
56 changes: 19 additions & 37 deletions assets/js/preview.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,32 @@
import { getLocationHash, descriptionElementFromHash } from './helpers'
import { isEmbedded, isPreview } from './globals'
import { descriptionElementFromHash } from './helpers'

export function initialize () {
const previewing = descriptionElementFromHash(getLocationHash(), true)
if (isPreview && isEmbedded) {
const previewing = descriptionElementFromHash(true)

if (previewing) {
preview(previewing)
}
}
document.body.classList.add('preview')
document.getElementById('content').replaceChildren(...previewing.childNodes)

function preview (previewing) {
replaceContents(previewing)
makeLinksOpenInParent()
scrollToTop()
sendPreviewInfoToParent()
// Make links open in parent.
const links = document.getElementsByTagName('a:not([target=_blank]')
for (const element of links) {
element.setAttribute('target', '_parent')
}

window.addEventListener('resize', event => {
sendPreviewInfoToParent()
})
window.scrollTo(0, 0)
// Stop iframe scrolling affecting parent by setting body position to fixed.
document.body.style.position = 'fixed'
// Defer preview message until all other scripts have run.
setTimeout(sendPreviewInfoToParent)
window.addEventListener('resize', sendPreviewInfoToParent)
}
}

function sendPreviewInfoToParent () {
const maxHeight = document.body.scrollHeight
const contentHeight = document.getElementById('content').parentElement.offsetHeight
const message = {
type: 'preview',
maxHeight,
contentHeight
contentHeight: document.getElementById('content').parentElement.offsetHeight
}
window.parent.postMessage(message, '*')
}

function makeLinksOpenInParent () {
const links = document.getElementsByTagName('a')
for (const element of links) {
if (element.getAttribute('target') !== '_blank') {
element.setAttribute('target', '_parent')
}
}
}

function scrollToTop () {
window.scrollTo(0, 0)
}

function replaceContents (previewing) {
document.body.classList.add('preview')
const content = document.getElementById('content')
content.innerHTML = previewing.innerHTML
}
9 changes: 8 additions & 1 deletion assets/js/quick-switch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { debounce, el, qs, qsAll } from './helpers'
import { openModal } from './modal'
import quickSwitchModalBodyHtml from './handlebars/templates/quick-switch-modal-body.html'
import { isEmbedded } from './globals'

const HEX_DOCS_ENDPOINT = 'https://hexdocs.pm/%%'
const OTP_DOCS_ENDPOINT = 'https://www.erlang.org/doc/apps/%%'
Expand Down Expand Up @@ -71,7 +72,13 @@ const state = {
/**
* Initializes the quick switch modal.
*/
export function initialize () {

if (!isEmbedded) {
window.addEventListener('swup:page:view', initialize)
initialize()
}

function initialize () {
qsAll(QUICK_SWITCH_LINK_SELECTOR).forEach(element => {
element.addEventListener('click', openQuickSwitchModal)
})
Expand Down
9 changes: 8 additions & 1 deletion assets/js/search-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
AUTOCOMPLETE_CONTAINER_SELECTOR,
AUTOCOMPLETE_SUGGESTION_LIST_SELECTOR
} from './autocomplete/autocomplete-list'
import { isEmbedded } from './globals'
import { isAppleOS, qs } from './helpers'

const SEARCH_INPUT_SELECTOR = 'form.search-bar input'
Expand All @@ -18,7 +19,13 @@ const SEARCH_CLOSE_BUTTON_SELECTOR = 'form.search-bar .search-close-button'
/**
* Initializes the sidebar search box.
*/
export function initialize () {

if (!isEmbedded) {
window.addEventListener('swup:page:view', initialize)
initialize()
}

function initialize () {
addEventListeners()

window.onTogglePreviewClick = function (event, open) {
Expand Down
6 changes: 5 additions & 1 deletion assets/js/search-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ lunr.Pipeline.registerFunction(docTrimmerFunction, 'docTrimmer')
*
* Activates only on the `/search.html` page.
*/
export function initialize () {

window.addEventListener('swup:page:view', initialize)
initialize()

function initialize () {
const pathname = window.location.pathname
if (pathname.endsWith('/search.html') || pathname.endsWith('/search')) {
const query = getQueryParamByName('q')
Expand Down
12 changes: 5 additions & 7 deletions assets/js/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ const modalTabs = [
/**
* Sets up the settings modal.
*/
export function initialize () {
addEventListeners()
}

function addEventListeners () {
window.addEventListener('swup:page:view', initialize)
initialize()

function initialize () {
qsAll(SETTINGS_LINK_SELECTOR).forEach(element => {
element.addEventListener('click', event => {
openSettingsModal()
})
element.addEventListener('click', openSettingsModal)
})
}

Expand Down
Loading