Skip to main content

misc.js

Utility modules and UI components: inject triggers, image handling, clipboard, toolbar, floating header, and miscellaneous helpers

Pathjs/misc.js
LanguageJavaScript
Lines2737
Sourcejs/misc.js
at 406d3e423

Read this when Use this page when tracing browser-side behavior, runtime state, event handling, popups, or UI code around misc.

Overview​

misc.js is a collection of standalone utility modules and UI components that don't fit elsewhere. Despite its name, it contains substantial functionality: element injection triggers, asset versioning, image thumbnail/inversion/outlining helpers, clipboard processing, the page toolbar (gear icon with collapsible settings), the floating header (breadcrumb navigation), pop-frame spawn widgets (help/search), keyboard command handling, and various DOM/document utilities.

The file follows a modular organization, with each feature in its own section. Many features integrate with the notification center (GW.notificationCenter) for loose coupling. The page toolbar and floating header are the most complex subsystems, providing the site's primary UI controls on both desktop and mobile.

Key design decisions include: lazy SVG icon loading with placeholders, an external API for image inversion/outlining decisions (invertornot.com), a sophisticated toolbar demo system (auto-collapses after showing new users the controls), and a widget system for spawning help/search popups from toolbar buttons or keyboard shortcuts.


Public API​

Inject Triggers​

onInject(uuid, f) → uuid​

Registers a function to run when an element with matching data-uuid is injected into the DOM.

let uuid = onInject(null, (element) => {
element.classList.add("processed");
});
// Returns auto-generated UUID if null passed

Called by: placeholder(), internal systems Calls: MutationObserver system


placeholder(replaceFunction, wrapperFunction) → string​

Returns HTML for a placeholder <span> that self-replaces when injected.

let html = placeholder(
(element) => elementFromHTML("<div>Replacement</div>"),
doWhenSVGIconsLoaded // Optional delay wrapper
);

Called by: GW.svg() (for deferred icon rendering)


Asset Management​

versionedAssetURL(pathname) → URL​

Returns a fully-qualified URL with version query parameter for cache busting.

versionedAssetURL("/static/img/icon/icons.svg")
// → URL object: ".../icons.svg?v=abc123"

Called by: Icon loading, dropcap system, special occasions


getAssetPathname(pattern, options) → string|null​

Returns an asset pathname matching a regex pattern with %R as number placeholder.

getAssetPathname("/static/img/logo/logo-%R.svg", {
sequenceIndex: 1, // Get specific index
sequenceCurrent: null // Or use "next"/"previous" with current path
});

Called by: Dropcap system, special occasion logos


GW.svg(icon) → string​

Returns SVG markup for a named icon from the loaded icon file.

GW.svg("gear-solid")  // Returns <svg>...</svg> string
// Returns placeholder if icons not yet loaded

Called by: Page toolbar, floating header, all UI components


Images​

Images.thumbnailURLForImage(image, size) → URL|null​

Returns thumbnail URL for an image, or null if SVG or external.


Images.thumbnailifyImage(image) → void​

Replaces image src with thumbnail URL, saving original in data-src-size-full.


Images.unthumbnailifyImage(image) → void​

Restores original full-size image src.


requestImageInversionJudgmentsForImagesInContainer(container) → void​

Sends images to invertornot.com API to determine if they should be inverted in dark mode.

Fires: GW.imageInversionJudgmentsAvailable


applyImageInversionJudgment(image) → boolean|null​

Applies .invert-auto or .invert-not-auto class based on API response.


Clipboard​

copyTextToClipboard(text) → void​

Copies text to clipboard using a hidden scratchpad element.


addCopyProcessor(processor) → void​

Registers a function to process clipboard content on copy events.

addCopyProcessor((event, selection) => {
// Modify selection DocumentFragment
return true; // Continue to next processor
});

registerCopyProcessorsForDocument(doc) → void​

Sets up copy event handling for a document (main or shadow root).


Notes & Citations​

Notes.allNotesForCitation(citation) → Element[]​

