mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/Vector.git
synced 2024-09-24 10:49:25 +00:00
Limited width toggle
* Introduce a generalized feature toggle system that uses user preferences or localStorage for anons (right now the latter is out of scope but will be explored in a follow up) * Feature flagged to VisualEnhancementNext for now, given the dependency on icon size Bug: T319449 Change-Id: I7343a3f38b720411d5ef5f3414f25f475b0bb84a
This commit is contained in:
parent
685a3171bd
commit
0eb8811e7a
|
@ -29,10 +29,10 @@ module.exports = {
|
||||||
// An object that configures minimum threshold enforcement for coverage results
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
coverageThreshold: {
|
coverageThreshold: {
|
||||||
global: {
|
global: {
|
||||||
branches: 25,
|
branches: 26,
|
||||||
functions: 29,
|
functions: 32,
|
||||||
lines: 32,
|
lines: 34,
|
||||||
statements: 32
|
statements: 34
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@ var mockMediaWiki = require( '@wikimedia/mw-node-qunit/src/mockMediaWiki.js' );
|
||||||
global.mw = mockMediaWiki();
|
global.mw = mockMediaWiki();
|
||||||
global.$ = require('jquery');
|
global.$ = require('jquery');
|
||||||
global.mw.util.showPortlet = function() {};
|
global.mw.util.showPortlet = function() {};
|
||||||
|
global.mw.Api.prototype.saveOption = function() {};
|
||||||
|
|
58
resources/skins.vector.js/features.js
Normal file
58
resources/skins.vector.js/features.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/** @interface MwApi */
|
||||||
|
|
||||||
|
var /** @type {MwApi} */api,
|
||||||
|
debounce = require( /** @type {string} */ ( 'mediawiki.util' ) ).debounce;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves preference to user preferences and/or localStorage.
|
||||||
|
*
|
||||||
|
* @param {string} feature
|
||||||
|
* @param {boolean} enabled
|
||||||
|
*/
|
||||||
|
function save( feature, enabled ) {
|
||||||
|
var featuresJSON,
|
||||||
|
// @ts-ignore
|
||||||
|
features = mw.storage.get( 'VectorFeatures' ) || '{}';
|
||||||
|
|
||||||
|
try {
|
||||||
|
featuresJSON = JSON.parse( features );
|
||||||
|
} catch ( e ) {
|
||||||
|
featuresJSON = {};
|
||||||
|
}
|
||||||
|
featuresJSON[ feature ] = enabled;
|
||||||
|
// @ts-ignore
|
||||||
|
mw.storage.set( 'VectorFeatures', JSON.stringify( featuresJSON ) );
|
||||||
|
|
||||||
|
if ( !mw.user.isAnon() ) {
|
||||||
|
debounce( function () {
|
||||||
|
api = api || new mw.Api();
|
||||||
|
api.saveOption( 'vector-' + feature, enabled ? 1 : 0 );
|
||||||
|
}, 500 )();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @throws {Error} if unknown feature toggled.
|
||||||
|
*/
|
||||||
|
function toggle( name ) {
|
||||||
|
var featureClassEnabled = 'vector-feature-' + name + '-enabled',
|
||||||
|
classList = document.body.classList,
|
||||||
|
featureClassDisabled = 'vector-feature-' + name + '-disabled';
|
||||||
|
|
||||||
|
if ( classList.contains( featureClassDisabled ) ) {
|
||||||
|
classList.remove( featureClassDisabled );
|
||||||
|
classList.add( featureClassEnabled );
|
||||||
|
save( name, true );
|
||||||
|
} else if ( classList.contains( featureClassEnabled ) ) {
|
||||||
|
classList.add( featureClassDisabled );
|
||||||
|
classList.remove( featureClassEnabled );
|
||||||
|
save( name, false );
|
||||||
|
} else {
|
||||||
|
throw new Error( 'Attempt to toggle unknown feature: ' + name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
toggle: toggle
|
||||||
|
};
|
1
resources/skins.vector.js/images/fullscreen-close.svg
Normal file
1
resources/skins.vector.js/images/fullscreen-close.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M11.5 8.5v-5H13V7h3.5v1.5h-5ZM11.5 11.5v5H13V13h3.5v-1.5h-5ZM8.5 8.5v-5H7V7H3.5v1.5h5ZM8.5 11.5v5H7V13H3.5v-1.5h5Z"/></g></svg>
|
After Width: | Height: | Size: 234 B |
1
resources/skins.vector.js/images/fullscreen.svg
Normal file
1
resources/skins.vector.js/images/fullscreen.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M16.5 3.5v5H15V5h-3.5V3.5h5ZM16.5 16.5v-5H15V15h-3.5v1.5h5ZM3.5 3.5v5H5V5h3.5V3.5h-5ZM3.5 16.5v-5H5V15h3.5v1.5h-5Z"/></g></svg>
|
After Width: | Height: | Size: 234 B |
15
resources/skins.vector.js/limitedWidthToggle.js
Normal file
15
resources/skins.vector.js/limitedWidthToggle.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
var features = require( './features.js' );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a toggle button
|
||||||
|
*/
|
||||||
|
function init() {
|
||||||
|
var toggle = document.createElement( 'div' );
|
||||||
|
toggle.classList.add( 'mw-ui-icon', 'mw-ui-icon-element', 'mw-ui-button', 'vector-limited-width-toggle' );
|
||||||
|
document.body.appendChild( toggle );
|
||||||
|
toggle.addEventListener( 'click', function () {
|
||||||
|
features.toggle( 'limited-width' );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = init;
|
25
resources/skins.vector.js/limitedWidthToggle.less
Normal file
25
resources/skins.vector.js/limitedWidthToggle.less
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
.vector-limited-width-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note on certain pages the control will have no effect e.g. Special:RecentChanges
|
||||||
|
// Defining this at 1600px was a product decision so do not change it
|
||||||
|
// (more context at https://phabricator.wikimedia.org/T319449#8346630)
|
||||||
|
@media ( min-width: 1600px ) {
|
||||||
|
.vector-feature-visual-enhancement-next-enabled .vector-limited-width-toggle {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 8px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
background-image: url( images/fullscreen.svg );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vector-feature-visual-enhancement-next-enabled.vector-feature-limited-width-content-disable {
|
||||||
|
.vector-limited-width-toggle:before {
|
||||||
|
background-image: url( images/fullscreen-close.svg );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ var languageButton = require( './languageButton.js' ),
|
||||||
initSearchLoader = require( './searchLoader.js' ).initSearchLoader,
|
initSearchLoader = require( './searchLoader.js' ).initSearchLoader,
|
||||||
dropdownMenus = require( './dropdownMenus.js' ).dropdownMenus,
|
dropdownMenus = require( './dropdownMenus.js' ).dropdownMenus,
|
||||||
sidebarPersistence = require( './sidebarPersistence.js' ),
|
sidebarPersistence = require( './sidebarPersistence.js' ),
|
||||||
|
limitedWidthToggle = require( './limitedWidthToggle.js' ),
|
||||||
watchstar = require( './watchstar.js' ),
|
watchstar = require( './watchstar.js' ),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
menuTabs = require( './menuTabs.js' ),
|
menuTabs = require( './menuTabs.js' ),
|
||||||
|
@ -81,6 +82,7 @@ function main( window ) {
|
||||||
// menuTabs should follow `dropdownMenus` as that can move menu items from a
|
// menuTabs should follow `dropdownMenus` as that can move menu items from a
|
||||||
// tab menu to a dropdown.
|
// tab menu to a dropdown.
|
||||||
menuTabs();
|
menuTabs();
|
||||||
|
limitedWidthToggle();
|
||||||
addNamespacesGadgetSupport();
|
addNamespacesGadgetSupport();
|
||||||
if ( document.body.classList.contains( 'vector-feature-visual-enhancement-next-enabled' ) ) {
|
if ( document.body.classList.contains( 'vector-feature-visual-enhancement-next-enabled' ) ) {
|
||||||
watchstar();
|
watchstar();
|
||||||
|
|
|
@ -381,6 +381,7 @@
|
||||||
"mediawiki.page.ready",
|
"mediawiki.page.ready",
|
||||||
"mediawiki.page.watch.ajax",
|
"mediawiki.page.watch.ajax",
|
||||||
"mediawiki.util",
|
"mediawiki.util",
|
||||||
|
"mediawiki.storage",
|
||||||
"mediawiki.experiments"
|
"mediawiki.experiments"
|
||||||
],
|
],
|
||||||
"messages": [
|
"messages": [
|
||||||
|
@ -391,6 +392,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"skins.vector.js": {
|
"skins.vector.js": {
|
||||||
|
"styles": [
|
||||||
|
"resources/skins.vector.js/limitedWidthToggle.less"
|
||||||
|
],
|
||||||
"packageFiles": [
|
"packageFiles": [
|
||||||
"resources/skins.vector.js/skin.js",
|
"resources/skins.vector.js/skin.js",
|
||||||
{
|
{
|
||||||
|
@ -403,6 +407,8 @@
|
||||||
"resources/skins.vector.js/sidebarPersistence.js",
|
"resources/skins.vector.js/sidebarPersistence.js",
|
||||||
"resources/skins.vector.js/languageButton.js",
|
"resources/skins.vector.js/languageButton.js",
|
||||||
"resources/skins.vector.js/echo.js",
|
"resources/skins.vector.js/echo.js",
|
||||||
|
"resources/skins.vector.js/limitedWidthToggle.js",
|
||||||
|
"resources/skins.vector.js/features.js",
|
||||||
"resources/skins.vector.js/searchLoader.js",
|
"resources/skins.vector.js/searchLoader.js",
|
||||||
"resources/skins.vector.js/menuTabs.js"
|
"resources/skins.vector.js/menuTabs.js"
|
||||||
],
|
],
|
||||||
|
|
3
tests/jest/__mocks__/mediawiki.util.js
Normal file
3
tests/jest/__mocks__/mediawiki.util.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
debounce: ( /** @type {Function} */fn ) => fn
|
||||||
|
};
|
34
tests/jest/skins.vector.js/features.test.js
Normal file
34
tests/jest/skins.vector.js/features.test.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
const features = require( '../../../resources/skins.vector.js/features.js' );
|
||||||
|
|
||||||
|
describe( 'features', () => {
|
||||||
|
beforeEach( () => {
|
||||||
|
document.body.setAttribute( 'class', 'vector-feature-foo-disabled vector-feature-bar-enabled hello' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'toggle', () => {
|
||||||
|
features.toggle( 'foo' );
|
||||||
|
features.toggle( 'bar' );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.body.classList.contains( 'vector-feature-foo-enabled' )
|
||||||
|
).toBe( true );
|
||||||
|
expect(
|
||||||
|
document.body.classList.contains( 'vector-feature-foo-disabled' )
|
||||||
|
).toBe( false );
|
||||||
|
expect(
|
||||||
|
document.body.classList.contains( 'vector-feature-bar-disabled' )
|
||||||
|
).toBe( true );
|
||||||
|
expect(
|
||||||
|
document.body.classList.contains( 'vector-feature-bar-enabled' )
|
||||||
|
).toBe( false );
|
||||||
|
expect(
|
||||||
|
document.body.classList.contains( 'hello' )
|
||||||
|
).toBe( true );
|
||||||
|
} );
|
||||||
|
|
||||||
|
test( 'toggle unknown feature', () => {
|
||||||
|
expect( () => {
|
||||||
|
features.toggle( 'unknown' );
|
||||||
|
} ).toThrow();
|
||||||
|
} );
|
||||||
|
} );
|
Loading…
Reference in a new issue