Monobook Mustache

Changes in HTML markup that fix various bugs and lead to consistencies
with other skins:
* firstHeading now has `dir` attribute
* `tagline` message no longer parsed - plain text only - this is consistent with
other skins
* printfooter now child of #bodyContent
* #ca-view is outputted (but hidden with CSS)
* Order of attributes on  #p-search-label changed
* Search input form elements are no longer self closing
* The #mw-searchButton element gains class mw-fallbackSearchButton
* The generated-sidebar class is no longer present on sidebar portlets,
consistent with other skins
* The print link disappears when ElectronPdf is installed so there
are not two print links.

Changes in functionality:
* Previously (in getCactions) a nomobile class would be added if
less than 2 tabs.
If not 1 tab, more would be appended. This is dropped.

Bug: T285989
Change-Id: I03d0dc1dad23894e7e64ceeb8956692316265144
This commit is contained in:
jdlrobson 2020-03-27 16:21:25 -07:00
parent 1810f569a9
commit abe94aa408
9 changed files with 187 additions and 588 deletions

View file

@ -11,6 +11,7 @@
"monobook-jumptonavigation": "Jump to navigation",
"monobook-jumptosearch": "Jump to search",
"monobook-more-actions": "More",
"cactions-mobile": "Page actions",
"monobook-cactions-label": "Page actions",
"monobook-notifications-link": "Notifications ($1)",
"monobook-notifications-link-none": "Notifications"

View file

@ -21,6 +21,7 @@
"monobook-jumptonavigation": "Accessibility link for jumping to the navigation links. Visually hidden by default.\n\nSee also\n* {{msg-mw|Navigation}}\n\n{{Identical|jumptonavigation}}\n\njay94ks:\nMaybe this translation context is duplicated. :)\nI've found the perfectly same thing even description also same.\n* MediaWiki:Vector-jumptonavigation/ko - This context must be filled out with same content.",
"monobook-jumptosearch": "Accessibility link for jumping to the site search. Visually hidden by default.\n\nSee also\n* {{msg-mw|Search}}\n\n{{Identical|jumptosearch}}",
"monobook-more-actions": "Label for the less-important or rarer actions that are hidden from the usual tabs on mobile interfaces (like moving the page, or for sysops deleting or protecting the page). {{Identical|More}}",
"cactions-mobile": "Header for the content actions menu (tabs on the top of the page)",
"monobook-cactions-label": "Header for the content actions menu (tabs on the top of the page)",
"monobook-notifications-link": "Label for Extension:Notifications link in mobile personal toolbar\n\nParameters:\n* $1 - number of current alerts/notifications",
"monobook-notifications-link-none": "Label for Extension:Notifications link in mobile personal toolbar when no notifications present\n{{Identical|Notification}}"

View file

@ -24,6 +24,7 @@ namespace MonoBook;
use OutputPage;
use Skin;
use SkinTemplate;
class Hooks {
/**
@ -48,4 +49,51 @@ class Hooks {
$bodyAttrs['class'] .= ' monobook-capitalize-all-nouns';
}
}
/**
* SkinTemplateNavigationUniversal hook handler
*
* @param SkinTemplate $skin
* @param array &$content_navigation
*/
public static function onSkinTemplateNavigationUniversal( SkinTemplate $skin, array &$content_navigation ) {
$title = $skin->getTitle();
if ( $skin->getSkinName() === 'monobook' ) {
$tabs = [];
$namespaces = $content_navigation['namespaces'];
foreach ( $namespaces as $nsid => $attribs ) {
$id = $nsid . '-mobile';
$tabs[$id] = [] + $attribs;
$tabs[$id]['title'] = $attribs['text'];
$tabs[$id]['id'] = $id;
}
if ( !$title->isSpecialPage() ) {
$tabs['more'] = [
'text' => $skin->msg( 'monobook-more-actions' )->text(),
'href' => '#p-cactions',
'id' => 'ca-more'
];
}
$tabs['toolbox'] = [
'text' => $skin->msg( 'toolbox' )->text(),
'href' => '#p-tb',
'id' => 'ca-tools',
'title' => $skin->msg( 'toolbox' )->text()
];
$languages = $skin->getLanguages();
if ( count( $languages ) > 0 ) {
$tabs['languages'] = [
'text' => $skin->msg( 'otherlanguages' )->text(),
'href' => '#p-lang',
'id' => 'ca-languages',
'title' => $skin->msg( 'otherlanguages' )->text()
];
}
$content_navigation['cactions-mobile'] = $tabs;
}
}
}

