Implement percentDecode for finding link fragment targets

Relying on :target getting set means we can't use
history.pushState to change the URL without scrolling.

Should conform to https://url.spec.whatwg.org/#percent-decode

Change-Id: I4ccc3fd745884849a781a9f7fc8b00b8b689e20a
This commit is contained in:
Ed Sanders 2022-08-04 13:58:36 +01:00 committed by Bartosz Dziewoński
parent ce567a1eeb
commit b3b543040b
2 changed files with 46 additions and 5 deletions

View file

@ -15,7 +15,8 @@
},
"settings": {
"polyfills": [
"URL"
"URL",
"URLSearchParams"
],
"jsdoc": {
"preferredTypes": {

View file

@ -162,8 +162,7 @@ function highlightTargetComment( threadItemSet, noScroll ) {
missingTargetNotifPromise = null;
}
// eslint-disable-next-line no-jquery/no-global-selector
var targetElement = $( ':target' )[ 0 ];
var targetElement = getTargetFromFragment( location.hash.slice( 1 ) );
if ( targetElement && targetElement.hasAttribute( 'data-mw-comment-start' ) ) {
var comment = threadItemSet.findCommentById( targetElement.getAttribute( 'id' ) );
@ -356,8 +355,9 @@ function clearHighlightTargetComment( threadItemSet ) {
}
var url = new URL( location.href );
// eslint-disable-next-line no-jquery/no-global-selector
var targetElement = $( ':target' )[ 0 ];
var targetElement = getTargetFromFragment( location.hash.slice( 1 ) );
if ( targetElement && targetElement.hasAttribute( 'data-mw-comment-start' ) ) {
// Clear the hash from the URL, triggering the 'hashchange' event and updating the :target
// selector (so that our code to clear our highlight works), but without scrolling anywhere.
@ -389,6 +389,46 @@ function clearHighlightTargetComment( threadItemSet ) {
}
}
/**
* Get the target element from a link hash
*
* This is the same element as you would get from
* document.querySelectorAll(':target'), but can be used on
* an arbitrary hash fragment, or after pushState/replaceState
* has been used.
*
* @param {string} hash Hash fragment, without the leading '#'
* @return {Element|null} Element, if found
*/
function getTargetFromFragment( hash ) {
// Per https://html.spec.whatwg.org/multipage/browsing-the-web.html#target-element
// we try the raw fragment first, then the percent-decoded fragment.
return document.getElementById( hash ) ||
document.getElementById( percentDecode( hash ) );
}
/**
* Percent decode a link fragment
*
* Link fragments can be unencoded, fully encoded or partially
* encoded, as defined in the spec.
*
* We can't just use decodeURI as that assumes the fragment
* is fully encoded, and throws an error on a string like '%A'.
*
* @param {string} text Text to decode
* @return {string|null} Decoded text, null if decoding failed
*/
function percentDecode( text ) {
var params = new URLSearchParams(
'q=' +
// Query string param decoding replaces '+' with ' ' before doing the
// percent_decode, so encode '+' to prevent this.
text.replace( /\+/g, '%2B' )
);
return params.get( 'q' );
}
/**
* Get the last highlighted just-published comment, if any
*