Skip to content

Commit 58a0ba7

Browse files
GiteaBotlunnysilverwind
authored
Fix and rewrite markup anchor processing (#29931) (#29946)
Backport #29931 by @lunny Fix #29877 Co-authored-by: Lunny Xiao <[email protected]> Co-authored-by: silverwind <[email protected]>
1 parent b4a6c6f commit 58a0ba7

File tree

1 file changed

+51
-33
lines changed

1 file changed

+51
-33
lines changed

web_src/js/markup/anchors.js

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,68 @@
11
import {svg} from '../svg.js';
22

3-
const headingSelector = '.markup h1, .markup h2, .markup h3, .markup h4, .markup h5, .markup h6';
4-
53
// scroll to anchor while respecting the `user-content` prefix that exists on the target
6-
function scrollToAnchor(hash, initial) {
4+
function scrollToAnchor(encodedId, initial) {
75
// abort if the browser has already scrolled to another anchor during page load
8-
if (initial && document.querySelector(':target')) return;
9-
if (hash?.length <= 1) return;
10-
const id = decodeURIComponent(hash.substring(1));
11-
const el = document.getElementById(`user-content-${id}`);
12-
if (el) {
13-
el.scrollIntoView();
14-
} else if (id.startsWith('user-content-')) { // compat for links with old 'user-content-' prefixed hashes
6+
if (!encodedId || (initial && document.querySelector(':target'))) return;
7+
const id = decodeURIComponent(encodedId);
8+
let el = document.getElementById(`user-content-${id}`);
9+
10+
// check for matching user-generated `a[name]`
11+
if (!el) {
12+
const nameAnchors = document.getElementsByName(`user-content-${id}`);
13+
if (nameAnchors.length) {
14+
el = nameAnchors[0];
15+
}
16+
}
17+
18+
// compat for links with old 'user-content-' prefixed hashes
19+
if (!el && id.startsWith('user-content-')) {
1520
const el = document.getElementById(id);
1621
if (el) el.scrollIntoView();
1722
}
23+
24+
if (el) {
25+
el.scrollIntoView();
26+
}
1827
}
1928

2029
export function initMarkupAnchors() {
21-
if (!document.querySelector('.markup')) return;
22-
23-
// create link icons for markup headings, the resulting link href will remove `user-content-`
24-
for (const heading of document.querySelectorAll(headingSelector)) {
25-
const originalId = heading.id.replace(/^user-content-/, '');
26-
const a = document.createElement('a');
27-
a.classList.add('anchor');
28-
a.setAttribute('href', `#${encodeURIComponent(originalId)}`);
29-
a.innerHTML = svg('octicon-link');
30-
a.addEventListener('click', (e) => {
31-
scrollToAnchor(e.currentTarget.getAttribute('href'), false);
32-
});
33-
heading.prepend(a);
34-
}
30+
const markupEls = document.querySelectorAll('.markup');
31+
if (!markupEls.length) return;
32+
33+
for (const markupEl of markupEls) {
34+
// create link icons for markup headings, the resulting link href will remove `user-content-`
35+
for (const heading of markupEl.querySelectorAll(`:is(h1, h2, h3, h4, h5, h6`)) {
36+
const originalId = heading.id.replace(/^user-content-/, '');
37+
const a = document.createElement('a');
38+
a.classList.add('anchor');
39+
a.setAttribute('href', `#${encodeURIComponent(originalId)}`);
40+
a.innerHTML = svg('octicon-link');
41+
heading.prepend(a);
42+
}
43+
44+
// remove `user-content-` prefix from links so they don't show in url bar when clicked
45+
for (const a of markupEl.querySelectorAll('a[href^="#"]')) {
46+
const href = a.getAttribute('href');
47+
if (!href.startsWith('#user-content-')) continue;
48+
const originalId = href.replace(/^#user-content-/, '');
49+
a.setAttribute('href', `#${originalId}`);
50+
}
51+
52+
// add `user-content-` prefix to user-generated `a[name]` link targets
53+
// TODO: this prefix should be added in backend instead
54+
for (const a of markupEl.querySelectorAll('a[name]')) {
55+
const name = a.getAttribute('name');
56+
if (!name) continue;
57+
a.setAttribute('name', `user-content-${a.name}`);
58+
}
3559

36-
// handle user-defined `name` anchors like `[Link](#link)` linking to `<a name="link"></a>Link`
37-
for (const a of document.querySelectorAll('.markup a[href^="#"]')) {
38-
const href = a.getAttribute('href');
39-
if (!href.startsWith('#user-content-')) continue;
40-
const originalId = href.replace(/^#user-content-/, '');
41-
a.setAttribute('href', `#${encodeURIComponent(originalId)}`);
42-
if (a.closest('.markup').querySelectorAll(`a[name="${originalId}"]`).length !== 1) {
60+
for (const a of markupEl.querySelectorAll('a[href^="#"]')) {
4361
a.addEventListener('click', (e) => {
44-
scrollToAnchor(e.currentTarget.getAttribute('href'), false);
62+
scrollToAnchor(e.currentTarget.getAttribute('href')?.substring(1), false);
4563
});
4664
}
4765
}
4866

49-
scrollToAnchor(window.location.hash, true);
67+
scrollToAnchor(window.location.hash.substring(1), true);
5068
}

0 commit comments

Comments
 (0)