[JavaScript] Validate types

Lift the mists of confusion by checking that all JavaScript types align.
No ignores! This is the JavaScript equivalent to Phan.

This patch adds the necessary infrastructure for verifying typing and
fixes the few flaws found.

Bug: T239262
Change-Id: I2557471421196ea46cd13dfb786a52968fbfcc97
This commit is contained in:
Stephen Niedzielski 2020-03-09 12:31:36 -06:00
parent 7d42117f7f
commit bd7bd75569
12 changed files with 193 additions and 81 deletions

View File

@ -4,5 +4,9 @@
"wikimedia/client", "wikimedia/client",
"wikimedia/jquery", "wikimedia/jquery",
"wikimedia/mediawiki" "wikimedia/mediawiki"
] ],
"rules": {
// Interferes with @type annotations.
"one-var": "off"
}
} }

View File

@ -3,11 +3,11 @@ Each portal has the following composition:
string portal-id string portal-id
string html-tooltip string html-tooltip
string msg-label-id string msg-label-id
string|null html-userlangattributes string? html-userlangattributes
string msg-label} string msg-label
string html-portal-content string html-portal-content
string|null html-after-portal string? html-after-portal
string|null html-hook-vector-after-toolbox is deprecated and used by the toolbox portal. string? html-hook-vector-after-toolbox is deprecated and used by the toolbox portal.
}} }}
<div class="portal" role="navigation" id="{{portal-id}}" {{{html-tooltip}}} aria-labelledby="{{msg-label-id}}"> <div class="portal" role="navigation" id="{{portal-id}}" {{{html-tooltip}}} aria-labelledby="{{msg-label-id}}">
<h3 {{{html-userlangattributes}}} id="{{msg-label-id}}"> <h3 {{{html-userlangattributes}}} id="{{msg-label-id}}">

84
package-lock.json generated
View File

