Skip to content

Small refactors in anchors.js (#29947) #30003

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
Mar 22, 2024
Merged
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
40 changes: 21 additions & 19 deletions web_src/js/markup/anchors.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import {svg} from '../svg.js';

const addPrefix = (str) => `user-content-${str}`;
const removePrefix = (str) => str.replace(/^user-content-/, '');
const hasPrefix = (str) => str.startsWith('user-content-');

// scroll to anchor while respecting the `user-content` prefix that exists on the target
function scrollToAnchor(encodedId, initial) {
// abort if the browser has already scrolled to another anchor during page load
if (!encodedId || (initial && document.querySelector(':target'))) return;
function scrollToAnchor(encodedId) {
if (!encodedId) return;
const id = decodeURIComponent(encodedId);
let el = document.getElementById(`user-content-${id}`);
const prefixedId = addPrefix(id);
let el = document.getElementById(prefixedId);

// check for matching user-generated `a[name]`
if (!el) {
const nameAnchors = document.getElementsByName(`user-content-${id}`);
const nameAnchors = document.getElementsByName(prefixedId);
if (nameAnchors.length) {
el = nameAnchors[0];
}
}

// compat for links with old 'user-content-' prefixed hashes
if (!el && id.startsWith('user-content-')) {
const el = document.getElementById(id);
if (el) el.scrollIntoView();
if (!el && hasPrefix(id)) {
return document.getElementById(id)?.scrollIntoView();
}

if (el) {
el.scrollIntoView();
}
el?.scrollIntoView();
}

export function initMarkupAnchors() {
Expand All @@ -32,11 +33,10 @@ export function initMarkupAnchors() {

for (const markupEl of markupEls) {
// create link icons for markup headings, the resulting link href will remove `user-content-`
for (const heading of markupEl.querySelectorAll(`:is(h1, h2, h3, h4, h5, h6`)) {
const originalId = heading.id.replace(/^user-content-/, '');
for (const heading of markupEl.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const a = document.createElement('a');
a.classList.add('anchor');
a.setAttribute('href', `#${encodeURIComponent(originalId)}`);
a.setAttribute('href', `#${encodeURIComponent(removePrefix(heading.id))}`);
a.innerHTML = svg('octicon-link');
heading.prepend(a);
}
Expand All @@ -45,24 +45,26 @@ export function initMarkupAnchors() {
for (const a of markupEl.querySelectorAll('a[href^="#"]')) {
const href = a.getAttribute('href');
if (!href.startsWith('#user-content-')) continue;
const originalId = href.replace(/^#user-content-/, '');
a.setAttribute('href', `#${originalId}`);
a.setAttribute('href', `#${removePrefix(href.substring(1))}`);
}

// add `user-content-` prefix to user-generated `a[name]` link targets
// TODO: this prefix should be added in backend instead
for (const a of markupEl.querySelectorAll('a[name]')) {
const name = a.getAttribute('name');
if (!name) continue;
a.setAttribute('name', `user-content-${a.name}`);
a.setAttribute('name', addPrefix(a.name));
}

for (const a of markupEl.querySelectorAll('a[href^="#"]')) {
a.addEventListener('click', (e) => {
scrollToAnchor(e.currentTarget.getAttribute('href')?.substring(1), false);
scrollToAnchor(e.currentTarget.getAttribute('href')?.substring(1));
});
}
}

scrollToAnchor(window.location.hash.substring(1), true);
// scroll to anchor unless the browser has already scrolled somewhere during page load
if (!document.querySelector(':target')) {
scrollToAnchor(window.location.hash?.substring(1));
}
}