rewrite.js
Path: js/rewrite.js | Language: JavaScript | Lines: ~4,536
DOM transformation pipeline—registers dozens of handlers that run on content load/inject to enhance raw HTML
Overview
rewrite.js is the largest JavaScript file in gwern.net and serves as the central DOM transformation pipeline. It contains approximately 90+ handlers that register themselves via addContentLoadHandler() and addContentInjectHandler() from initial.js. When content is loaded (from the server, via transclusion, or from annotations), these handlers fire in sequence to transform raw HTML into the enhanced gwern.net reading experience.
The handlers are organized around content categories: images and figures, tables, code blocks, footnotes, links, lists, blockquotes, poetry, annotations, typography, search, ID-based loading (/ref/ system), aux-links/backlinks, and more. Each handler typically queries for specific selectors within the injected container, then applies transformations—wrapping elements, adding classes, setting CSS properties, binding event listeners, or restructuring the DOM.
This architecture separates concerns cleanly: content.js and transclude.js handle fetching and templating content, while rewrite.js focuses purely on transformation. The notification center's phase system ensures handlers run in the correct order (e.g., structural changes before event listener attachment).
Handler Registration System
Handlers are registered via two convenience functions defined in initial.js:
addContentLoadHandler(name, handler, phase, condition)
Registers a handler for GW.contentDidLoad events. These fire when content is loaded from any source but before injection into the document.
addContentInjectHandler(name, handler, phase, condition)
Registers a handler for GW.contentDidInject events. These fire after content has been injected into a document (main page or pop-frame).
Parameters:
name— String identifier for the handler (e.g.,"wrapImages")handler— Function receiving aneventInfoobject with:container— The DOM element containing the loaded/injected contentdocument— The document (main or pop-frame) the content belongs tosource— String identifying the content origin (e.g., "transclude", "Annotation.load")contentType— String like "annotation", "localPage", "tweet", etc.loadLocation— URL of the loaded contentfullWidthPossible— Boolean flag for layout contextincludeLink— The include-link element that triggered transclusion (if applicable)
phase— Execution order hint (see Phase System below)condition— Optional predicate function for selective execution
Phase System
Handlers run in a controlled order based on their declared phase. The phase orders are defined by GW.notificationCenter.handlerPhaseOrders:
GW.contentDidLoad: transclude → rewrite
GW.contentDidInject: rewrite → eventListeners
"<phase" and ">phase" are before/after pseudo-phases for each named phase.
Within a phase, you can fine-tune order with prefixes:
"<rewrite"— Run before main rewrite phase">rewrite"— Run after main rewrite phase
// Runs before most rewrite handlers
addContentLoadHandler("myEarlyHandler", (eventInfo) => {
// ...
}, "<rewrite");
// Runs after most rewrite handlers
addContentInjectHandler("myLateHandler", (eventInfo) => {
// ...
}, ">rewrite");
The "eventListeners" phase is used for binding mouse/keyboard events—always done last since DOM structure must be finalized.
Handler Categories
Clipboard & Copy Processors (~100 lines)
registerCopyProcessorsForDocument()— Sets up copy processors in main document- Copy processors for:
- Soft hyphen removal (
Typography.processElement) - Citation joiner text display (
.cite-joiner) - Symbol normalization
- Date range metadata stripping
- Inflation adjuster formatting
- Math LaTeX source copying
- Mode selector button text representation
- Soft hyphen removal (
Search (~50 lines)
setUpSearchIframe— Configures search iframe with dark mode support, "search where" functionality, and form submit override
ID-Based Loading / /ref/ System (~300 lines)
loadReferencedIdentifier— Loads content based on URL path in/ref/namespace- Handles URL lookups via
id-to-urlmapping files - Supports prefix matching for both IDs and URLs
- Normalizes manual IDs (lowercase, reversed date fix)
- Injects helpful error messages and suggestions
- Handles URL lookups via
Aux-Links & Backlinks (~400 lines)
anonymizeLinksInBacklinkContextBlocks— Strips IDs from links in backlink contextsgetBacklinksBlockForSectionOrFootnote()— Creates/retrieves backlinks block for sections or footnotesupdateBacklinksCountDisplay()— Updates backlink count in displayaddWithinPageBacklinksToSectionBacklinksBlocks— Adds within-page section backlinksrectifyLocalizedBacklinkContextLinks— Converts "full context" to "context" for local backlinksinjectBacklinksLinkIntoLocalSectionPopFrame— Adds backlinks link to section popupsremoveAuxLinksListLabelsInAuxLinksSections— Removes redundant labels in aux-links sections
Lists (~150 lines)
designateListTypes— Sets ordered list type classes (decimal → upper-roman → lower-alpha cycle)orderedListType()/setOrderedListType()— Get/set OL typeunorderedListLevel()/setUnorderedListLevel()— Get/set UL nesting level (cycles 1-3)paragraphizeListTextNodes— Wraps text nodes in list items in<p>tagsrectifyListHeadings— Fixes styling of<p><strong>:</strong></p>patterns
Blockquotes (~50 lines)
designateBlockquoteLevels— Sets blockquote level classes (cycles 1-6)blockquoteLevel()/setBlockquoteLevel()— Get/set blockquote nesting level
Tables (~100 lines)
deleteColgroups— Removes Pandoc-inserted<colgroup>elementswrapTables— Wraps tables in.table-wrapperand.table-scroll-wrappermakeTablesSortable— Imports tablesorter.js and initializes sortable tablesrectifyFullWidthTableWrapperStructure— Fixes wrapper structure for full-width tables
Figures & Images (~600 lines)
addSwapOutThumbnailEvents— Lazy-swaps thumbnails for full images when viewport requiresrequestImageInversionJudgments— Requests inversion/outlining judgments from APIsapplyImageInversionAndOutliningJudgments— Applies inversion/outlining based on API responsesapplyImageInversionJudgmentNowOrLater()/applyImageOutliningJudgmentNowOrLater()— Applies judgments immediately or when availableparagraphizeFigcaptionTextNodes— Wraps figcaption text nodes in<p>tagsrectifyImageAuxText— Ensures figcaption, alt, and title don't duplicatewrapImages— Wraps bare images in<figure>tagsinjectThumbnailIntoPopFramePageAbstract— Injects page thumbnail into full-page pop-frame abstractssetMediaElementDimensions()— Sets CSS dimensions from HTML attributesupdateMediaElementDimensions— Updates dimensions for pop-frame mediasetImageDimensionsFromImageData— Sets dimensions from data: URIsaddOrientationChangeMediaElementDimensionUpdateEvents— Updates on device rotationwrapFigures— Adds inner structure:.figure-outer-wrapper,.image-wrapper,.caption-wrapperaddMediaLinkWrappers— Wraps annotated images/videos in link wrappers for popup integrationdisableAnnotatedMediaLinkWrapperClickEvents— Disables click on media wrapper links (desktop)designateImageBackdropInversionStatus— Sets dark mode inversion classes on image wrappersremoveEmptyFigureCaptions— Removes empty figcaptionsrectifyFigureClasses— Moves float/outline classes from media to figuredeFloatSolitaryFigures— Removes float from figures that are only childrenprepareFullWidthFigures— Sets up full-width figures with caption width constraints
Video (~100 lines)
videoPosterURL()— Returns video poster URLsetVideoPosters— Auto-sets poster URL for local videoslazyLoadVideoPosters— Implements lazy loading for video postersenableVideoClickToPlay— Enables click-anywhere to play/pause videos
Poetry (~300 lines)
processPreformattedPoems— Processes HTML poems using whitespace for layout (enjambment)processPoems— Divides poems into stanzas with each line as<p>wrapSlashesInPoems— Wraps line-break-indicator slashes inspan.slashwrapCaesuraMarksInPoems— Wraps||caesura marksrewriteCenteredPoemThingies— Special layout for centered stanzas with caesura marksdesignateFirstAndLastLinesInPoemStanzas— Adds.first-line/.last-lineclasses
Epigraphs (~100 lines)
designatePoemEpigraphsInPoems— Marks epigraphs in poemsdesignateEpigraphAttributions— Converts em-dash-prefixed paragraphs to.attributiondesignateNarrowEpigraphs— Marks epigraphs squeezed by floats
Code Blocks (~100 lines)
wrapPreBlocks— Wraps<pre>in.sourceCodedivaddCodeBlockLineClasses— Adds.lineclass to each line spanrectifyCodeBlockClasses— Moves float classes from<pre>to wrapperwrapFullWidthPreBlocks— Wraps full-width code blocks
Embeds (~50 lines)
markLoadedEmbeds— Adds load tracking for iframesapplyIframeScrollFix— Workaround for Chrome anchor scrolling bug
Headings (~50 lines)
injectCopySectionLinkButtons— Adds copy-link buttons to section headings
Columns (~25 lines)
disableSingleItemColumnBlocks— Removes.columnsclass from single-item lists
Interviews (~75 lines)
rewriteInterviews— Restructures interview HTML with speaker/utterance classes
Margin Notes (~75 lines)
wrapMarginNotes— Wraps margin note contents in inner wrapperaggregateMarginNotes— Aggregates margin notes viaaggregateMarginNotesInDocument()
Typography (~200 lines)
rectifyTypographyInContentTransforms— Applies smart quotes, word breaks, ellipses for Wikipedia/tweet contentrectifyTypographyInBodyText— Adds word breaks after slashes in body textremoveExtraneousWhitespaceFromCitations— Cleans up.citeelementsiconifyUnicodeIconGlyphs— Converts Unicode glyphs (e.g., ☞) to icon spanshyphenate— Runs Hyphenopoly on eligible paragraphs
Full-Width Blocks (~100 lines)
createFullWidthBlockLayoutStyles()— Creates CSS variables for full-width layoutsetMarginsOnFullWidthBlocks— Sets margins for full-width blocks
Annotations (~200 lines)
rewriteTruncatedAnnotations— Makes partial annotations link to fulldesignateBlogPosts— Marks blog post annotationsrectifyBlogPosts— Removes title from blog post annotations on their own pagesrewriteAnnotationTitleLinksInPopFrames— Strips quotes from title-links in pop-framesrectifyFileAppendClasses— Fixes file-include collapse stylingrectifyInlineAnnotationTitleClasses— Treats un-annotated include links as title-linkshandleFileIncludeUncollapseInAnnotations— Handles file include uncollapse eventsrewriteAuxLinksLinksInTranscludedAnnotations— Makes aux-links scroll to appended blocksbindSectionHighlightEventsToAnnotatedLinks— Highlights annotations on link hover
Directory Indexes (~50 lines)
stripInvalidFileAppends— Removes invalid file embed links
Link Bibliography (~50 lines)
applyLinkBibliographyCompactStylingClass— Applies compact styling to link-bibsrectifyLinkBibliographyContextLinks— Injects context links into annotation title lines
Table of Contents (~150 lines)
setTOCCollapseState()— Sets TOC collapse state and buttoninjectTOCCollapseToggleButton— Adds collapse button to main TOCstripTOCLinkSpans— Removes spurious spans from TOC linksupdateMainPageTOC— Updates TOC with new sectionsrectifyTypographyInTOC— Applies word breaks to TOCdisableTOCLinkDecoration— Disables link decoration on TOC linksrewriteDirectoryIndexTOC— Relocates and cleans up TOC on tag index pagesaddRecentlyModifiedDecorationsToPageTOC— Adds star icons to recently modified TOC entriesupdateTOCVisibility— Hides TOC if ≤1 entry
Footnotes (~200 lines)
rectifyFootnoteSectionTagName— Ensures footnotes section is a<section>injectFootnoteSectionSelfLink— Adds self-link to footnotes sectionaddFootnoteClassToFootnotes— Adds.footnoteclass to footnote list itemsmarkTargetedFootnote— Marks hash-targeted footnote with.targetedinjectFootnoteSelfLinks— Adds self-link anchors to footnotesrewriteFootnoteBackLinks— Replaces Pandoc back-link text with SVG arrowinvalidateCachedNotesIfNeeded— Invalidates cached notes when content has footnotesbindNoteHighlightEventsToCitations— Highlights notes on citation hoverbindHighlightEventsToFootnoteSelfLinks— Highlights footnotes on self-link hover
Links (~400 lines)
reverseArchivedLinkPolarity— Swapshrefwithdata-url-originalqualifyAnchorLinks— Rewrites anchor link pathnames for transcluded contentaddSpecialLinkClasses— Adds.link-selfand.link-pageclassesidentifyAnchorLinks— Adds IDs to within-page linksdesignateLocalNavigationLinkIcons— Assigns icons (↑/↓/¶/gwern logo) to linkscleanSpuriousLinkIcons— Removes link icons from excluded contextsrenderQuadLinkIcon()— Renders SVG quad-letter link iconenableLinkIcon()/disableLinkIcon()— Enable/disable link icon displaysetLinkIconStates— Updates link icon display statesenableLinkIconColor()/disableLinkIconColor()— Enable/disable link hover colorizationsetLinkHoverColors— Sets hover colors for links withdata-link-icon-color
Date Ranges & Inflation (~100 lines)
prettifyCurrencyString()— Formats currency amountsrewriteInflationAdjusters— Rewrites inflation-adjustment elementsaddDoubleClickListenersToInflationAdjusters— Enables double-click selection
Miscellaneous (~200 lines)
resolveRandomElementSelectors— Handlesdisplay-random-NcontainersregeneratePlaceholderIds— Regenerates placeholder IDsremoveNoscriptTags— Removes<noscript>tagscleanUpImageAltText— Sets default alt text, URL-encodes % signsnoBreakForCitations— Prevents line breaks around citationsdesignateColorInvertedContainers— Marks containers with dark backgroundsparagraphizeAdmonitionTextNodes— Wraps admonition text in<p>tagsrectifySpecialTextBlockTagTypes— Fixes incorrect<div>vs<p>usagedesignateOrdinals— Marks ordinal superscripts (1st, 2nd, etc.)rectifyPageMetadataFieldLinkAppearance— Fixes colon appearance in page metadatarectifyTOCAdjacentBlockLayout— Handles TOC-adjacent block clearing
Dropcaps (~250 lines)
rewriteDropcaps— Creates graphical or textual dropcapsactivateDynamicGraphicalDropcaps— Swaps dropcap images on dark mode changelinkifyDropcaps— Wraps dropcaps in links to/dropcap#typepreventDropcapsOverlap— Prevents dropcap blocks from overlapping
Math (~150 lines)
unwrapMathBlocks— Unwraps<p>wrappers of math blocksaddDoubleClickListenersToMathBlocks— Enables double-click to select equationsaddBlockButtonsToMathBlocks— Adds copy button to block mathactivateMathBlockButtons— Activates copy functionality
Printing (~50 lines)
beforeprint/afterprinthandlers — Triggers transcludes and expands collapses for printing
Broken HTML/Anchor Checking (~50 lines)
reportBrokenAnchorLink()— Reports broken anchor links to server- Broken anchor check — Validates location hash on load and hash change
Link Prefetching (instant.page) (~100 lines)
- instant.page integration — Prefetches links on hover (1600ms delay)
- Configurable via
delayOnHover,linkPrefetchExclusionSelector
Key Utility Functions
These are defined in utility.js and used throughout rewrite.js:
| Function | Purpose |
|---|---|
wrapElement(el, spec, opts) | Wraps element in new parent (spec = "div.class1.class2") |
wrapAll(selector, spec, opts) | Wraps all matching elements |
unwrap(wrapper, opts) | Replaces wrapper with its children |
unwrapAll(selector, opts) | Unwraps all matching elements |
newElement(tag, attrs, props) | Creates element with attributes and properties |
elementFromHTML(html) | Parses HTML string into element |
transferClasses(from, to, classes) | Moves classes between elements |
atomicDOMUpdate(element, callback) | Performs DOM update atomically |
paragraphizeTextNodesOfElementRetainingMetadata() | Wraps text nodes in <p> tags |
Configuration
Hyphenopoly configuration (lines ~2368-2377):
Hyphenopoly.config({
require: { "en-us": "FORCEHYPHENOPOLY" },
setup: { hide: "none", keepAlive: true, safeCopy: false }
});
Full-width block layout (GW.fullWidthBlockLayout):
sideMargin: 25— Pixels of margin on viewport edges- Dynamically updated on window resize
Ordered list types (GW.layout.orderedListTypes):
decimal,lower-alpha,upper-alpha,lower-roman,upper-roman,lower-greek
Link prefetching (instant.page integration):
delayOnHover: 1600— ms before prefetch on hoverlinkPrefetchExclusionSelector:.prefetch-not,.has-content
Integration Points
Events Listened:
GW.contentDidLoad— Primary hook for content transformationsGW.contentDidInject— Post-injection DOM work and event bindingGW.hashDidChange— Updates targeted footnote styling, broken anchor checkingDarkMode.didSetMode— Updates mode-dependent stylingDarkMode.computedModeDidChange— Swaps graphical dropcap imagesGW.imageInversionJudgmentsAvailable— Applies inversion when API respondsGW.imageOutliningJudgmentsAvailable— Applies outlining when API respondsLayout.layoutProcessorDidComplete— TOC-adjacent block layout rectificationCollapse.collapseStateDidChange— Backlinks link uncollapse handlingbeforeprint/afterprint— Print preparation
Events Fired:
Rewrite.contentDidChange— When content modification completesRewrite.fullWidthMediaDidLoad— When full-width image/video loads
Module Dependencies:
GW.notificationCenter(initial.js)Transclude(transclude.js) — For triggering transcludesAnnotations(annotations.js) — For annotation link detectionNotes(notes.js) — For footnote/sidenote managementExtracts(extracts.js) — For pop-frame context detectionDarkMode(dark-mode.js) — For theme-aware stylingTypography(typography.js) — For text processingContent(content.js) — For reference dataImages(misc.js) — For thumbnail/image utilitiesAuxLinks(misc.js) — For aux-links type detectionSidenotes(sidenotes.js) — For viewport width breakpoint media queryColor(color.js) — For link icon colorizationImageFocus(image-focus.js) — For popup/image-focus coordinationPopups(popups.js) — For popup coordination
See Also
- initial.js - Notification center and handler registration that rewrite.js depends on
- rewrite-initial.js - Fast non-block-layout processors that run before rewrite.js
- content.js - Content loading and data providers consumed by rewrite handlers
- transclude.js - Transclusion pipeline that triggers rewrite handlers
- typography.js - Typography processing called by rewrite handlers
- utility.js - DOM manipulation utilities (wrapElement, unwrap, newElement)
- extracts.js - Pop-frame extraction system that uses rewrite handlers
- dark-mode.js - Dark mode system for theme-aware styling
- misc.js - Image utilities and aux-links helpers