@ -1455,12 +1455,27 @@
"integrity": "sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==", "integrity": "sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w==",
"dev": true "dev": true
}, },
"@types/jquery": {
"version": "3.3.33",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.33.tgz",
"integrity": "sha512-U6IdXYGkfUI42SR79vB2Spj+h1Ly3J3UZjpd8mi943lh126TK7CB+HZOxGh2nM3IySor7wqVQdemD/xtydsBKA==",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/minimist": { "@types/minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=",
"dev": true "dev": true
}, },
"@types/mustache": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.0.1.tgz",
"integrity": "sha512-wH6Tu9mbiOt0n5EvdoWy0VGQaJMHfLIxY/6wS0xLC7CV1taM6gESEzcYy0ZlWvxxiiljYvfDIvz4hHbUUDRlhw==",
"dev": true
},
"@types/node": { "@types/node": {
"version": "13.9.1", "version": "13.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.1.tgz",
@ -1529,6 +1544,12 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
"integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==",
"dev": true
},
"@types/unist": { "@types/unist": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
@ -2125,8 +2146,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true, "dev": true
"optional": true
}, },
"assign-symbols": { "assign-symbols": {
"version": "1.0.0", "version": "1.0.0",
@ -3430,7 +3450,6 @@
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
} }
@ -3938,8 +3957,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true, "dev": true
"optional": true
}, },
"delegate": { "delegate": {
"version": "3.2.0", "version": "3.2.0",
@ -5148,8 +5166,7 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true, "dev": true
"optional": true
}, },
"fast-deep-equal": { "fast-deep-equal": {
"version": "2.0.1", "version": "2.0.1",
@ -5613,8 +5630,7 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -5635,14 +5651,12 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -5657,20 +5671,17 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -5787,8 +5798,7 @@
"inherits": { "inherits": {
"version": "2.0.4", "version": "2.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -5800,7 +5810,6 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -5815,7 +5824,6 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -5823,14 +5831,12 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.9.0", "version": "2.9.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -5849,7 +5855,6 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -5939,8 +5944,7 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -5952,7 +5956,6 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -6038,8 +6041,7 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -6075,7 +6077,6 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -6095,7 +6096,6 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -6139,14 +6139,12 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.1.1", "version": "3.1.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true
"optional": true
} }
} }
}, },
@ -7391,8 +7389,7 @@
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true, "dev": true
"optional": true
}, },
"jsdoc": { "jsdoc": {
"version": "3.6.3", "version": "3.6.3",
@ -11871,8 +11868,7 @@
"version": "0.14.5", "version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true, "dev": true
"optional": true
}, },
"type-check": { "type-check": {
"version": "0.3.2", "version": "0.3.2",
@ -11920,6 +11916,12 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"typescript": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
"integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
"dev": true
},
"uc.micro": { "uc.micro": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View File

@ -2,7 +2,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "npm -s test && npm -s run doc", "build": "npm -s test && npm -s run doc",
"test": "npm -s run lint", "test": "npm -s run lint && tsc",
"lint": "npm -s run lint:js && npm -s run lint:styles && npm -s run lint:i18n", "lint": "npm -s run lint:js && npm -s run lint:styles && npm -s run lint:i18n",
"lint:fix": "npm -s run lint:js -- --fix && npm -s run lint:styles -- --fix", "lint:fix": "npm -s run lint:js -- --fix && npm -s run lint:styles -- --fix",
"lint:js": "eslint --cache --max-warnings 0 .", "lint:js": "eslint --cache --max-warnings 0 .",
@ -16,6 +16,8 @@
"devDependencies": { "devDependencies": {
"@babel/core": "7.7.7", "@babel/core": "7.7.7",
"@storybook/html": "5.2.8", "@storybook/html": "5.2.8",
"@types/jquery": "3.3.33",
"@types/mustache": "4.0.1",
"babel-loader": "8.0.6", "babel-loader": "8.0.6",
"eslint-config-wikimedia": "0.15.0", "eslint-config-wikimedia": "0.15.0",
"grunt-banana-checker": "0.8.1", "grunt-banana-checker": "0.8.1",
@ -26,6 +28,7 @@
"mustache": "3.0.1", "mustache": "3.0.1",
"pre-commit": "1.2.2", "pre-commit": "1.2.2",
"stylelint-config-wikimedia": "0.9.0", "stylelint-config-wikimedia": "0.9.0",
"svgo": "1.3.2" "svgo": "1.3.2",
"typescript": "3.8.3"
} }
} }

34
resources/CollapsibleTabsPlugin.d.ts vendored Normal file
View File

@ -0,0 +1,34 @@
interface JQueryStatic {
collapsibleTabs: CollapsibleTabsStatic;
}
interface JQuery {
collapsibleTabs(options: Partial<CollapsibleTabsOptions>): void;
}
/** A jQuery plugin that makes collapsible tabs for the Vector skin. */
interface CollapsibleTabsOptions {
/** Optional tab selector. Defaults to `#p-views ul`. */
expandedContainer: string;
/** Optional menu item selector. Defaults to `#p-cactions ul`. */
collapsedContainer: string;
/** Optional selector for tabs that are collapsible. Defaults to `li.collapsible`. */
collapsible: string;
shifting: boolean;
expandedWidth: number;
expandCondition(eleWidth: number): boolean;
collapseCondition(): boolean;
}
interface CollapsibleTabsStatic {
defaults: CollapsibleTabsOptions;
instances: JQuery[];
addData($collapsible: JQuery): void;
getSettings($collapsible: JQuery): CollapsibleTabsOptions;
handleResize(): void;
moveToCollapsed($moving: JQuery): void;
moveToExpanded($moving: JQuery): void;
calculateTabDistance(): number;
}
interface CollapsibleTabs extends CollapsibleTabsStatic, CollapsibleTabsOptions {}

20
resources/mediawiki.d.ts vendored Normal file
View File

