1
+ /**
2
+ * Add anchor links to any H2 - H6 within the <main> element that:
3
+ * - are not children of a <nav> element
4
+ * - do not have an ancestor with the data-anchor="no" attribute, with
5
+ * the `hidden` attribute or with the .visuallyhidden class
6
+ * - do not themselves have the data-anchor="no" attribute, the `hidden`
7
+ * attribute or the .visuallyhidden class
8
+ *
9
+ * If a heading does not already possess an ID, use regular expressions on
10
+ * the textContent of the heading to generate a string that is valid to
11
+ * use for both the heading ID and the anchor href. Supports non-Latin
12
+ * scripts by matching any Unicode letter - \p{L} - or number - \p{N}. The
13
+ * u flag enables Unicode matching, to support characters from any script.
14
+ *
15
+ * Otherwise, generate the anchor using the existing heading ID value.
16
+ */
17
+
18
+ import { translate } from './translations' ;
19
+
20
+ let headingAnchors = function ( ) {
21
+
22
+ let languageCode = document . documentElement . lang ;
23
+
24
+ // Only add heading anchor links on "full" sites
25
+ if ( languageCode === 'en' || languageCode === 'ja' || languageCode === 'zh-hans' ) {
26
+
27
+ let headingsArray = Array . from ( document . querySelectorAll ( 'main h2, main h3, main h4, main h5, main h6' ) ) ;
28
+
29
+ if ( headingsArray . length > 0 ) {
30
+
31
+ // Filter out headings that:
32
+ // - Are not children of <nav>
33
+ // - Do not have an ancestor with the data-anchor="no" attribute
34
+ // - Do not have an ancestor with the `hidden` attribute
35
+ // - Do not have an ancestor with the `.visuallyhidden` class
36
+ // - Do not themselves have the data-anchor="no" attribute
37
+ // - Do not themselves have the `hidden` attribute
38
+ // - Do not themselves have the `.visuallyhidden` class
39
+ let targetedHeadings = headingsArray . filter ( function ( heading ) {
40
+ let insideNav = heading . closest ( 'nav' ) !== null ;
41
+ let parentHasDataAttribute = heading . closest ( '[data-anchor="no"]' ) !== null ;
42
+ let hiddenParent = heading . closest ( '[hidden]' ) !== null ;
43
+ let visuallyhiddenParent = heading . closest ( '.visuallyhidden' ) !== null ;
44
+ let hasDataAttribute = heading . getAttribute ( 'data-anchor' ) === 'no' ;
45
+ let isHidden = heading . getAttribute ( 'hidden' ) ;
46
+ let isVisuallyhidden = heading . classList . contains ( 'visuallyhidden' ) ;
47
+
48
+ return ! insideNav && ! parentHasDataAttribute && ! hiddenParent && ! visuallyhiddenParent && ! hasDataAttribute && ! isHidden && ! isVisuallyhidden ;
49
+ } ) ;
50
+
51
+ if ( targetedHeadings . length > 0 ) {
52
+ targetedHeadings . forEach ( function ( heading ) {
53
+
54
+ let anchor = document . createElement ( 'a' ) ;
55
+ let anchorHref ;
56
+
57
+ // If the heading already has an ID, use this for constructing the anchor
58
+ if ( heading . getAttribute ( 'id' ) ) {
59
+ anchorHref = heading . id ;
60
+ } else {
61
+ // If the heading does not already have an ID, generate anchor href from the heading text. Steps are:
62
+ // - Remove leading/trailing spaces
63
+ // - Use RegEx to remove invalid characters but keep all Unicode letters/numbers
64
+ // - Use RegEx to replace spaces with hyphens
65
+ // - convert to lowercase as per URL policy
66
+ anchorHref = heading . textContent
67
+ . trim ( )
68
+ . replace ( / [ ^ \p{ L} \p{ N} \s - ] / gu, '' )
69
+ . replace ( / \s + / g, '-' )
70
+ . toLowerCase ( ) ;
71
+ heading . id = anchorHref ;
72
+ }
73
+
74
+ anchor . setAttribute ( 'href' , '#' + anchorHref ) ;
75
+ anchor . setAttribute ( 'class' , 'heading-anchor' ) ;
76
+ anchor . innerHTML = '<span aria-hidden="true">§</span>'
77
+ + '<span class="visuallyhidden">'
78
+ + translate . translate ( 'anchor' , languageCode ) + '</span>' ;
79
+ heading . textContent += '\xa0' ;
80
+ heading . appendChild ( anchor ) ;
81
+
82
+ } ) ;
83
+ }
84
+
85
+ }
86
+
87
+ }
88
+
89
+ }
90
+
91
+ export { headingAnchors } ;
0 commit comments