Merge "Integrate WVUI search into Vector"

This commit is contained in:
jenkins-bot 2020-12-08 22:18:38 +00:00 committed by Gerrit Code Review
commit 3c6c8ebbba
14 changed files with 266 additions and 60 deletions

View File

@ -14,5 +14,9 @@
{
"resourceModule": "skins.vector.legacy.js",
"maxSize": "1.8 kB"
},
{
"resourceModule": "skins.vector.search",
"maxSize": "2.8 kB"
}
]

View File

@ -7,6 +7,7 @@ use ExtensionRegistry;
use HTMLForm;
use MediaWiki\MediaWikiServices;
use OutputPage;
use ResourceLoader;
use ResourceLoaderContext;
use Skin;
use SkinTemplate;
@ -263,6 +264,9 @@ class Hooks {
return;
}
if ( !$out->getConfig()->get( 'VectorUseCoreSearch' ) ) {
$bodyAttrs['class'] .= ' skin-vector-search-vue';
}
$bodyAttrs['class'] .= ' skin-vector-max-width';
// As of 2020/08/12, the following CSS classes are referred to by the following deployed
@ -329,4 +333,39 @@ class Hooks {
private static function isSkinVersionLegacy(): bool {
return !VectorServices::getFeatureManager()->isFeatureEnabled( Constants::FEATURE_LATEST_SKIN );
}
/**
* ResourceLoaderRegisterModules hook handler
*
* Register the new search module.
* This hook will be removed when wvui is available in core when the patch
* https://gerrit.wikimedia.org/r/c/mediawiki/core/+/641052 is merged.
*
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules
* @param ResourceLoader $rl
*/
public static function onResourceLoaderRegisterModules( ResourceLoader $rl ) {
if ( $rl->isModuleRegistered( 'wvui' ) ) {
$module = [
'localBasePath' => __DIR__ . '/..',
'remoteExtPath' => 'Vector',
'dependencies' => [
'mediawiki.util',
],
"packageFiles" => [
"resources/skins.vector.search/skins.vector.search.js",
"resources/skins.vector.search/App.vue"
],
"dependencies" => [
"wvui"
],
"messages" => [
"search",
"searchresults",
"searchsuggest-containing"
],
];
$rl->register( 'skins.vector.search', $module );
}
}
}

View File

@ -31,7 +31,8 @@
"MediaWikiPageReadyModule": "https://doc.wikimedia.org/mediawiki-core/master/js/#!/api/mw.plugin.page.ready",
"JQueryStatic": "https://api.jquery.com",
"VectorResourceLoaderVirtualConfig": "#",
"void": "#"
"void": "#",
"Vue.VNode": "https://vuejs.org/v2/api/#VNode-Interface"
}
}
}

67
package-lock.json generated
View File

@ -2429,8 +2429,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true,
"optional": true
"dev": true
},
"assign-symbols": {
"version": "1.0.0",
@ -5953,8 +5952,7 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true,
"optional": true
"dev": true
},
"fast-deep-equal": {
"version": "2.0.1",
@ -6456,8 +6454,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -6478,14 +6475,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -6500,20 +6495,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -6630,8 +6622,7 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -6643,7 +6634,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -6658,7 +6648,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -6666,14 +6655,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -6692,7 +6679,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -6782,8 +6768,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -6795,7 +6780,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6881,8 +6865,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6918,7 +6901,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6938,7 +6920,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -6982,14 +6963,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -8315,8 +8294,7 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true,
"optional": true
"dev": true
},
"jsdoc": {
"version": "3.6.3",
@ -9847,7 +9825,7 @@
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
@ -9998,7 +9976,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
@ -11645,7 +11623,7 @@
},
"safe-regex": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {
@ -13357,7 +13335,7 @@
},
"through": {
"version": "2.3.8",
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
@ -13516,8 +13494,7 @@
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true,
"optional": true
"dev": true
},
"type-check": {
"version": "0.4.0",
@ -14042,6 +14019,12 @@
"integrity": "sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==",
"dev": true
},
"vue": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==",
"dev": true
},
"vue-eslint-parser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.1.0.tgz",

View File

@ -36,6 +36,7 @@
"pre-commit": "1.2.2",
"stylelint-config-wikimedia": "0.10.3",
"svgo": "1.3.2",
"typescript": "3.8.3"
"typescript": "3.8.3",
"vue": "2.6.11"
}
}

