Skip to content

[SPARK-60281][DOCS] Upgrade AnchorJS to v5.0.0 - #59547

Open
nchammas wants to merge 8 commits into
apache:masterfrom
nchammas:anchorjs-bump
Open

nchammas wants to merge 8 commits into
apache:masterfrom
nchammas:anchorjs-bump

Conversation

@nchammas

@nchammas nchammas commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Upgrade AnchorJS to v5.0.0, and update its config accordingly to maintain roughly the current production behavior.

Additionally:

  • Move the AnchorJS config to global.html, which matches where we include and configure other vendored libraries.
  • Remove the anchors global from main.js, which is no longer needed now that the config is handled outside of there.
  • Fix the included licenses for AnchorJS so it includes the copyright holder's name and year.

Why are the changes needed?

The current version of AnchorJS we are using is more than 11 years old. The releases since then have added more accessible heading links and more reliable fragment generation.

This upgrade will also keep the versions in sync with the main website's version of AnchorJS: apache/spark-website#700

Any browser released since early 2021 should support this new version without issue.

Does this PR introduce any user-facing change?

No, documentation behavior tweaks only.

How was this patch tested?

I built and reviewed the site locally with SKIP_API=1 bundle exec jekyll serve. Confirmed that anchors work as expected, including for h1 headings.

Was this patch authored or co-authored using generative AI tooling?

Co-authored-by: GitHub Copilot

@HyukjinKwon

Copy link
Copy Markdown
Member

(the label is still under dev .. I will likely be able to enable it this week)

spark-test

This comment was marked as duplicate.

@spark-test

This comment was marked as duplicate.

@spark-test spark-test removed the ai-review-requested Trigger AI review by Omnigent on Databricks label Oct 19, 2026
@HyukjinKwon HyukjinKwon added the ai-review-requested Trigger AI review by Omnigent on Databricks label Oct 19, 2026
@spark-test

This comment was marked as outdated.

@spark-test spark-test removed the ai-review-requested Trigger AI review by Omnigent on Databricks label Oct 19, 2026

@HyukjinKwon HyukjinKwon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@uros-b

uros-b commented Oct 19, 2026

Copy link
Copy Markdown
Member

+1, thank you @nchammas and @HyukjinKwon!

@dongjoon-hyun dongjoon-hyun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the upgrade, @nchammas. I reviewed the latest commit, 3a8ed3d, and left 9 inline comments. Here is a summary, ordered by priority.

Correctness and compatibility

  1. licenses-binary/LICENSE-AnchorJS.txt L3: the copyright fix is not applied to the binary copy, which the binary tarball and the pyspark package ship.
  2. global.html L209: the page-wide selector also anchors the sidebar <h3> titles and the hero <h1> (not a regression), so the titles of 7 streaming pages get -1 ids; scoping it to #content would fix this.
  3. global.html L209: right after a release, a browser with the old main.js and 1.1.1 still cached can add every anchor twice.
  4. anchor.min.js L8: the bare globalThis drops all anchors on Chrome < 71, Firefox < 65, and Safari < 12.1 (negligible in practice).

Maintainability

  1. global.html L206: the inline config is not covered by dev/lint-js, unlike the same lines in main.js.
  2. global.html L199: the sync note sits in the ignored body of <script src>, and its link returns 404 until apache/spark-website#700 is merged.

PR title and description

  1. Title: [INFRA] maps to Project Infra, but docs front-end changes have used [DOCS]; AnchorJS itself was added as [SPARK-8353] [DOCS]. Could the title be [SPARK-60281][DOCS] Upgrade AnchorJS to v5.0.0, with the JIRA component to match?
  2. Description: the generative-AI section does not have the Generated-by: <tool> <version> phrase that the PR template asks for, e.g. Generated-by: GitHub Copilot <version>, and "AnchorsJS" is a typo. Both end up in the commit message.

Optional

  1. global.html L206: placement: 'right' is the default, so the line has no effect.
  2. anchor.min.js L8: FYI, 5.0.0 queries <base> once per heading (an upstream issue).
  3. main.js L100: pre-existing and outside this PR: the resize handler keeps re-binding the dropdown hover handlers.

Items 1, 2, and 5 are the ones I would most like to see addressed here, and items 7 and 8 only need the PR title and description. The rest are optional.