Returns all footnote/sidenote elements associated with a citation link.


Notes.noteNumberFromHash(hash) → string​

Extracts note number from URL hash (#fn3 → "3", #fnref3 → "3").


Table of Contents​

updatePageTOC(container) → void​

Updates main TOC with any new sections from transcluded content.

Called by: transclude.js, rewrite handlers


Page Toolbar​

GW.pageToolbar.addWidget(widget) → Element​

Adds a widget element to the toolbar.

GW.pageToolbar.addWidget(`<div id="my-widget">...</div>`);

GW.pageToolbar.removeWidget(widgetID) → Element|null​

Removes and returns the widget with given ID.


GW.pageToolbar.toggleCollapseState(collapse, options) → void​

Collapses or expands the toolbar.

GW.pageToolbar.toggleCollapseState(true, {
delay: 500, // Delay before action
temp: false, // Temporary expand (re-collapse on unhover)
slow: false // Slow collapse animation
});

GW.pageToolbar.flashWidget(widgetID, options) → void​

Flashes a widget to draw attention.


Floating Header​

GW.floatingHeader.updateState(event, maxChainLength) → void​

Updates header visibility and breadcrumb chain based on scroll position.

Called by: Scroll listener


Pop-Frame Spawn Widgets​

GW.popFrameSpawnWidgets.addWidgetType(name, spec) → object​

Registers a new widget type for toolbar/inline use.

GW.popFrameSpawnWidgets.addWidgetType("help", {
linkHref: "/help",
iconName: "question-solid",
addToolbarWidget: true,
toolbarWidgetLabel: "Help",
keyCommand: "?"
});

Miscellaneous Helpers​

scrollContainerOf(element) → Element|null​

Returns the scroll container for an element (pop-frame scroll view, or null for viewport).


getPageScrollPosition() → number​

Returns scroll position as integer percentage (0-100).


getSavedCount(key) → number / incrementSavedCount(key) / resetSavedCount(key)​

Local storage helpers for tracking counts (e.g., toolbar demo views).


addUIElement(element, options) → Element​

Adds element to #ui-elements-container.


Internal Architecture​

Inject Trigger System​

GW.elementInjectTriggers = { uuid: callback, ... }
GW.defunctElementInjectTriggers = { uuid: callback, ... }

MutationObserver watches document
→ On added node with data-uuid
→ Look up callback in elementInjectTriggers
→ Move to defunctElementInjectTriggers
→ Execute callback(element)

The system enables deferred rendering—SVG icons return placeholder spans that self-replace once the icon file loads.


Page Toolbar State Machine​

States:
- expanded (default for first N visits)
- collapsed
- expanded-temp (on hover, will re-collapse)
- faded (while scrolling down)

Transitions:
- Page load → Demo flash sequence → Auto-collapse (slow)
- Scroll up → Unfade
- Scroll down → Fade
- Hover toggle button → Expand temp
- Unhover → Collapse (if temp)
- Click toggle → Toggle collapsed/expanded
- Click while temp-expanded → Lock expanded

Demo count tracked in localStorage; after maxDemos (1), toolbar starts collapsed.


Floating Header​

On mobile: positioned at bottom, shows breadcrumb trail to current section. On desktop: positioned at top, shows nested section links.

getTrail(lookAhead) → ["header", "#section1", "#section1.1"]

constructLinkChain(trail, maxLength) → [<a href="#top">, <a href="#section1">, ...]

If trail too long:
- Splice with "…" ellipsis link
- On mobile: also set truncate-page-title class

Widget Type Registration​

widgetTypes = {
help: { linkHref, iconName, addToolbarWidget, keyCommand, ... },
search: { ..., additionalSetup, additionalWidgetActivation, ... }
}

setup(widgetType):
- If addToolbarWidget: add to GW.pageToolbar
- If inlineWidgetReplacedElementSelector: replace matching elements
- If keyCommand: register keyboard handler
- Run additionalSetup if provided

Key Patterns

Placeholder-Based Deferred Rendering​

GW.svg = (icon) => {
if (GW.svgIconFile == null)
return placeholder(
element => elementFromHTML(GW.svg(icon)),
doWhenSVGIconsLoaded
);
// ... return actual SVG
};

When icons aren't loaded yet, returns a placeholder that auto-replaces. The wrapper function (doWhenSVGIconsLoaded) delays replacement until icons are available.


Asset Sequencing​

For assets with variants (dropcaps, logos), supports:

  • Random selection (default)
  • Sequential access (next/previous)
  • Persistent index via localStorage
processAssetSequenceOptions(options, { assetSavedIndexKey }) → {
sequenceIndex: number|string,
sequenceCurrent: string
}

Conditional Event Handlers​

GW.notificationCenter.addHandlerForEvent("Popups.popupDidSpawn", handler, {
once: true,
condition: (info) => (info.popup.spawningTarget == widget.widgetLink)
});

Pattern used throughout for targeted one-shot event handling.


Configuration

Page Toolbar Timing​

GW.pageToolbar = {
maxDemos: 1,
hoverUncollapseDelay: 400,
unhoverCollapseDelay: 2500,
demoCollapseDelay: 9000,
collapseDuration: 250, // Synced with CSS
demoCollapseDuration: 1000, // Synced with CSS
widgetFlashRiseDuration: 1000,
widgetFlashFallDuration: 1000,
widgetFlashStayDuration: 500
};

Floating Header​

GW.floatingHeader = {
minimumYOffset: 0, // Calculated from page header height
maxChainLength: 6, // Max breadcrumb links
maxHeaderHeight: 60 // Mobile only, triggers chain truncation
};

Margin Notes​

GW.marginNotes = {
minimumAggregatedNotesCount: 3 // Hide block if fewer notes
};

Image APIs​

GW.invertOrNotAPIEndpoint = "https://invertornot.com/api/url";
GW.outlineOrNotAPIEndpoint = "https://api.obormot.net/outlineornot/url";

Integration Points​

Events Fired​

EventWhenPayload
GW.SVGIconsLoadedIcon file fetched—
GW.imageInversionJudgmentsAvailableAPI response received{ judgments }
GW.imageOutliningJudgmentsAvailableAPI response received{ judgments }
GW.pageToolbarCollapseStateDidChangeToolbar expand/collapse{ collapse, collapseOptions }
GW.keyWasPressedKeyboard command{ key, altKey, ... }
GW.hashDidChangeURL hash changed{ oldHash }
GW.hashHandlingSetupDidCompleteHash system ready—

Events Listened​

EventHandler
Popups.popupDidSpawnWidget popup pinning
Popups.popupWillDespawnSearch popup cleanup
Popovers.popoverDidInject / Popovers.popoverWillDespawnSearch widget setup/cleanup (popover mode)
Popovers.didLoadSet popover bottom position margin for mobile floating header
GW.keyWasPressedPop-frame spawn widget key commands

Note: the DarkMode.computedModeDidChange handler for dropcap images is registered in rewrite.js; misc.js only removes it (in resetDropcapInBlock()).


Shared State​

  • GW.svgIconFile — Parsed SVG icon document
  • GW.assetVersions — Asset path → version hash map
  • GW.invertOrNot / GW.outlineOrNot — Image URL → judgment cache
  • GW.scrollState — Current scroll position/direction (from initial.js)
  • GW.activities — Active operation stack
  • GW.TOC.mainTOC — Cached TOC element reference
  • Notes.notesForCitations — Citation → notes cache

See Also
  • initial.js - GW namespace, notification center, and scroll state
  • rewrite.js - DOM rewrite handlers (TOC updates, dropcaps)
  • utility.js - Core DOM helpers used throughout misc.js
  • popups.js - Popup windowing system (toolbar spawns popups)
  • extracts.js - Pop-frame coordinator (widget links are extract targets)
  • dark-mode.js - Dark mode system (image inversion integration)
  • console.js - Developer console utility module