View file

@ -1,583 +0,0 @@
<?php
/**
* MonoBook nouveau.
*
* Translated from gwicke's previous TAL template version to remove
* dependency on PHPTAL.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Skins
*/
namespace MonoBook;
use BaseTemplate;
use Html;
use Linker;
use Sanitizer;
/**
* @ingroup Skins
*/
class MonoBookTemplate extends BaseTemplate {
/**
* Template filter callback for MonoBook skin.
* Takes an associative array of data set from a SkinTemplate-based
* class, and a wrapper for MediaWiki's localization database, and
* outputs a formatted page.
*/
public function execute() {
// Open html, body elements, etc
$html = $this->get( 'headelement' );
$html .= Html::openElement( 'div', [ 'id' => 'globalWrapper' ] );
$html .= Html::openElement( 'div', [ 'id' => 'column-content' ] );
$html .= Html::rawElement( 'div', [ 'id' => 'content', 'class' => 'mw-body', 'role' => 'main' ],
Html::element( 'a', [ 'id' => 'top' ] ) .
$this->getIfExists( 'sitenotice', [
'wrapper' => 'div',
'parameters' => [ 'id' => 'siteNotice' ]
] ) .
$this->getIndicators() .
$this->getIfExists( 'title', [
'loose' => true,
'wrapper' => 'h1',
'parameters' => [
'id' => 'firstHeading',
'class' => 'firstHeading',
'lang' => $this->getSkin()->getTitle()->getPageViewLanguage()->getHtmlCode()
]
] ) .
Html::rawElement( 'div', [ 'id' => 'bodyContent', 'class' => 'monobook-body' ],
Html::rawElement( 'div', [ 'id' => 'siteSub' ], $this->getMsg( 'tagline' )->parse() ) .
Html::rawElement(
'div',
[ 'id' => 'contentSub', 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ],
$this->get( 'subtitle' )
) .
$this->getIfExists( 'undelete', [ 'wrapper' => 'div', 'parameters' => [
'id' => 'contentSub2'
] ] ) .
$this->getIfExists( 'newtalk', [ 'wrapper' => 'div', 'parameters' => [
'class' => 'usermessage'
] ] ) .
Html::element( 'div', [ 'id' => 'jump-to-nav' ] ) .
Html::element( 'a', [ 'href' => '#column-one', 'class' => 'mw-jump-link' ],
$this->getMsg( 'monobook-jumptonavigation' )->text()
) .
Html::element( 'a', [ 'href' => '#searchInput', 'class' => 'mw-jump-link' ],
$this->getMsg( 'monobook-jumptosearch' )->text()
) .
'<!-- start content -->' .
$this->get( 'bodytext' ) .
$this->getIfExists( 'catlinks' ) .
'<!-- end content -->' .
$this->getClear()
)
);
$html .= $this->getIfExists( 'dataAfterContent' ) . $this->getClear();
$html .= Html::closeElement( 'div' );
$html .= Html::rawElement( 'div',
[
'id' => 'column-one',
'lang' => $this->get( 'userlang' ),
'dir' => $this->get( 'dir' )
],
Html::element( 'h2', [], $this->getMsg( 'navigation-heading' )->text() ) .
$this->getCactions() .
$this->getBox( 'personal', $this->getPersonalTools(), 'personaltools' ) .
Html::rawElement( 'div', [ 'class' => 'portlet', 'id' => 'p-logo', 'role' => 'banner' ],
Html::element( 'a',
[
'href' => $this->data['nav_urls']['mainpage']['href'],
'class' => 'mw-wiki-logo',
]
+ Linker::tooltipAndAccesskeyAttribs( 'p-logo' )
)
) .
Html::rawElement( 'div', [ 'id' => 'sidebar' ], $this->getRenderedSidebar() ) .
$this->getMobileNavigationIcon(
'sidebar',
$this->getMsg( 'jumptonavigation' )->text()
) .
$this->getMobileNavigationIcon(
'p-personal',
$this->getMsg( 'monobook-jumptopersonal' )->text()
) .
$this->getMobileNavigationIcon(
'globalWrapper',
$this->getMsg( 'monobook-jumptotop' )->text()
)
);
$html .= '<!-- end of the left (by default at least) column -->';
$html .= $this->getClear();
$html .= $this->getSimpleFooter();
$html .= Html::closeElement( 'div' );
$html .= $this->getTrail();
$html .= Html::closeElement( 'body' );
$html .= Html::closeElement( 'html' );
// The unholy echo
echo $html;
}
/**
* Create a wrapped link to create a mobile toggle/jump icon
* Needs to be an on-page link (as opposed to drawing something on the fly for an
* onclick event) for no-js support.
*
* @param string $target link target
* @param string $title icon title
*
* @return string html empty link block
*/
protected function getMobileNavigationIcon( $target, $title ) {
return Html::element( 'a', [
'href' => "#$target",
'title' => $title,
'class' => 'menu-toggle',
'id' => "$target-toggle"
] );
}
/**
* Generate the cactions (content actions) tabs, as well as a second set of spoof tabs for mobile
*
* @return string html
*/
protected function getCactions() {
$html = '';
$allTabs = $this->data['content_actions'];
$tabCount = count( $allTabs );
// Normal cactions
if ( $tabCount > 2 ) {
$html .= $this->getBox( 'cactions', $allTabs, 'monobook-cactions-label' );
} else {
// Is redundant with spoof, hide normal cactions entirely in mobile
$html .= $this->getBox( 'cactions', $allTabs, 'monobook-cactions-label',
[ 'extra-classes' => 'nomobile' ]
);
}
// Mobile cactions tabs
$tabs = $this->data['content_navigation']['namespaces'];
foreach ( $tabs as $tab => $attribs ) {
$tabs[$tab]['id'] = $attribs['id'] . '-mobile';
$tabs[$tab]['title'] = $attribs['text'];
}
if ( $tabCount !== 1 ) {
// Is not special page or stuff, append a 'more'
$tabs['more'] = [
'text' => $this->getMsg( 'monobook-more-actions' )->text(),
'href' => '#p-cactions',
'id' => 'ca-more'
];
}
$tabs['toolbox'] = [
'text' => $this->getMsg( 'toolbox' )->text(),
'href' => '#p-tb',
'id' => 'ca-tools',
'title' => $this->getMsg( 'toolbox' )->text()
];
$languages = $this->data['sidebar']['LANGUAGES'];
if ( $languages !== false ) {
$tabs['languages'] = [
'text' => $this->getMsg( 'otherlanguages' )->text(),
'href' => '#p-lang',
'id' => 'ca-languages',
'title' => $this->getMsg( 'otherlanguages' )->text()
];
}
$html .= $this->getBox( 'cactions-mobile', $tabs, 'monobook-cactions-label' );
return $html;
}
/**
* Generate the full sidebar
*
* @return string html
* @suppress PhanTypeMismatchArgument $content is an array
* even though we are comparing it to boolean
*/
protected function getRenderedSidebar() {
$sidebar = $this->data['sidebar'];
$html = '';
$languagesHTML = '';
if ( !isset( $sidebar['SEARCH'] ) ) {
$sidebar['SEARCH'] = true;
}
foreach ( $sidebar as $boxName => $content ) {
if ( $content === false ) {
continue;
}
// Numeric strings gets an integer when set as key, cast back - T73639
$boxName = (string)$boxName;
if ( $boxName == 'SEARCH' ) {
$html .= $this->getSearchBox();
} elseif ( $boxName == 'TOOLBOX' ) {
$html .= $this->getToolboxBox( $content );
} elseif ( $boxName == 'LANGUAGES' ) {
$languagesHTML = $this->getLanguageBox( $content );
} else {
$html .= $this->getBox(
$boxName,
$content,
null,
[ 'extra-classes' => 'generated-sidebar' ]
);
}
}
// Output language portal last given it can be long
// on articles which support multiple languages (T254546)
return $html . $languagesHTML;
}
/**
* Generate the search button
*
* @return string html
*/
protected function getSearchBox() {
$html = '';
$optionButtons = "\u{00A0} " . $this->makeSearchButton(
'fulltext',
[ 'id' => 'mw-searchButton', 'class' => 'searchButton' ]
);
$searchInputId = 'searchInput';
$searchForm = Html::rawElement( 'form', [
'action' => $this->get( 'wgScript' ),
'id' => 'searchform'
],
Html::hidden( 'title', $this->get( 'searchtitle' ) ) .
$this->makeSearchInput( [ 'id' => $searchInputId ] ) .
$this->makeSearchButton( 'go', [ 'id' => 'searchButton', 'class' => 'searchButton' ] ) .
$optionButtons
);
$html .= $this->getBox( 'search', $searchForm, null, [
'search-input-id' => $searchInputId,
'role' => 'search',
'body-id' => 'searchBody'
] );
return $html;
}
/**
* Generate the toolbox, complete with all three old hooks
*
* @param array $toolboxItems
* @return string html
*/
protected function getToolboxBox( $toolboxItems ) {
$html = '';
$html .= $this->getBox( 'tb', $toolboxItems, 'toolbox' );
return $html;
}
/**
* Generate the languages box
*
* @param array $languages Interwiki language links
* @return string html
*/
protected function getLanguageBox( $languages ) {
$html = '';
$name = 'lang';
if (
$languages !== [] ||
// Check getAfterPortlet to make sure the languages are shown
// when empty but something has been injected in the portal. (T252841)
$this->getAfterPortletHTML( $name )
) {
$html .= $this->getBox( $name, $languages, 'otherlanguages' );
}
return $html;
}
/**
* Generate a sidebar box using getPortlet(); prefill some common stuff
*
* @param string $name
* @param array|string $contents
* @param-taint $contents escapes_htmlnoent
* @param null|string|array|bool $msg
* @param array $setOptions
*
* @return string html
*/
protected function getBox( $name, $contents, $msg = null, $setOptions = [] ) {
$options = array_merge( [
'class' => 'portlet',
'body-class' => 'pBody',
'text-wrapper' => ''
], $setOptions );
return $this->getPortlet( $name, $contents, $msg, $options );
}
/**
* Generates a block of navigation links with a header
*
* @param string $name
* @param array|string $content array of links for use with makeListItem, or a block of text
* @param null|string|array $msg
* @param array $setOptions random crap to rename/do/whatever
*
* @return string html
* @suppress PhanTypeMismatchArgumentNullable Many false positives
*/
protected function getPortlet( $name, $content, $msg = null, $setOptions = [] ) {
// random stuff to override with any provided options
$options = array_merge( [
// handle role=search a little differently
'role' => 'navigation',
'search-input-id' => 'searchInput',
// extra classes/ids
'id' => 'p-' . $name,
'class' => 'mw-portlet',
'extra-classes' => '',
'body-id' => null,
'body-class' => 'mw-portlet-body',
'body-extra-classes' => '',
// wrapper for individual list items
'text-wrapper' => [ 'tag' => 'span' ],
], $setOptions );
// Handle the different $msg possibilities
if ( $msg === null ) {
$msg = $name;
$msgParams = [];
} elseif ( is_array( $msg ) ) {
$msgString = array_shift( $msg );
$msgParams = $msg;
$msg = $msgString;
} else {
$msgParams = [];
}
$msgObj = $this->getMsg( $msg, $msgParams );
if ( $msgObj->exists() ) {
$msgString = $msgObj->parse();
} else {
$msgString = htmlspecialchars( $msg );
}
$labelId = Sanitizer::escapeIdForAttribute( "p-$name-label" );
if ( is_array( $content ) ) {
$contentText = Html::openElement( 'ul',
[ 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ]
);
foreach ( $content as $key => $item ) {
if ( is_array( $options['text-wrapper'] ) ) {
$contentText .= $this->makeListItem(
$key,
$item,
[ 'text-wrapper' => $options['text-wrapper'] ]
);
} else {
$contentText .= $this->makeListItem(
$key,
$item
);
}
}
$contentText .= Html::closeElement( 'ul' );
} else {
$contentText = $content;
}
// Special handling for role=search
$divOptions = [
'role' => $options['role'],
'class' => $this->mergeClasses( $options['class'], $options['extra-classes'] ),
'id' => Sanitizer::escapeIdForAttribute( $options['id'] ),
'title' => Linker::titleAttrib( $options['id'] )
];
if ( $options['role'] !== 'search' ) {
$divOptions['aria-labelledby'] = $labelId;
}
$labelOptions = [
'id' => $labelId,
'lang' => $this->get( 'userlang' ),
'dir' => $this->get( 'dir' )
];
if ( $options['role'] == 'search' ) {
$msgString = Html::rawElement( 'label', [ 'for' => $options['search-input-id'] ], $msgString );
}
$bodyDivOptions = [
'class' => $this->mergeClasses( $options['body-class'], $options['body-extra-classes'] )
];
if ( is_string( $options['body-id'] ) ) {
$bodyDivOptions['id'] = $options['body-id'];
}
$html = Html::rawElement( 'div', $divOptions,
Html::rawElement( 'h3', $labelOptions, $msgString ) .
Html::rawElement( 'div', $bodyDivOptions,
$contentText . $this->getAfterPortletHTML( $name )
)
);
return $html;
}
/**
* Helper function for getPortlet
*
* Merge all provided css classes into a single array
* Account for possible different input methods matching what Html::element stuff takes
*
* @param string|array $class base portlet/body class
* @param string|array $extraClasses any extra classes to also include
*
* @return array all classes to apply
*/
protected function mergeClasses( $class, $extraClasses ) {
if ( !is_array( $class ) ) {
$class = [ $class ];
}
if ( !is_array( $extraClasses ) ) {
$extraClasses = [ $extraClasses ];
}
return array_merge( $class, $extraClasses );
}
/**
* Simple wrapper for random if-statement-wrapped $this->data things
*
* @param string $object name of thing
* @param array $setOptions
*
* @return string html
*/
protected function getIfExists( $object, $setOptions = [] ) {
$options = [
'loose' => false,
'wrapper' => 'none',
'parameters' => []
];
foreach ( $setOptions as $key => $value ) {
$options[$key] = $value;
}
$html = '';
if ( ( $options['loose'] && $this->data[$object] != '' ) ||
( !$options['loose'] && $this->data[$object] ) ) {
if ( $options['wrapper'] == 'none' ) {
$html .= $this->get( $object );
} else {
$html .= Html::rawElement(
$options['wrapper'],
$options['parameters'],
$this->get( $object )
);
}
}
return $html;
}
/**
* Renderer for getFooterIcons and getFooterLinks as a generic footer block
*
* @return string html
*/
protected function getSimpleFooter() {
$validFooterIcons = $this->get( 'footericons' );
$validFooterLinks = $this->getFooterLinks( 'flat' );
$html = '';
$html .= Html::openElement( 'div', [
'id' => 'footer',
'class' => 'mw-footer',
'role' => 'contentinfo',
'lang' => $this->get( 'userlang' ),
'dir' => $this->get( 'dir' )
] );
foreach ( $validFooterIcons as $blockName => $footerIcons ) {
$html .= Html::openElement( 'div', [
'id' => Sanitizer::escapeIdForAttribute( "f-{$blockName}ico" ),
'class' => 'footer-icons'
] );
foreach ( $footerIcons as $icon ) {
$html .= $this->getSkin()->makeFooterIcon( $icon );
}
$html .= Html::closeElement( 'div' );
}
if ( count( $validFooterLinks ) > 0 ) {
$html .= Html::openElement( 'ul', [ 'id' => 'f-list' ] );
foreach ( $validFooterLinks as $aLink ) {
$html .= Html::rawElement(
'li',
[ 'id' => Sanitizer::escapeIdForAttribute( $aLink ) ],
$this->get( $aLink )
);
}
$html .= Html::closeElement( 'ul' );
}
$html .= Html::closeElement( 'div' );
return $html;
}
/**
* Gets after portal HTML and wraps it with div and class
*
* @param string $name
* @return string html
*/
private function getAfterPortletHTML( $name ) {
$content = $this->getSkin()->getAfterPortlet( $name );
if ( $content !== '' ) {
return Html::rawElement(
'div',
[ 'class' => [ 'after-portlet', 'after-portlet-' . $name ] ],
$content
);
}
return '';
}
}

