@@ -63,3 +63,100 @@ const _prepareAccordion = (tocElement) => {
6363 tocEntryList . parentNode . insertBefore ( tocEntryWrapper , tocEntryList ) ;
6464 } ) ;
6565} ;
66+
67+ /**
68+ * Generate a list of fallback URLs from the closest to the furthest of the target URL and
69+ * return the first one that points to an existing resource.
70+ *
71+ * The generation consists of starting with the target URL and walking back toward the root of
72+ * the documentation while alternating between including the original language or not, if it was
73+ * included in the original URL. The last fallback URL is the root of the documentation with the
74+ * version stripped off to redirect the user to the index of the default version.
75+ *
76+ * Example:
77+ * 1. .../documentation/13.0/contributing/documentation.html
78+ * 2. .../documentation/13.0/contributing.html
79+ * 3. .../documentation/13.0
80+ * 4. .../documentation/
81+ *
82+ * Example:
83+ * 1. .../documentation/15.0/fr/administration/install.html
84+ * 2. .../documentation/15.0/administration/install.html
85+ * 3. .../documentation/15.0/fr/administration.html
86+ * 4. .../documentation/15.0/administration.html
87+ * 5. .../documentation/15.0/fr/
88+ * 6. .../documentation/15.0/
89+ * 7. .../documentation/
90+ */
91+ const _generateFallbackUrls = async ( targetUrl ) => {
92+
93+ const _deconstructUrl = ( urlObject ) => {
94+ let version = '' ;
95+ let language = '' ;
96+ const originalPathParts = [ ] ;
97+ for ( let fragment of urlObject . pathname . split ( '/' ) . reverse ( ) ) {
98+ if ( fragment . match ( / ^ (?: s a a s - ) ? \d { 2 } \. \d $ | ^ m a s t e r $ / ) ) {
99+ version = fragment ;
100+ } else if ( fragment . match ( / ^ [ a - z ] { 2 } (?: _ [ A - Z ] { 2 } ) ? $ / ) ) {
101+ language = fragment ;
102+ } else if ( fragment . length > 0 ) {
103+ originalPathParts . unshift ( fragment ) ;
104+ }
105+ }
106+ return [ version , language , originalPathParts ] ;
107+ } ;
108+
109+ const targetUrlObject = new URL ( targetUrl ) ;
110+ const [ version , language , originalPathParts ] = _deconstructUrl ( targetUrlObject ) ;
111+ const urlBase = targetUrlObject . origin ;
112+
113+ // Generate the fallback URLs.
114+ const fallbackUrls = [ ] ;
115+ for ( let i = originalPathParts . length ; i >= 0 ; i -- ) {
116+ const fallbackPathParts = originalPathParts . slice ( 0 , i ) ;
117+
118+ // Append '.html' to the last path part if it is missing and the part is not the root.
119+ if (
120+ fallbackPathParts . length > 0
121+ && ! fallbackPathParts [ fallbackPathParts . length - 1 ] . endsWith ( '.html' )
122+ ) {
123+ fallbackPathParts [ fallbackPathParts . length - 1 ] += '.html' ;
124+ }
125+
126+ // Build the fallback URL from the version, language and path parts, if any.
127+ if ( version && language )
128+ fallbackUrls . push (
129+ `${ urlBase } /${ version } /${ language } /${ fallbackPathParts . join ( '/' ) } ` ,
130+ `${ urlBase } /${ version } /${ fallbackPathParts . join ( '/' ) } ` ,
131+ ) ;
132+ else if ( version && ! language )
133+ fallbackUrls . push ( `${ urlBase } /${ version } /${ fallbackPathParts . join ( '/' ) } ` ) ;
134+ else if ( ! version && language )
135+ fallbackUrls . push (
136+ `${ urlBase } /${ language } /${ fallbackPathParts . join ( '/' ) } ` ,
137+ `${ urlBase } /${ fallbackPathParts . join ( '/' ) } ` ,
138+ ) ;
139+ else if ( ! version && ! language )
140+ fallbackUrls . push ( `${ urlBase } /${ fallbackPathParts . join ( '/' ) } ` ) ;
141+ }
142+ return fallbackUrls ;
143+ } ;
144+
145+ /**
146+ * Iterate over the provided URLs and return the first one that points to a valid resource.
147+ *
148+ * Since URLs don't have a protocol and cannot be fetched when the documentation is built locally
149+ * without the `ROOT` and `IS_REMOTE_BUILD` Make arguments, the URLs that don't have the protocol
150+ * 'http' or 'https' are not tested.
151+ */
152+ const _getFirstValidUrl = async ( urls ) => {
153+ for ( let url of urls ) {
154+ if ( url . startsWith ( 'http' ) ) {
155+ const response = await fetch ( url ) ;
156+ if ( response . ok ) {
157+ return url ;
158+ }
159+ }
160+ }
161+ return urls [ 0 ] ; // No valid URL found, return the first one.
162+ } ;
0 commit comments