@ -0,0 +1,20 @@
interface MediaWiki {
util: {
/**
* Return a wrapper function that is debounced for the given duration.
*
* When it is first called, a timeout is scheduled. If before the timer
* is reached the wrapper is called again, it gets rescheduled for the
* same duration from now until it stops being called. The original function
* is called from the "tail" of such chain, with the last set of arguments.
*
* @since 1.34
* @param {number} delay Time in milliseconds
* @param {Function} callback
* @return {Function}
*/
debounce(delay: number, callback: Function): () => void;
};
}
declare const mw: MediaWiki;

View File

@ -1,19 +1,9 @@
/** @interface CollapsibleTabsOptions */
( function () { ( function () {
var boundEvent, /** @type {boolean|undefined} */ var boundEvent;
isRTL = document.documentElement.dir === 'rtl', var isRTL = document.documentElement.dir === 'rtl';
rAF = window.requestAnimationFrame || setTimeout; var rAF = window.requestAnimationFrame || setTimeout;
/**
* A jQuery plugin that makes collapsible tabs for the Vector skin.
*
* @class jQuery.plugin.collapsibleTabs
* @param {Object} [options]
* @param {string} [options.expandedContainer=#p-views ul] List of tabs
* @param {string} [options.collapsedContainer=#p-cactions ul] List of menu items
* @param {string} [options.collapsible=li.collapsible] Match tabs that are collapsible
* @param {Function} [options.expandCondition]
* @param {Function} [options.collapseCondition]
*/
$.fn.collapsibleTabs = function ( options ) { $.fn.collapsibleTabs = function ( options ) {
// Merge options into the defaults // Merge options into the defaults
var settings = $.extend( {}, $.collapsibleTabs.defaults, options ); var settings = $.extend( {}, $.collapsibleTabs.defaults, options );
@ -54,6 +44,7 @@
collapsedContainer: '#p-cactions ul', collapsedContainer: '#p-cactions ul',
collapsible: 'li.collapsible', collapsible: 'li.collapsible',
shifting: false, shifting: false,
expandedWidth: 0,
expandCondition: function ( eleWidth ) { expandCondition: function ( eleWidth ) {
// If there are at least eleWidth + 1 pixels of free space, expand. // If there are at least eleWidth + 1 pixels of free space, expand.
// We add 1 because .width() will truncate fractional values but .offset() will not. // We add 1 because .width() will truncate fractional values but .offset() will not.
@ -125,19 +116,21 @@
} ); } );
}, },
moveToCollapsed: function ( $moving ) { moveToCollapsed: function ( $moving ) {
var outerData, expContainerSettings, target; /** @type {CollapsibleTabsOptions} */ var outerData;
/** @type {CollapsibleTabsOptions} */ var collapsedContainerSettings;
/** @type {string} */ var target;
outerData = $.collapsibleTabs.getSettings( $moving ); outerData = $.collapsibleTabs.getSettings( $moving );
if ( !outerData ) { if ( !outerData ) {
return; return;
} }
expContainerSettings = $.collapsibleTabs.getSettings( collapsedContainerSettings = $.collapsibleTabs.getSettings(
$( outerData.expandedContainer ) $( outerData.expandedContainer )
); );
if ( !expContainerSettings ) { if ( !collapsedContainerSettings ) {
return; return;
} }
expContainerSettings.shifting = true; collapsedContainerSettings.shifting = true;
// Remove the element from where it's at and put it in the dropdown menu // Remove the element from where it's at and put it in the dropdown menu
target = outerData.collapsedContainer; target = outerData.collapsedContainer;
@ -150,22 +143,26 @@
$( '<span>' ).addClass( 'placeholder' ).css( 'display', 'none' ).insertAfter( this ); $( '<span>' ).addClass( 'placeholder' ).css( 'display', 'none' ).insertAfter( this );
$( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', outerData ); $( this ).detach().prependTo( target ).data( 'collapsibleTabsSettings', outerData );
$( this ).attr( 'style', 'display: list-item;' ); $( this ).attr( 'style', 'display: list-item;' );
expContainerSettings.shifting = false; collapsedContainerSettings.shifting = false;
rAF( $.collapsibleTabs.handleResize ); rAF( $.collapsibleTabs.handleResize );
} ); } );
}, },
moveToExpanded: function ( $moving ) { moveToExpanded: function ( $moving ) {
var data, expContainerSettings, $target, expandedWidth; /** @type {CollapsibleTabsOptions} */ var data;
/** @type {CollapsibleTabsOptions} */ var expandedContainerSettings;
var $target;
var expandedWidth;
data = $.collapsibleTabs.getSettings( $moving ); data = $.collapsibleTabs.getSettings( $moving );
if ( !data ) { if ( !data ) {
return; return;
} }
expContainerSettings = $.collapsibleTabs.getSettings( $( data.expandedContainer ) ); expandedContainerSettings =
if ( !expContainerSettings ) { $.collapsibleTabs.getSettings( $( data.expandedContainer ) );
if ( !expandedContainerSettings ) {
return; return;
} }
expContainerSettings.shifting = true; expandedContainerSettings.shifting = true;
// grab the next appearing placeholder so we can use it for replacing // grab the next appearing placeholder so we can use it for replacing
$target = $( data.expandedContainer ).find( 'span.placeholder' ).first(); $target = $( data.expandedContainer ).find( 'span.placeholder' ).first();
@ -184,9 +181,9 @@
// change the tab's contents after the page load *gasp* (T71729). This // change the tab's contents after the page load *gasp* (T71729). This
// doesn't prevent a tab from collapsing back and forth once, but at // doesn't prevent a tab from collapsing back and forth once, but at
// least it won't continue to do that forever. // least it won't continue to do that forever.
data.expandedWidth = $moving.width(); data.expandedWidth = $moving.width() || 0;
$moving.data( 'collapsibleTabsSettings', data ); $moving.data( 'collapsibleTabsSettings', data );
expContainerSettings.shifting = false; expandedContainerSettings.shifting = false;
$.collapsibleTabs.handleResize(); $.collapsibleTabs.handleResize();
} ); } );
} ) } )
@ -215,10 +212,12 @@
leftTab = document.getElementById( 'right-navigation' ); leftTab = document.getElementById( 'right-navigation' );
rightTab = document.getElementById( 'left-navigation' ); rightTab = document.getElementById( 'left-navigation' );
} }
if ( leftTab && rightTab ) {
leftEnd = leftTab.getBoundingClientRect().right; leftEnd = leftTab.getBoundingClientRect().right;
rightStart = rightTab.getBoundingClientRect().left; rightStart = rightTab.getBoundingClientRect().left;
return rightStart - leftEnd; return rightStart - leftEnd;
}
return 0;
} }
}; };
}() ); }() );