View file

@ -1,7 +1,6 @@
@import 'variables.less';
// remove duplicates we're not using here
#sidebar .generated-sidebar,
#p-search-mobilejs,
#p-tb-mobilejs,
#p-lang-mobilejs,
@ -9,6 +8,12 @@
display: none;
}
#sidebar #p-tb,
#sidebar #p-lang,
#sidebar #p-search {
display: block;
}
// popouts
#p-cactions,
#p-personal,

View file

@ -310,6 +310,8 @@ li#ca-print {
margin-left: 1.6em;
}
/* Historically not present in Monobook skin */
#p-cactions li#ca-view,
/*
** mobile toggles; not used here
*/

View file

@ -15,14 +15,28 @@
},
"ValidSkinNames": {
"monobook": {
"class": "SkinTemplate",
"class": "SkinMustache",
"args": [
{
"name": "monobook",
"responsive": true,
"templateDirectory": "templates/",
"scripts": [ "skins.monobook.scripts" ],
"styles": [ "skins.monobook.styles" ],
"responsive": true,
"template": "MonoBook\\MonoBookTemplate"
"messages": [
"tagline",
"nstab-main",
"nstab-talk",
"monobook-more-actions",
"otherlanguages",
"toolbox",
"navigation-heading",
"monobook-jumptotop",
"monobook-jumptopersonal",
"monobook-jumptosearch",
"monobook-cactions-label",
"monobook-jumptonavigation"
]
}
]
}
@ -34,7 +48,8 @@
"monobook": "resources/mediawiki.less"
},
"Hooks": {
"OutputPageBodyAttributes": "MonoBook\\Hooks::onOutputPageBodyAttributes"
"OutputPageBodyAttributes": "MonoBook\\Hooks::onOutputPageBodyAttributes",
"SkinTemplateNavigation::Universal": "MonoBook\\Hooks::onSkinTemplateNavigationUniversal"
},
"MessagesDirs": {
"MonoBook": [

View file

@ -0,0 +1,10 @@
{{! monobook uses a `portlet` class. Standardisation of classes will come later }}
<div role="navigation" class="portlet {{class}}"
id="{{id}}" aria-labelledby="{{id}}-label">
<h3 id="{{id}}-label" {{{html-user-language-attributes}}}>{{label}}</h3>
<div class="pBody">
<ul {{{html-user-language-attributes}}}>{{{html-items}}}{{!
}}</ul>
{{{html-after-portal}}}
</div>
</div>

100
templates/skin.mustache Normal file
View file

@ -0,0 +1,100 @@
<div id="globalWrapper">
<div id="column-content">
<div id="content" class="mw-body" role="main">
<a id="top"></a>
<div id="siteNotice">{{{html-site-notice}}}</div>
<div class="mw-indicators">
{{#array-indicators}}
<div id="{{id}}" class="mw-indicator">{{{html}}}</div>
{{/array-indicators}}
</div>
<h1 id="firstHeading" class="firstHeading"
{{{html-user-language-attributes}}}>{{{html-title}}}</h1>
<div id="bodyContent" class="monobook-body">
<div id="siteSub">{{ msg-tagline }}</div>
<div id="contentSub" {{{html-user-language-attributes}}}>{{{html-subtitle}}}</div>
{{#html-undelete-link}}<div id="contentSub2">{{{.}}}</div>{{/html-undelete-link}}{{{html-newtalk}}}
<div id="jump-to-nav"></div><a href="#column-one" class="mw-jump-link">{{msg-monobook-jumptonavigation}}</a><a href="#searchInput" class="mw-jump-link">{{msg-monobook-jumptosearch}}</a>
<!-- start content -->
{{{html-body-content}}}
{{{html-categories}}}
<!-- end content -->
<div class="visualClear"></div>
</div>
</div>{{{html-after-content}}}
<div class="visualClear"></div>
</div>
<div id="column-one" {{{html-user-language-attributes}}}>
{{#data-portlets}}
<h2>{{msg-navigation-heading}}</h2>
<div role="navigation" class="portlet" id="p-cactions" aria-labelledby="p-cactions-label">
<h3 id="p-cactions-label" {{{html-user-language-attributes}}}>{{msg-monobook-cactions-label}}</h3>
<div class="pBody">
<ul {{{html-user-language-attributes}}}>
{{! comments used to avoid additional whitespace}}
{{{data-namespaces.html-items}}}{{!
}}{{{data-views.html-items}}}{{!
}}{{{data-actions.html-items}}}
{{{data-variants.html-items}}}
</ul>
</div>
</div>
{{#data-cactions-mobile}}{{>Portlet}}{{/data-cactions-mobile}}
{{#data-personal}}
<div role="navigation" class="portlet" id="{{id}}" aria-labelledby="{{id}}-label">
<h3 id="{{id}}-label" {{{html-user-language-attributes}}}>{{label}}</h3>
<div class="pBody">
<ul {{{html-user-language-attributes}}}>
{{{html-items}}}
</ul>
</div>
</div>
{{/data-personal}}
<div class="portlet" id="p-logo" role="banner">
<a href="{{link-mainpage}}" class="mw-wiki-logo"></a>
</div>
<div id="sidebar">
{{/data-portlets}}
{{#data-portlets-sidebar.data-portlets-first}}{{>Portlet}}{{/data-portlets-sidebar.data-portlets-first}}
{{#data-search-box}}
<div role="search" class="portlet" id="p-search">
<h3 id="p-search-label" dir="ltr" lang="en-GB"><label for="searchInput">{{msg-search}}</label></h3>
<div class="pBody" id="searchBody">
<form action="{{form-action}}" id="searchform"><input type="hidden" value="{{page-title}}" name="title">{{{html-input}}}{{{html-button-search}}} {{{html-button-search-fallback}}}</form>
</div>
</div>
{{/data-search-box}}
{{#data-portlets-sidebar.array-portlets-rest}}{{>Portlet}}{{/data-portlets-sidebar.array-portlets-rest}}
{{#data-portlets.data-languages}}{{>Portlet}}{{/data-portlets.data-languages}}
</div>
{{! previously SkinMonobook::getMobileNavigationIcon }}
<a href="#sidebar" title="{{msg-monobook-jumptonavigation}}"
class="menu-toggle" id="sidebar-toggle"></a>
<a href="#p-personal" title="{{msg-monobook-jumptopersonal}}"
class="menu-toggle" id="p-personal-toggle"></a>
<a href="#globalWrapper" title="{{msg-monobook-jumptotop}}"
class="menu-toggle" id="globalWrapper-toggle"></a>
</div>
<!-- end of the left (by default at least) column -->
<div class="visualClear"></div>
<div id="footer" class="mw-footer" role="contentinfo"
{{{html-user-language-attributes}}}>
{{#data-footer}}
{{#data-icons}}
{{#array-items}}
<div id="f-{{name}}ico" class="footer-icons">
{{{html}}}
</div>
{{/array-items}}
{{/data-icons}}
<ul id="f-list">
{{#data-info}}
{{#array-items}}<li id="{{name}}">{{{html}}}</li>{{/array-items}}
{{/data-info}}{{! no whitespace
}}{{#data-places}}{{! no whitespace
}}{{#array-items}}<li id="{{name}}">{{{html}}}</li>{{/array-items}}
{{/data-places}}
</ul>
{{/data-footer}}
</div>
</div>