mediawiki-skins-Citizen/resources/skins.citizen.preferences/clientPrefs.localStorage.js
alistair3149 ed226a400e
feat(preferences): implement a localStorage version of mw.user.clientPrefs
This is the first step of migrating to the clientPrefs library when it is avaliable.
Currently only themes are using it and the standard classes are added to the HTML element.

Related: #780
2024-04-24 00:21:36 -04:00

117 lines
3.7 KiB
JavaScript

/**
* mw.user.clientPrefs modified to only use localStorage
* TODO: Revisit when we move to MW 1.43 and the interface is more stable
*/
const CLIENTPREF_STORAGE_NAME = 'mwclientpreferences';
const CLIENTPREF_SUFFIX = '-clientpref-';
const CLIENTPREF_DELIMITER = ',';
/**
* Check if the feature name is composed of valid characters.
*
* A valid feature name may contain letters, numbers, and "-" characters.
*
* @private
* @param {string} value
* @return {boolean}
*/
function isValidFeatureName( value ) {
return value.match( /^[a-zA-Z0-9-]+$/ ) !== null;
}
/**
* Check if the value is composed of valid characters.
*
* @private
* @param {string} value
* @return {boolean}
*/
function isValidFeatureValue( value ) {
return value.match( /^[a-zA-Z0-9]+$/ ) !== null;
}
/**
* Save the feature value to the client preferences localStorage.
* Modified from the original to use localStorage instead of cookie.
*
* @private
* @param {string} feature
* @param {string} value
*/
function saveClientPrefs( feature, value ) {
const existingStorage = mw.storage.get( CLIENTPREF_STORAGE_NAME ) || '';
const data = {};
existingStorage.split( CLIENTPREF_DELIMITER ).forEach( function ( keyValuePair ) {
const m = keyValuePair.match( /^([\w-]+)-clientpref-(\w+)$/ );
if ( m ) {
data[ m[ 1 ] ] = m[ 2 ];
}
} );
data[ feature ] = value;
const newStorage = Object.keys( data ).map( function ( key ) {
return key + CLIENTPREF_SUFFIX + data[ key ];
} ).join( CLIENTPREF_DELIMITER );
mw.storage.set( CLIENTPREF_STORAGE_NAME, newStorage );
}
function clientPrefs() {
return {
/**
* Change the class on the HTML document element, and save the value in a localStorage.
*
* @memberof mw.user.clientPrefs
* @param {string} feature
* @param {string} value
* @return {boolean} True if feature was stored successfully, false if the value
* uses a forbidden character or the feature is not recognised
* e.g. a matching class was not defined on the HTML document element.
*/
set: function ( feature, value ) {
if ( !isValidFeatureName( feature ) || !isValidFeatureValue( value ) ) {
return false;
}
const currentValue = this.get( feature );
const oldFeatureClass = feature + CLIENTPREF_SUFFIX + currentValue;
const newFeatureClass = feature + CLIENTPREF_SUFFIX + value;
// The following classes are removed here:
// * feature-name-clientpref-<old-feature-value>
// * e.g. vector-font-size--clientpref-small
document.documentElement.classList.remove( oldFeatureClass );
// The following classes are added here:
// * feature-name-clientpref-<feature-value>
// * e.g. vector-font-size--clientpref-xlarge
document.documentElement.classList.add( newFeatureClass );
saveClientPrefs( feature, value );
return true;
},
/**
* Retrieve the current value of the feature from the HTML document element.
*
* @memberof mw.user.clientPrefs
* @param {string} feature
* @return {string|boolean} returns boolean if the feature is not recognized
* returns string if a feature was found.
*/
get: function ( feature ) {
const featurePrefix = feature + CLIENTPREF_SUFFIX;
const docClass = document.documentElement.className;
// eslint-disable-next-line security/detect-non-literal-regexp
const featureRegEx = new RegExp(
'(^| )' + mw.util.escapeRegExp( featurePrefix ) + '([a-zA-Z0-9]+)( |$)'
);
const match = docClass.match( featureRegEx );
// check no further matches if we replaced this occurance.
const isAmbiguous = docClass.replace( featureRegEx, '$1$3' ).match( featureRegEx ) !== null;
return !isAmbiguous && match ? match[ 2 ] : false;
}
};
}
/** @module clientPrefs */
module.exports = clientPrefs;