* @param string $legend Legend text for the fieldset
* @param string $section The section content in plain Html
* @param array $attributes Additional attributes for the fieldset
+ * @param bool $isRoot Section is at the root of the tree
* @return string The fieldset's Html
*/
- protected function wrapFieldSetSection( $legend, $section, $attributes ) {
+ protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
return Xml::fieldset( $legend, $section, $attributes ) . "\n";
}
if ( $fieldsetIDPrefix ) {
$attributes['id'] = Sanitizer::escapeIdForAttribute( "$fieldsetIDPrefix$key" );
}
- $subsectionHtml .= $this->wrapFieldSetSection( $legend, $section, $attributes );
+ $subsectionHtml .= $this->wrapFieldSetSection(
+ $legend, $section, $attributes, $fields === $this->mFieldTree
+ );
} else {
// Just return the inputs, nothing fancy.
$subsectionHtml .= $section;
[ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
}
- protected function wrapFieldSetSection( $legend, $section, $attributes ) {
+ protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
// to get a user visible effect, wrap the fieldset into a framed panel layout
$layout = new OOUI\PanelLayout( [
'expanded' => false,
if ( $this->oouiEnabled ) {
$out->addModules( 'mediawiki.special.preferences.ooui' );
$out->addModuleStyles( 'mediawiki.special.preferences.styles.ooui' );
+ $out->addModuleStyles( 'oojs-ui-widgets.styles' );
} else {
$out->addModules( 'mediawiki.special.preferences' );
$out->addModuleStyles( 'mediawiki.special.preferences.styles' );
];
}
$out->addJsConfigVars( 'wgPreferencesTabs', $prefTabs );
-
- // TODO: Render fake tabs here to avoid FOUC.
- // $out->addHTML( $fakeTabs );
} else {
$prefTabs = '';
return $data;
}
+ protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
+ // to get a user visible effect, wrap the fieldset into a framed panel layout
+ if ( $isRoot ) {
+ // Mimic TabPanelLayout
+ $wrapper = new OOUI\PanelLayout( [
+ 'expanded' => false,
+ 'scrollable' => true,
+ // Framed and padded for no-JS, frame hidden with CSS
+ 'framed' => true,
+ 'infusable' => false,
+ 'classes' => [ 'oo-ui-stackLayout oo-ui-indexLayout-stackLayout' ]
+ ] );
+ $layout = new OOUI\PanelLayout( [
+ 'expanded' => false,
+ 'scrollable' => true,
+ 'infusable' => false,
+ 'classes' => [ 'oo-ui-tabPanelLayout' ]
+ ] );
+ $wrapper->appendContent( $layout );
+ } else {
+ $wrapper = $layout = new OOUI\PanelLayout( [
+ 'expanded' => false,
+ 'padded' => true,
+ 'framed' => true,
+ 'infusable' => false,
+ ] );
+ }
+
+ $layout->appendContent(
+ new OOUI\FieldsetLayout( [
+ 'label' => $legend,
+ 'infusable' => false,
+ 'items' => [
+ new OOUI\Widget( [
+ 'content' => new OOUI\HtmlSnippet( $section )
+ ] ),
+ ],
+ ] + $attributes )
+ );
+ return $wrapper;
+ }
+
/**
* Get the whole body of the form.
* @return string
*/
function getBody() {
- return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
+ // Construct fake tabs to avoid FOUC. The structure mimics OOUI's tabPanelLayout.
+ // TODO: Consider creating an infusable TabPanelLayout in OOUI-PHP.
+ $fakeTabs = [];
+ foreach ( $this->getPreferenceSections() as $i => $key ) {
+ $fakeTabs[] =
+ Html::rawElement(
+ 'div',
+ [
+ 'class' =>
+ 'oo-ui-widget oo-ui-widget-enabled oo-ui-optionWidget '.
+ 'oo-ui-tabOptionWidget oo-ui-labelElement' .
+ ( $i === 0 ? ' oo-ui-optionWidget-selected' : '' )
+ ],
+ Html::element(
+ 'a',
+ [
+ 'class' => 'oo-ui-labelElement-label',
+ // Make this a usable link instead of a span so the tabs
+ // can be used before JS runs
+ 'href' => '#mw-prefsection-' . $key
+ ],
+ $this->getLegend( $key )
+ )
+ );
+ }
+ $fakeTabsHtml = Html::rawElement(
+ 'div',
+ [ 'class' => 'oo-ui-layout oo-ui-panelLayout oo-ui-indexLayout-tabPanel' ],
+ Html::rawElement(
+ 'div',
+ [ 'class' => 'oo-ui-widget oo-ui-widget-enabled oo-ui-selectWidget '.
+ 'oo-ui-selectWidget-depressed oo-ui-tabSelectWidget' ],
+ implode( $fakeTabs )
+ )
+ );
+
+ return Html::rawElement(
+ 'div',
+ [ 'class' => 'oo-ui-layout oo-ui-panelLayout oo-ui-panelLayout-framed mw-prefs-faketabs' ],
+ Html::rawElement(
+ 'div',
+ [ 'class' => 'oo-ui-layout oo-ui-menuLayout oo-ui-menuLayout-static ' .
+ 'oo-ui-menuLayout-top oo-ui-menuLayout-showMenu oo-ui-indexLayout' ],
+ Html::rawElement(
+ 'div',
+ [ 'class' => 'oo-ui-menuLayout-menu' ],
+ $fakeTabsHtml
+ ) .
+ Html::rawElement(
+ 'div',
+ [ 'class' => 'oo-ui-menuLayout-content' ],
+ $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' )
+ )
+ )
+ );
}
/**
'oojs-ui-widgets' => [
'class' => ResourceLoaderOOUIFileModule::class,
'scripts' => 'resources/lib/oojs-ui/oojs-ui-widgets.js',
- 'themeStyles' => 'widgets',
'dependencies' => [
'oojs-ui-core',
+ 'oojs-ui-widgets.styles',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-advanced',
],
'targets' => [ 'desktop', 'mobile' ],
],
+ // You should never directly load this module. The CSS classes it defines are not a public API,
+ // they depend on the internal structure of OOUI widgets, which can change at any time. If you
+ // find that you need to load this module, you're probably doing something wrong or very hacky.
+ 'oojs-ui-widgets.styles' => [
+ 'class' => ResourceLoaderOOUIFileModule::class,
+ 'themeStyles' => 'widgets',
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
// Toolbar and tools module.
'oojs-ui-toolbars' => [
'class' => ResourceLoaderOOUIFileModule::class,
} );
wrapper.$element.append( tabs.$element );
$preferences.prepend( wrapper.$element );
+ $( '.mw-prefs-faketabs' ).remove();
function updateHash( panel ) {
var scrollTop, active;
transform: none;
}
-#preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
- border-color: #c8ccd1;
- border-width: 1px 0 0;
+#preferences .oo-ui-menuLayout .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+ border-width: 0;
border-radius: 0;
+ box-shadow: none;
padding-left: 0;
padding-right: 0;
+}
+
+.mw-prefs-faketabs > .oo-ui-menuLayout > .oo-ui-menuLayout-menu a {
+ color: inherit;
+ text-decoration: none;
+}
+
+/* Adjust the borders when JS is disabled: frame each prefsection instead of the
+ * whole tabLayout wrapper */
+.client-nojs #preferences .oo-ui-menuLayout .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
+ border-color: #c8ccd1;
+ border-width: 1px 0 0;
+}
+
+.client-nojs .mw-prefs-faketabs {
+ border-width: 0;
+ border-radius: 0;
box-shadow: none;
}
-/* Tweak the margins to reduce the shifting of form contents
- * after JS code loads and rearranges the page */
-.client-js #preferences > .oo-ui-panelLayout {
- margin: 1em 0;
+.client-nojs .mw-prefs-faketabs > .oo-ui-menuLayout > .oo-ui-menuLayout-content > .oo-ui-stackLayout {
+ margin-bottom: 1em;
}
-.client-js #preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
- margin-left: 0.25em;
+/* Hide the tab menu when JS is disabled as we can't use this feature */
+.client-nojs .mw-prefs-faketabs > .oo-ui-menuLayout > .oo-ui-menuLayout-menu {
+ display: none;
+}
+
+.client-nojs #preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed:last-child {
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+
+/* Hide top level legends when JS is enabled, as they will not be visible
+ * when the real tabLayout is built */
+.client-js #preferences .oo-ui-tabPanelLayout > fieldset > legend {
+ display: none;
}
.client-js #preferences .oo-ui-tabPanelLayout {
padding-top: 0.5em;
- padding-bottom: 0.5em;
}
-.client-js #preferences .oo-ui-tabPanelLayout .oo-ui-panelLayout-framed {
+.client-js #preferences .oo-ui-panelLayout-framed .oo-ui-panelLayout-framed {
margin-left: 0;
margin-bottom: 0;
- border: 0;
- padding-top: 0;
+ padding: 0;
+ border-width: 0;
+ border-radius: 0;
+ box-shadow: none;
}
.client-js #preferences > .oo-ui-panelLayout > .oo-ui-fieldsetLayout > .oo-ui-fieldsetLayout-header {