View File

@ -24,10 +24,7 @@ var /** @type {VectorResourceLoaderVirtualConfig} */
LOAD_MEASURE = 'mwVectorVueSearchLoadStartToLoadEnd',
SEARCH_FORM_ID = 'simpleSearch',
SEARCH_INPUT_ID = 'searchInput',
SEARCH_LOADING_CLASS = 'search-form__loader',
SEARCH_MODULE_NAME = config.wgVectorUseCoreSearch ?
'mediawiki.searchSuggest' :
'skins.vector.search';
SEARCH_LOADING_CLASS = 'search-form__loader';
/**
* Loads the search module via `mw.loader.using` on the element's
@ -140,18 +137,23 @@ function initSearchLoader( document ) {
}
/**
* 1. If we're using the search module from MediaWiki Core (searchSuggest),
* load the module.
* 1. If $wgVectorUseCoreSearch is true,
* or we are in a browser that doesn't support fetch
* load the legacy searchSuggest module. The check for window.fetch
* can be removed when IE11 support is finally officially dropped.
* 2. If we're using a different search module, enable the loading indicator
* before the search module loads.
**/
if ( config.wgVectorUseCoreSearch ) {
loadSearchModule( searchInput, SEARCH_MODULE_NAME, function () {} );
if ( config.wgVectorUseCoreSearch || !window.fetch ) {
loadSearchModule( searchInput, 'mediawiki.searchSuggest', function () {} );
} else {
// Remove tooltips while Vue search is still loading
searchInput.setAttribute( 'autocomplete', 'off' );
searchInput.removeAttribute( 'title' );
setLoadingIndicatorListeners( searchForm, true, renderSearchLoadingIndicator );
loadSearchModule(
searchInput,
SEARCH_MODULE_NAME,
'skins.vector.search',
function () {
markLoadEnd();

View File

@ -0,0 +1,86 @@
<template>
<div id="p-search">
<wvui-typeahead-search
id="searchform"
ref="searchForm"
:domain="domain"
:footer-search-text="$i18n('searchsuggest-containing').escaped()"
:suggestions-label="$i18n('searchresults').escaped()"
:accesskey="searchAccessKey"
:title="searchTitle"
:placeholder="searchPlaceholder"
:aria-label="searchPlaceholder"
:initial-input-value="searchQuery"
:button-label="$i18n( 'search' ).escaped()"
:form-action="action"
:search-language="language"
>
<input type="hidden"
name="title"
value="Special:Search"
>
</wvui-typeahead-search>
</div>
</template>
<script>
var wvui = require( 'wvui' );
module.exports = {
name: 'App',
components: wvui,
mounted: function () {
// access the element associated with the wvui-typeahead-search component
// eslint-disable-next-line no-jquery/variable-pattern
var wvuiSearchForm = this.$refs.searchForm.$el;
if ( this.autofocusInput ) {
// TODO: The wvui-typeahead-search component accepts an id prop but does not
// display that value as an HTML attribute on the form element.
wvuiSearchForm.querySelector( 'form' ).setAttribute( 'id', 'searchform' );
// TODO: The wvui-typeahead-search component does not accept an autofocus parameter
// or directive. This can be removed when its does.
wvuiSearchForm.querySelector( 'input' ).focus();
}
},
computed: {
language: function () {
return mw.config.get( 'wgUserLanguage' );
},
domain: function () {
// It might be helpful to allow this to be configurable in future.
return location.host;
}
},
props: {
autofocusInput: {
type: Boolean,
default: false
},
action: {
type: String,
default: ''
},
/** The keyboard shortcut to focus search. */
searchAccessKey: {
type: String
},
/** The access key informational tip for search. */
searchTitle: {
type: String
},
/** The ghost text shown when no search query is entered. */
searchPlaceholder: {
type: String
},
/**
* The search query string taken from the server-side rendered input immediately before
* client render.
*/
searchQuery: {
type: String
}
}
};
</script>

View File

@ -0,0 +1,45 @@
var
Vue = require( 'vue' ).default || require( 'vue' ),
App = require( './App.vue' );
/**
* @param {HTMLElement} searchForm
* @param {HTMLInputElement} search
* @return {void}
*/
function initApp( searchForm, search ) {
// eslint-disable-next-line no-new
new Vue( {
el: '#p-search',
/**
*
* @param {Function} createElement
* @return {Vue.VNode}
*/
render: function ( createElement ) {
return createElement( App, {
props: {
autofocusInput: search === document.activeElement,
action: searchForm.getAttribute( 'action' ),
searchAccessKey: search.getAttribute( 'accessKey' ),
searchTitle: search.getAttribute( 'title' ),
searchPlaceholder: search.getAttribute( 'placeholder' ),
searchQuery: search.value
}
} );
}
} );
}
/**
* @param {Document} document
* @return {void}
*/
function main( document ) {
var
searchForm = /** @type {HTMLElement} */ ( document.querySelector( '#searchform' ) ),
search = /** @type {HTMLInputElement|null} */ ( document.getElementById( 'searchInput' ) );
if ( search && searchForm ) {
initApp( searchForm, search );
}
}
main( document );

View File

@ -0,0 +1,42 @@
@import 'SearchBox.less';
/**
* Minimal styling for initial no-JS server-rendered
* search form, which gets replaced by WVUI on focus.
* Most values are hard-coded since they aim to
* mimic WVUI-specific variables and disable the ResourceLoader LESS transformation of `calc`.
*/
// Parent class can be removed when $wgVectorUseCoreSearch is no longer supported.
.skin-vector-search-vue {
// Position search in header.
#searchInput {
padding-left: 36px;
font-size: inherit;
.transition( none );
// Recreate WVUI expanding input.
&:focus {
position: relative;
// Use ~ and fixed values to disable the LESS transformation in ResourceLoader LESS implementation
padding-left: ~'calc(12px + 2.57142857em + 12px)';
width: ~'calc( 100% + 24px )';
left: ~'calc( -1 * 24px )';
}
}
// Move & resize search icon to match WVUI.
#searchButton {
top: 0;
right: auto;
left: 0;
width: 36px;
min-height: 36px;
background-size: 20px auto;
}
// Reposition search icon for expanded input.
#searchInput:focus ~ #searchButton {
left: -9px;
}
}

View File

@ -38,6 +38,8 @@ body {
flex-wrap: wrap;
// https://caniuse.com/#search=align-items
align-items: center;
// allow z-index to apply so search results overlay article
position: relative;
z-index: @z-index-header;
}

View File

@ -12,7 +12,7 @@
@import 'Indicators.less';
@import 'SiteNotice.less';
@import 'Menu.less';
@import 'SearchBox.less';
@import 'VueEnhancedSearchBox.less';
@import 'SearchBoxLoader.less';
@import 'MenuTabs.less';
@import 'TabWatchstarLink.less';

4
resources/vue.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "*.vue" {
import Vue from 'vue';
export default Vue;
}

View File

@ -66,6 +66,7 @@
"SkinPageReadyConfig": "Vector\\Hooks::onSkinPageReadyConfig",
"GetPreferences": "Vector\\Hooks::onGetPreferences",
"PreferencesFormPreSave": "Vector\\Hooks::onPreferencesFormPreSave",
"ResourceLoaderRegisterModules": "Vector\\Hooks::onResourceLoaderRegisterModules",
"SkinTemplateNavigation::Universal": "Vector\\Hooks::onSkinTemplateNavigation",
"LocalUserCreated": "Vector\\Hooks::onLocalUserCreated",
"OutputPageBodyAttributes": "Vector\\Hooks::onOutputPageBodyAttributes",
@ -111,11 +112,6 @@
],
"styles": [ "resources/skins.vector.styles.responsive.less" ]
},
"skins.vector.search": {
"dependencies": [
"vue"
]
},
"skins.vector.js": {
"packageFiles": [
"resources/skins.vector.js/skin.js",

View File

@ -117,7 +117,8 @@
// See skinStyles/jquery.ui/jquery.ui.datepicker.css.
// @z-index-ui-datepicker-cover: -1;
@z-index-base: 0;
@z-index-header: 1;
// Header z-index-header higher than z-index-menu so that search results overlay variants and more menu
@z-index-header: 3;
@z-index-sidebar: 1;
@z-index-menu-checkbox: 1;
@z-index-search-button: 1;