Comment thread licenses/LICENSE-AnchorJS.txt
Comment thread docs/_layouts/global.html Outdated
anchors.options = { placement: 'right' };
// Note: AnchorJS 4.0+ excludes h1 from the default selector, but many
// Spark doc pages use h1 for section headings, so we include it explicitly.
anchors.add('h1, h2, h3, h4, h5, h6');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. This selector also anchors the layout's own headings, not just the page content. It matches the fixed sidebar titles from _includes/nav-left-wrapper-*.html (for example <h3><a href="sql-programming-guide.html">Spark SQL Guide</a></h3>) and the hero <h1> on the landing page. As a result:

  • On the 151 sql*.html pages, the sidebar title gets an anchor to #spark-sql-guide on the current page, and screen readers read one more "Anchor" link there.
  • On the 7 streaming pages titled "Structured Streaming Programming Guide", the sidebar <h3> comes first in DOM order, so it takes #structured-streaming-programming-guide, and the real page title gets #structured-streaming-programming-guide-1.

This is not a regression, because 1.1.1's default selector matched the same elements. But since this line is being rewritten anyway, could we scope it to the content? Both layout branches use id="content", so anchors.add('#content h1, #content h2, #content h3, #content h4, #content h5, #content h6'); keeps the page-title and section anchors and drops the sidebar and hero ones.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. I used the more modern :is CSS selector here, as it's cleaner. Any browser released since early 2021 will support it.

Comment thread docs/_layouts/global.html Outdated
anchors.options = { placement: 'right' };
// Note: AnchorJS 4.0+ excludes h1 from the default selector, but many
// Spark doc pages use h1 for section headings, so we include it explicitly.
anchors.add('h1, h2, h3, h4, h5, h6');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Minor: moving this call out of main.js can briefly double the anchors right after a release. spark.apache.org serves docs/latest/js/*.js with only Last-Modified and ETag, without Cache-Control or Expires, so browsers may keep reusing cached copies for days after docs/latest moves to a new release. A returning visitor who still has the old main.js and the 1.1.1 anchor.min.js cached runs this new anchors.add(...) first and the old anchors.add() in main.js second. 1.1.1 has no check for an existing anchor, so every heading shows two icons until the cache expires. It is cosmetic and temporary. Could we keep the call in main.js as in item 5, which avoids this mixed state?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think this is worth any cache-busting fixes. It's a minor and transient issue, as you noted. I will address item 5 separately.

// Copyright (c) 2023 Bryan Braun; Licensed MIT
//
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
!function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(globalThis,function(){"use strict";return function(A){function u(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function d(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],u(this.options),this.add=function(A){var e,t,o,i,n,s,a,r,l,c,h,p=[];if(u(this.options),0!==(e=d(A=A||"h2, h3, h4, h5, h6")).length){for(null===document.head.querySelector("style.anchorjs")&&((A=document.createElement("style")).className="anchorjs",A.appendChild(document.createTextNode("")),void 0===(h=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(A):document.head.insertBefore(A,h),A.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",A.sheet.cssRules.length),A.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i<e.length;i++)if(this.hasAnchorJSLink(e[i]))p.push(i);else{if(e[i].hasAttribute("id"))o=e[i].getAttribute("id");else if(e[i].hasAttribute("data-anchor-id"))o=e[i].getAttribute("data-anchor-id");else{for(r=a=this.urlify(e[i].textContent),s=0;n=t.indexOf(r=void 0!==n?a+"-"+s:r),s+=1,-1!==n;);n=void 0,t.push(r),e[i].setAttribute("id",r),o=r}(l=document.createElement("a")).className="anchorjs-link "+this.options.class,l.setAttribute("aria-label",this.options.ariaLabel),l.setAttribute("data-anchorjs-icon",this.options.icon),this.options.titleText&&(l.title=this.options.titleText),c=document.querySelector("base")?window.location.pathname+window.location.search:"",c=this.options.base||c,l.href=c+"#"+o,"always"===this.options.visible&&(l.style.opacity="1"),""===this.options.icon&&(l.style.font="1em/1 anchorjs-icons","left"===this.options.placement)&&(l.style.lineHeight="inherit"),"left"===this.options.placement?(l.style.position="absolute",l.style.marginLeft="-1.25em",l.style.paddingRight=".25em",l.style.paddingLeft=".25em",e[i].insertBefore(l,e[i].firstChild)):(l.style.marginLeft=".1875em",l.style.paddingRight=".1875em",l.style.paddingLeft=".1875em",e[i].appendChild(l))}for(i=0;i<p.length;i++)e.splice(p[i]-i,1);this.elements=this.elements.concat(e)}return this},this.remove=function(A){for(var e,t,o=d(A),i=0;i<o.length;i++)(t=o[i].querySelector(".anchorjs-link"))&&(-1!==(e=this.elements.indexOf(o[i]))&&this.elements.splice(e,1),o[i].removeChild(t));return this},this.removeAll=function(){this.remove(this.elements)},this.urlify=function(A){var e=document.createElement("textarea");return e.innerHTML=A,A=e.value,this.options.truncate||u(this.options),A.trim().replace(/'/gi,"").replace(/[& +$,:;=?@"#{}|^~[`%!'<>\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Minor: 5.0.0 passes a bare globalThis to its UMD wrapper here, unlike 1.1.1 (plain ES5) and our Bootstrap 5.0.2 bundle (which checks typeof globalThis). On Chrome < 71, Firefox < 65 (including ESR 60), and Safari < 12.1, which Bootstrap 5.0.2's .browserslistrc still lists, this line throws ReferenceError: globalThis is not defined. Then global.html:206 throws anchors is not defined, and those browsers get no heading anchors at all. The rest of the page still works, because the config runs in its own <script>. These browsers have been out of support for years, so accepting this is fine with me. Could you mention it in the PR description, or add if (typeof globalThis === 'undefined') { window.globalThis = window; } before this script if you want to keep them working?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chrome < 71, Firefox < 65 (including ESR 60), and Safari < 12.1

These browser versions date to early 2019 at the latest. I don't think code changes to support them are merited. I will update the PR description.

Comment thread docs/_layouts/global.html Outdated
<script>
// Display anchor links when hovering over headings.
// See: https://www.bryanbraun.com/anchorjs/
anchors.options = { placement: 'right' };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Minor: moving these lines out of docs/js/main.js also takes them out of the lint check. dev/lint-js runs ESLint (eslint:recommended, which includes no-undef) on docs/js only, and nothing lints docs/_layouts. So a later typo here, such as anchros.add(...), would pass CI and silently remove the anchors from every page, while the same typo in main.js fails no-undef (which is why main.js declared /* global $, anchors */). A separate ready handler in main.js keeps both the lint check and the isolation you get from a separate script, because jQuery 3.7.1 runs each ready handler in its own promise chain, so an error in one does not skip the dropdown setup in the other:
$(function() {
  anchors.options = { placement: 'right' };
  anchors.add('h1, h2, h3, h4, h5, h6');
});