View File

@ -14,7 +14,7 @@ $( function () {
// must not return 0 if hidden, but rather virtually render it // must not return 0 if hidden, but rather virtually render it
// and compute its width, then hide it again. jQuery width() does // and compute its width, then hide it again. jQuery width() does
// all that for us. // all that for us.
var width = $cactions.width(); var width = $cactions.width() || 0;
initialCactionsWidth = function () { initialCactionsWidth = function () {
return width; return width;
}; };
@ -98,8 +98,8 @@ $( function () {
// 3. and, the left-navigation and right-navigation are overlapping // 3. and, the left-navigation and right-navigation are overlapping
// each other, e.g. when making the window very narrow, or if a gadget // each other, e.g. when making the window very narrow, or if a gadget
// added a lot of tabs. // added a lot of tabs.
$tabContainer.children( 'li.collapsible' ).each( function ( index, element ) { $tabContainer.children( 'li.collapsible' ).each( function ( _index, element ) {
collapsibleWidth += $( element ).width(); collapsibleWidth += $( element ).width() || 0;
if ( collapsibleWidth > initialCactionsWidth() ) { if ( collapsibleWidth > initialCactionsWidth() ) {
// We've found one or more collapsible links that are wider // We've found one or more collapsible links that are wider
// than the "More" menu would be if it were made visible, // than the "More" menu would be if it were made visible,
@ -108,6 +108,7 @@ $( function () {
// Stop this possibly expensive loop the moment the condition is met once. // Stop this possibly expensive loop the moment the condition is met once.
return false; return false;
} }
return;
} ); } );
return doCollapse; return doCollapse;
} }

View File

@ -4,6 +4,22 @@ import '../resources/skins.vector.styles/Portal.less';
import '../.storybook/common.less'; import '../.storybook/common.less';
import { placeholder, htmluserlangattributes } from './utils'; import { placeholder, htmluserlangattributes } from './utils';
/**
* @typedef {Object} PortletContext
* @prop {string} portal-id
* @prop {string} html-tooltip
* @prop {string} msg-label-id
* @prop {string} [html-userlangattributes]
* @prop {string} msg-label
* @prop {string} html-portal-content
* @prop {string} [html-after-portal]
* @prop {string} [html-hook-vector-after-toolbox] Deprecated and used by the toolbox portal.
*/
/**
* @param {PortletContext} data
* @return {HTMLElement}
*/
export const wrapPortlet = ( data ) => { export const wrapPortlet = ( data ) => {
const node = document.createElement( 'div' ); const node = document.createElement( 'div' );
node.setAttribute( 'id', 'mw-panel' ); node.setAttribute( 'id', 'mw-panel' );
@ -11,6 +27,10 @@ export const wrapPortlet = ( data ) => {
return node; return node;
}; };
/**
* @param {string} html
* @return {string}
*/
const portletAfter = ( html ) => { const portletAfter = ( html ) => {
return `<div class="after-portlet after-portlet-tb">${html}</div>`; return `<div class="after-portlet after-portlet-tb">${html}</div>`;
}; };
@ -33,6 +53,7 @@ export const PORTALS = {
}, },
navigation: { navigation: {
'portal-id': 'p-navigation', 'portal-id': 'p-navigation',
'html-tooltip': 'A message tooltip-p-navigation must exist for this to appear',
'msg-label': 'Navigation', 'msg-label': 'Navigation',
'msg-label-id': 'p-navigation-label', 'msg-label-id': 'p-navigation-label',
'html-userlangattributes': htmluserlangattributes, 'html-userlangattributes': htmluserlangattributes,
@ -71,7 +92,7 @@ ${placeholder( `<p>Further hook output possible (lang)</p>`, 60 )}`
}, },
otherProjects: { otherProjects: {
'portal-id': 'p-wikibase-otherprojects', 'portal-id': 'p-wikibase-otherprojects',
'html-tooltip': 'A message tooltip-p-lang must exist for this to appear', 'html-tooltip': 'A message tooltip-p-wikibase-otherprojects must exist for this to appear',
'msg-label': 'In other projects', 'msg-label': 'In other projects',
'msg-label-id': 'p-wikibase-otherprojects-label', 'msg-label-id': 'p-wikibase-otherprojects-label',
'html-userlangattributes': htmluserlangattributes, 'html-userlangattributes': htmluserlangattributes,

4
stories/rawLoader.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "!!raw-loader!*" {
const src: string;
export default src;
}

View File

@ -1,3 +1,8 @@
/**
* @param {string} msg
* @param {number} [height=200]
* @return {string}
*/
const placeholder = ( msg, height ) => { const placeholder = ( msg, height ) => {
return `<div style="width: 100%; height: ${height || 200}px; margin-bottom: 2px; return `<div style="width: 100%; height: ${height || 200}px; margin-bottom: 2px;
font-size: 12px; padding: 8px; box-sizing: border-box; font-size: 12px; padding: 8px; box-sizing: border-box;

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"exclude": ["docs", "vendor"],
"compilerOptions": {
"resolveJsonModule": true,
"esModuleInterop": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"newLine": "lf",
"forceConsistentCasingInFileNames": true,
"pretty": true,
"target": "es5",
"lib": ["dom"],
"allowJs": true,
"checkJs": true,
"noEmit": true
}
}