Could you move the config back into main.js this way and restore /* global $, anchors */?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already include other libraries in global.html, like jQuery, MathJax, and DocSearch, which is why I moved AnchorJS there. I think the correct fix here is in a follow-up PR to add HTML linting via eslint-plugin-html.

Comment thread docs/_layouts/global.html Outdated
Comment thread docs/_layouts/global.html Outdated
// Copyright (c) 2023 Bryan Braun; Licensed MIT
//
// @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt Expat
!function(A,e){"use strict";"function"==typeof define&&define.amd?define([],e):"object"==typeof module&&module.exports?module.exports=e():(A.AnchorJS=e(),A.anchors=new A.AnchorJS)}(globalThis,function(){"use strict";return function(A){function u(A){A.icon=Object.prototype.hasOwnProperty.call(A,"icon")?A.icon:"",A.visible=Object.prototype.hasOwnProperty.call(A,"visible")?A.visible:"hover",A.placement=Object.prototype.hasOwnProperty.call(A,"placement")?A.placement:"right",A.ariaLabel=Object.prototype.hasOwnProperty.call(A,"ariaLabel")?A.ariaLabel:"Anchor",A.class=Object.prototype.hasOwnProperty.call(A,"class")?A.class:"",A.base=Object.prototype.hasOwnProperty.call(A,"base")?A.base:"",A.truncate=Object.prototype.hasOwnProperty.call(A,"truncate")?Math.floor(A.truncate):64,A.titleText=Object.prototype.hasOwnProperty.call(A,"titleText")?A.titleText:""}function d(A){var e;if("string"==typeof A||A instanceof String)e=[].slice.call(document.querySelectorAll(A));else{if(!(Array.isArray(A)||A instanceof NodeList))throw new TypeError("The selector provided to AnchorJS was invalid.");e=[].slice.call(A)}return e}this.options=A||{},this.elements=[],u(this.options),this.add=function(A){var e,t,o,i,n,s,a,r,l,c,h,p=[];if(u(this.options),0!==(e=d(A=A||"h2, h3, h4, h5, h6")).length){for(null===document.head.querySelector("style.anchorjs")&&((A=document.createElement("style")).className="anchorjs",A.appendChild(document.createTextNode("")),void 0===(h=document.head.querySelector('[rel="stylesheet"],style'))?document.head.appendChild(A):document.head.insertBefore(A,h),A.sheet.insertRule(".anchorjs-link{opacity:0;text-decoration:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}",A.sheet.cssRules.length),A.sheet.insertRule(":hover>.anchorjs-link,.anchorjs-link:focus{opacity:1}",A.sheet.cssRules.length),A.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",A.sheet.cssRules.length),A.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',A.sheet.cssRules.length)),h=document.querySelectorAll("[id]"),t=[].map.call(h,function(A){return A.id}),i=0;i<e.length;i++)if(this.hasAnchorJSLink(e[i]))p.push(i);else{if(e[i].hasAttribute("id"))o=e[i].getAttribute("id");else if(e[i].hasAttribute("data-anchor-id"))o=e[i].getAttribute("data-anchor-id");else{for(r=a=this.urlify(e[i].textContent),s=0;n=t.indexOf(r=void 0!==n?a+"-"+s:r),s+=1,-1!==n;);n=void 0,t.push(r),e[i].setAttribute("id",r),o=r}(l=document.createElement("a")).className="anchorjs-link "+this.options.class,l.setAttribute("aria-label",this.options.ariaLabel),l.setAttribute("data-anchorjs-icon",this.options.icon),this.options.titleText&&(l.title=this.options.titleText),c=document.querySelector("base")?window.location.pathname+window.location.search:"",c=this.options.base||c,l.href=c+"#"+o,"always"===this.options.visible&&(l.style.opacity="1"),""===this.options.icon&&(l.style.font="1em/1 anchorjs-icons","left"===this.options.placement)&&(l.style.lineHeight="inherit"),"left"===this.options.placement?(l.style.position="absolute",l.style.marginLeft="-1.25em",l.style.paddingRight=".25em",l.style.paddingLeft=".25em",e[i].insertBefore(l,e[i].firstChild)):(l.style.marginLeft=".1875em",l.style.paddingRight=".1875em",l.style.paddingLeft=".1875em",e[i].appendChild(l))}for(i=0;i<p.length;i++)e.splice(p[i]-i,1);this.elements=this.elements.concat(e)}return this},this.remove=function(A){for(var e,t,o=d(A),i=0;i<o.length;i++)(t=o[i].querySelector(".anchorjs-link"))&&(-1!==(e=this.elements.indexOf(o[i]))&&this.elements.splice(e,1),o[i].removeChild(t));return this},this.removeAll=function(){this.remove(this.elements)},this.urlify=function(A){var e=document.createElement("textarea");return e.innerHTML=A,A=e.value,this.options.truncate||u(this.options),A.trim().replace(/'/gi,"").replace(/[& +$,:;=?@"#{}|^~[`%!'<>\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Nit: FYI, 5.0.0's add() calls document.querySelector('base') inside its per-heading loop (anchor.js L140 at tag 5.0.0). Our pages have no <base>, so each heading triggers a scan of the whole document. On the largest page, sql-ref-functions-builtin.html (about 1.5 MB, ~48K elements, 48 headings), that is about 48 extra scans during parsing. I have not measured it, but it should be a few milliseconds. Nothing to change here, since this file should stay identical to upstream. Could it be worth an upstream issue to move that query out of the loop?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked into this. I think a future, dedicated performance analysis of the website would likely find higher-impact improvements we could make. At that time, we can pursue upstream fixes as needed.

Comment thread docs/js/main.js
};
anchors.add();

// Make dropdown menus in nav bars show on hover instead of click

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Nit: this is pre-existing and outside this PR. The dropdown block below (L100-L119) calls $dropdown.hover(...) again on every load or resize event at 768px or wider, without unbinding the previous handlers. jQuery's hover() is just .on("mouseenter", ...).on("mouseleave", ...), so the handlers pile up until the window becomes narrower than 768px. They are idempotent, so this only wastes work. The 768px threshold also differs from the navbar's navbar-expand-lg breakpoint (992px). This dates from SPARK-30654. Could $dropdown.off("mouseenter mouseleave").hover(...) go into a small follow-up?

@nchammas nchammas changed the title [SPARK-60281][INFRA] Upgrade AnchorJS to v5.0.0 [SPARK-60281][DOCS] Upgrade AnchorJS to v5.0.0 Oct 21, 2026
@nchammas

nchammas commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author
  • Title: [INFRA] maps to Project Infra, but docs front-end changes have used [DOCS]; AnchorJS itself was added as [SPARK-8353] [DOCS]. Could the title be [SPARK-60281][DOCS] Upgrade AnchorJS to v5.0.0, with the JIRA component to match?

Done.

  • Description: the generative-AI section does not have the Generated-by: <tool> <version> phrase that the PR template asks for, e.g. Generated-by: GitHub Copilot <version>, and "AnchorsJS" is a typo. Both end up in the commit message.

I think Generated-by: is too strong a label to use given that I authored this PR. I use AI assistance, yes, but Generated-by: indicates that some AI tool generated the patch, which is not correct.

I've submitted #59666 for our PR template to match ASF's guidance in suggesting Co-authored-by:, which is more appropriate in this case.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants