[Dev] processTemplate used in one place

Moving all the templateParser calls to one function
so its easier to see how the template is composed.

The diff of changes to the stories folder highlight
the internal changes which are:

* html-portals replaced with html-sidebar in main template
* new Sidebar template added which outputs to html-sidebar
* Mention of "MainMenu" replaced with better understood "Sidebar"

This is precursory work to adopt templatePartials

Change-Id: I6b2196e39087f818e774d04b2d1b9ab8cb8816a1
This commit is contained in:
jdlrobson 2020-02-14 12:33:54 -08:00
parent de76ab59c1
commit 698752e38a
9 changed files with 166 additions and 112 deletions

View File

@ -87,6 +87,7 @@ class VectorTemplate extends BaseTemplate {
$htmlHookVectorBeforeFooter = ob_get_contents();
ob_end_clean();
$tp = $this->getTemplateParser();
// Naming conventions for Mustache parameters:
// - Prefix "is" for boolean values.
// - Prefix "msg-" for interface messages.
@ -134,37 +135,43 @@ class VectorTemplate extends BaseTemplate {
'html-debuglog' => $this->get( 'debughtml', '' ),
// From BaseTemplate::getTrail (handles bottom JavaScript)
'html-printtail' => $this->getTrail() . '</body></html>',
'html-footer' => $this->getTemplateParser()->processTemplate( 'Footer', [
'html-footer' => $tp->processTemplate( 'Footer', [
'html-userlangattributes' => $this->get( 'userlangattributes', '' ),
'html-hook-vector-before-footer' => $htmlHookVectorBeforeFooter,
'array-footer-rows' => $this->getTemplateFooterRows(),
] ),
'html-navigation' => $this->getTemplateParser()->processTemplate( 'Navigation', [
'html-navigation' => $tp->processTemplate( 'Navigation', [
'html-navigation-heading' => $this->getMsg( 'navigation-heading' ),
'html-personal-menu' => $this->renderPersonalComponent(),
'html-navigation-left-tabs' => $this->renderNamespacesComponent() .
$this->renderVariantsComponent(),
'html-navigation-right-tabs' => $this->renderViewsComponent()
. $this->renderActionsComponent() . $this->renderSearchComponent(),
'html-logo-attributes' => Xml::expandAttributes(
Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) + [
'class' => 'mw-wiki-logo',
'href' => Skin::makeMainPageUrl(),
]
'html-personal-menu' => $tp->processTemplate( 'PersonalMenu', $this->buildPersonalProps() ),
'html-navigation-left-tabs' =>
$tp->processTemplate( 'VectorTabs', $this->buildNamespacesProps() )
. $tp->processTemplate( 'VectorMenu', $this->buildVariantsProps() ),
'html-navigation-right-tabs' =>
$tp->processTemplate( 'VectorTabs', $this->buildViewsProps() )
. $tp->processTemplate( 'VectorMenu', $this->buildActionsProps() )
. $tp->processTemplate( 'SearchBox', $this->buildSearchProps() ),
'html-sidebar' => $tp->processTemplate( 'Sidebar',
[
'html-logo-attributes' => Xml::expandAttributes(
Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) + [
'class' => 'mw-wiki-logo',
'href' => Skin::makeMainPageUrl(),
]
),
] + $this->buildSidebarProps( $this->data['sidebar'] )
),
'html-portals' => $this->renderPortals( $this->data['sidebar'] ),
] ),
];
// Prepare and output the HTML response
echo $this->getTemplateParser()->processTemplate( 'index', $params );
echo $tp->processTemplate( 'index', $params );
}
/**
* Get rows that make up the footer
* @return array for use in Mustache template describing the footer elements.
*/
private function getTemplateFooterRows() {
private function getTemplateFooterRows() : array {
$footerRows = [];
foreach ( $this->getFooterLinks() as $category => $links ) {
$items = [];
@ -213,10 +220,10 @@ class VectorTemplate extends BaseTemplate {
* Render a series of portals
*
* @param array $portals
* @return string
* @return array
*/
protected function renderPortals( array $portals ) {
$html = '';
private function buildSidebarProps( array $portals ) : array {
$props = [];
// Force the rendering of the following portals
if ( !isset( $portals['TOOLBOX'] ) ) {
$portals['TOOLBOX'] = true;
@ -237,24 +244,29 @@ class VectorTemplate extends BaseTemplate {
case 'SEARCH':
break;
case 'TOOLBOX':
$html .= $this->renderPortal( 'tb', $this->getToolbox(), 'toolbox', 'SkinTemplateToolboxEnd' );
$portal = $this->buildPortalProps( 'tb', $this->getToolbox(), 'toolbox',
'SkinTemplateToolboxEnd' );
ob_start();
Hooks::run( 'VectorAfterToolbox', [], '1.35' );
$html .= ob_get_clean();
$props[] = $portal + [
'html-hook-vector-after-toolbox' => ob_get_clean(),
];
break;
case 'LANGUAGES':
if ( $this->data['language_urls'] !== false ) {
$html .= $this->renderPortal(
$props[] = $this->buildPortalProps(
'lang', $this->data['language_urls'], 'otherlanguages'
);
}
break;
default:
$html .= $this->renderPortal( $name, $content );
$props[] = $this->buildPortalProps( $name, $content );
break;
}
}
return $html;
return [
'array-portals' => $props,
];
}
/**
@ -262,9 +274,9 @@ class VectorTemplate extends BaseTemplate {
* @param array|string $content
* @param null|string $msg
* @param null|string|array $hook
* @return string
* @return array
*/
protected function renderPortal( $name, $content, $msg = null, $hook = null ) {
private function buildPortalProps( $name, $content, $msg = null, $hook = null ) : array {
if ( $msg === null ) {
$msg = $name;
}
@ -300,7 +312,7 @@ class VectorTemplate extends BaseTemplate {
$props['html-portal-content'] = $content;
}
return $this->getTemplateParser()->processTemplate( 'Portal', $props );
return $props;
}
/**
@ -332,9 +344,9 @@ class VectorTemplate extends BaseTemplate {
}
/**
* @return string
* @return array
*/
private function renderNamespacesComponent() {
private function buildNamespacesProps() : array {
$props = [
'tabs-id' => 'p-namespaces',
'empty-portlet' => ( count( $this->data['namespace_urls'] ) == 0 ) ? 'emptyPortlet' : '',
@ -348,13 +360,13 @@ class VectorTemplate extends BaseTemplate {
$props[ 'html-items' ] .= $this->makeListItem( $key, $item );
}
return $this->getTemplateParser()->processTemplate( 'VectorTabs', $props );
return $props;
}
/**
* @return string
* @return array
*/
private function renderVariantsComponent() {
private function buildVariantsProps() : array {
$props = [
'empty-portlet' => ( count( $this->data['variant_urls'] ) == 0 ) ? 'emptyPortlet' : '',
'menu-id' => 'p-variants',
@ -375,13 +387,13 @@ class VectorTemplate extends BaseTemplate {
$props['html-items'] .= $this->makeListItem( $key, $item );
}
return $this->getTemplateParser()->processTemplate( 'VectorMenu', $props );
return $props;
}
/**
* @return string
* @return array
*/
private function renderViewsComponent() {
private function buildViewsProps() : array {
$props = [
'tabs-id' => 'p-views',
'empty-portlet' => ( count( $this->data['view_urls'] ) == 0 ) ? 'emptyPortlet' : '',
@ -397,13 +409,13 @@ class VectorTemplate extends BaseTemplate {
] );
}
return $this->getTemplateParser()->processTemplate( 'VectorTabs', $props );
return $props;
}
/**
* @return string
* @return array
*/
private function renderActionsComponent() {
private function buildActionsProps() : array {
$props = [
'empty-portlet' => ( count( $this->data['action_urls'] ) == 0 ) ? 'emptyPortlet' : '',
'msg-label' => $this->getMsg( 'vector-more-actions' )->text(),
@ -417,13 +429,13 @@ class VectorTemplate extends BaseTemplate {
$props['html-items'] .= $this->makeListItem( $key, $item );
}
return $this->getTemplateParser()->processTemplate( 'VectorMenu', $props );
return $props;
}
/**
* @return string
* @return array
*/
private function renderPersonalComponent() {
private function buildPersonalProps() : array {
$personalTools = $this->getPersonalTools();
$props = [
'empty-portlet' => ( count( $this->data['personal_urls'] ) == 0 ) ? 'emptyPortlet' : '',
@ -452,10 +464,13 @@ class VectorTemplate extends BaseTemplate {
$props['html-personal-tools'] .= $this->makeListItem( $key, $item );
}
return $this->getTemplateParser()->processTemplate( 'PersonalMenu', $props );
return $props;
}
private function renderSearchComponent() {
/**
* @return array
*/
private function buildSearchProps() : array {
$props = [
'searchHeaderAttrsHTML' => $this->data[ 'userlangattributes' ] ?? '',
'searchActionURL' => $this->data[ 'wgScript' ] ?? '',
@ -472,6 +487,6 @@ class VectorTemplate extends BaseTemplate {
),
'searchInputLabel' => $this->getMsg( 'search' )
];
return $this->getTemplateParser()->processTemplate( 'SearchBox', $props );
return $props;
}
}

View File

@ -4,8 +4,7 @@
string html-personal-menu content that appears as the first child of mw-head
string html-navigation-left-tabs that appears inside #left-navigation (namespaces and variants)
string html-navigation-right-tabs that appears inside #right-navigation (page actions and search)
string html-logo-attributes for site logo. Must be used inside tag e.g. `class="logo" lang="en-gb"`
string html-portals for portal(s) that appear in the main menu for Vector
string html-sidebar sidebar HTML
}}
<div id="mw-navigation">
<h2>{{{html-navigation-heading}}}</h2>
@ -18,10 +17,5 @@
{{{html-navigation-right-tabs}}}
</div>
</div>
<div id="mw-panel">
<div id="p-logo" role="banner">
<a {{{html-logo-attributes}}}></a>
</div>
{{{html-portals}}}
</div>
{{{html-sidebar}}}
</div>

View File

@ -1,4 +1,5 @@
{{!
Each portal has the following composition:
string portal-id
string html-tooltip
string msg-label-id
@ -6,8 +7,8 @@
string msg-label}
string html-portal-content
string|null html-after-portal
string|null 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}}">
<h3 {{{html-userlangattributes}}} id="{{msg-label-id}}">
{{msg-label}}
@ -17,3 +18,5 @@
{{{html-after-portal}}}
</div>
</div>
{{! Note: html-hook-vector-after-toolbox is deprecated. }}
{{{html-hook-vector-after-toolbox}}}

View File

@ -0,0 +1,13 @@
{{!
string html-logo-attributes for site logo. Must be used inside tag e.g. `class="logo" lang="en-gb"`
array array-portals contains options for Portal template
}}
<div id="mw-panel">
<div id="p-logo" role="banner">
<a {{{html-logo-attributes}}}></a>
</div>
{{#array-portals}}
{{>Portal}}
{{/array-portals}}
</div>

View File

@ -7,13 +7,10 @@ import { loggedOut, loggedInWithEcho } from './personalNavigation.stories';
import { viewTabs, namespaceTabs } from './tabs.stories';
import { more, variants } from './menu.stories';
import { simpleSearch } from './searchBox.stories';
import { navigationPortal, otherProjects, toolbox, langlinks } from './portal.stories';
import { placeholder } from './utils';
const HOOKINFO = 'Portals can be added, removed or reordered using SidebarBeforeOutput hook';
import { sidebarWithPortals } from './sidebar.stories';
export default {
title: 'Navigation (Header + Main Menu)'
title: 'Navigation (Header + Sidebar)'
};
export const navigationLoggedOutWithVariants = () => mustache.render( navTemplate,
@ -21,12 +18,7 @@ export const navigationLoggedOutWithVariants = () => mustache.render( navTemplat
'html-personalmenu': loggedOut(),
'html-navigation-left-tabs': namespaceTabs() + variants(),
'html-navigation-right-tabs': `${viewTabs()} ${simpleSearch()}`,
'html-portals': `${navigationPortal().innerHTML}
${toolbox().innerHTML}
${otherProjects().innerHTML}
${langlinks().innerHTML}
${placeholder( HOOKINFO, 60 )}
`,
'html-sidebar': sidebarWithPortals(),
'html-navigation-heading': 'Navigation menu',
'html-logo-attributes': `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`
}
@ -37,12 +29,7 @@ export const navigationLoggedInWithMore = () => mustache.render( navTemplate,
'html-personalmenu': loggedInWithEcho(),
'html-navigation-left-tabs': namespaceTabs(),
'html-navigation-right-tabs': `${viewTabs()} ${more()} ${simpleSearch()}`,
'html-portals': `${navigationPortal().innerHTML}
${toolbox().innerHTML}
${otherProjects().innerHTML}
${langlinks().innerHTML}
${placeholder( HOOKINFO, 60 )}
`,
'html-sidebar': sidebarWithPortals(),
'html-navigation-heading': 'Navigation menu',
'html-logo-attributes': `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`
}

View File

@ -4,11 +4,7 @@ import '../resources/skins.vector.styles/navigation.less';
import '../.storybook/common.less';
import { placeholder, htmluserlangattributes } from './utils';
export default {
title: 'Portals'
};
const wrapPortlet = ( data ) => {
export const wrapPortlet = ( data ) => {
const node = document.createElement( 'div' );
node.setAttribute( 'id', 'mw-panel' );
node.innerHTML = mustache.render( portalTemplate, data );
@ -19,8 +15,8 @@ const portletAfter = ( html ) => {
return `<div class="after-portlet after-portlet-tb">${html}</div>`;
};
export const portal = () => wrapPortlet(
{
export const PORTALS = {
example: {
'portal-id': 'p-example',
'html-tooltip': 'Message tooltip-p-example acts as tooltip',
'msg-label': 'Portal title',
@ -34,11 +30,8 @@ export const portal = () => wrapPortlet(
'html-after-portal': portletAfter(
placeholder( `<p>Beware: The <a href="https://codesearch.wmflabs.org/search/?q=BaseTemplateAfterPortlet&i=nope&files=&repos=">BaseTemplateAfterPortlet hook</a> can be used to inject arbitary HTML here for any portlet.</p>`, 60 )
)
}
);
export const navigationPortal = () => wrapPortlet(
{
},
navigation: {
'portal-id': 'p-navigation',
'msg-label': 'Navigation',
'msg-label-id': 'p-navigation-label',
@ -47,11 +40,8 @@ export const navigationPortal = () => wrapPortlet(
<li id="n-mainpage-description"><a href="/wiki/Main_Page" title="Visit the main page [⌃⌥z]" accesskey="z">Main page</a></li><li id="n-contents"><a href="/wiki/Wikipedia:Contents" title="Guides to browsing Wikipedia">Contents</a></li><li id="n-featuredcontent"><a href="/wiki/Wikipedia:Featured_content" title="Featured content the best of Wikipedia">Featured content</a></li><li id="n-currentevents"><a href="/wiki/Portal:Current_events" title="Find background information on current events">Current events</a></li><li id="n-randompage"><a href="/wiki/Special:Random" title="Load a random page [x]" accesskey="x">Random page</a></li><li id="n-sitesupport"><a href="https://donate.wikimedia.org/wiki/Special:FundraiserRedirector?utm_source=donate&amp;utm_medium=sidebar&amp;utm_campaign=C13_en.wikipedia.org&amp;uselang=en" title="Support us">Donate</a></li><li id="n-shoplink"><a href="//shop.wikimedia.org" title="Visit the Wikipedia store">Wikipedia store</a></li>
</ul>`,
'html-after-portal': portletAfter( placeholder( 'Possible hook output (navigation)', 50 ) )
}
);
export const toolbox = () => wrapPortlet(
{
},
toolbox: {
'portal-id': 'p-tb',
'html-tooltip': 'A message tooltip-p-tb must exist for this to appear',
'msg-label': 'Tools',
@ -61,11 +51,8 @@ export const toolbox = () => wrapPortlet(
<li id="t-whatlinkshere"><a href="/wiki/Special:WhatLinksHere/Spain" title="A list of all wiki pages that link here [⌃⌥j]" accesskey="j">What links here</a></li><li id="t-recentchangeslinked"><a href="/wiki/Special:RecentChangesLinked/Spain" rel="nofollow" title="Recent changes in pages linked from this page [k]" accesskey="k">Related changes</a></li><li id="t-upload"><a href="/wiki/Wikipedia:File_Upload_Wizard" title="Upload files [u]" accesskey="u">Upload file</a></li><li id="t-specialpages"><a href="/wiki/Special:SpecialPages" title="A list of all special pages [q]" accesskey="q">Special pages</a></li><li id="t-permalink"><a href="/w/index.php?title=Spain&amp;oldid=935087243" title="Permanent link to this revision of the page">Permanent link</a></li><li id="t-info"><a href="/w/index.php?title=Spain&amp;action=info" title="More information about this page">Page information</a></li><li id="t-wikibase"><a href="https://www.wikidata.org/wiki/Special:EntityPage/Q29" title="Link to connected data repository item [g]" accesskey="g">Wikidata item</a></li><li id="t-cite"><a href="/w/index.php?title=Special:CiteThisPage&amp;page=Spain&amp;id=935087243" title="Information on how to cite this page">Cite this page</a></li>
</ul>`,
'html-after-portal': portletAfter( placeholder( 'Possible hook output (tb)', 50 ) )
}
);
export const langlinks = () => wrapPortlet(
{
},
langlinks: {
'portal-id': 'p-lang',
'html-tooltip': 'A message tooltip-p-lang must exist for this to appear',
'msg-label': 'In other languages',
@ -81,11 +68,8 @@ export const langlinks = () => wrapPortlet(
`<span class="wb-langlinks-edit wb-langlinks-link"><a href="https://www.wikidata.org/wiki/Special:EntityPage/Q29#sitelinks-wikipedia" title="Edit interlanguage links (provided by WikiBase extension)" class="wbc-editpage">Edit links</a></span></div>
${placeholder( `<p>Further hook output possible (lang)</p>`, 60 )}`
)
}
);
export const otherProjects = () => wrapPortlet(
{
},
otherProjects: {
'portal-id': 'p-wikibase-otherprojects',
'html-tooltip': 'A message tooltip-p-lang must exist for this to appear',
'msg-label': 'In other projects',
@ -95,4 +79,4 @@ export const otherProjects = () => wrapPortlet(
<li class="wb-otherproject-link wb-otherproject-commons"><a href="https://commons.wikimedia.org/wiki/Category:Spain" hreflang="en">Wikimedia Commons</a></li><li class="wb-otherproject-link wb-otherproject-wikinews"><a href="https://en.wikinews.org/wiki/Category:Spain" hreflang="en">Wikinews</a></li><li class="wb-otherproject-link wb-otherproject-wikiquote"><a href="https://en.wikiquote.org/wiki/Spain" hreflang="en">Wikiquote</a></li><li class="wb-otherproject-link wb-otherproject-wikivoyage"><a href="https://en.wikivoyage.org/wiki/Spain" hreflang="en">Wikivoyage</a></li></ul>`,
'html-after-portal': ''
}
);
};

View File

@ -0,0 +1,11 @@
import { PORTALS, wrapPortlet } from './portal.stories.data';
export default {
title: 'Portals'
};
export const portal = () => wrapPortlet( PORTALS.example );
export const navigationPortal = () => wrapPortlet( PORTALS.navigation );
export const toolbox = () => wrapPortlet( PORTALS.toolbox );
export const langlinks = () => wrapPortlet( PORTALS.langlinks );
export const otherProjects = () => wrapPortlet( PORTALS.otherProjects );

View File

@ -0,0 +1,51 @@
import mustache from 'mustache';
import sidebarTemplate from '!!raw-loader!../includes/templates/Sidebar.mustache';
import portalTemplate from '!!raw-loader!../includes/templates/Portal.mustache';
import { PORTALS } from './portal.stories.data';
import '../.storybook/common.less';
import '../resources/skins.vector.styles/navigation.less';
const HTML_LOGO_ATTRIBUTES = `class="mw-wiki-logo" href="/wiki/Main_Page" title="Visit the main page"`;
const SIDEBAR_BEFORE_OUTPUT_HOOKINFO = `Beware: Portals can be added, removed or reordered using
SidebarBeforeOutput hook as in this example.`;
export default {
title: 'Sidebar'
};
export const sidebarWithNoPortals = () => mustache.render( sidebarTemplate,
{
'array-portals': [],
'html-logo-attributes': HTML_LOGO_ATTRIBUTES
}
);
export const sidebarWithPortals = () => mustache.render( sidebarTemplate,
{
'array-portals': [
PORTALS.navigation,
PORTALS.toolbox,
PORTALS.otherProjects,
PORTALS.langlinks
],
'html-logo-attributes': HTML_LOGO_ATTRIBUTES
},
{
Portal: portalTemplate
}
);
export const sidebarThirdParty = () => mustache.render( sidebarTemplate,
{
'array-portals': [
PORTALS.toolbox,
PORTALS.navigation,
{
'html-portal-content': SIDEBAR_BEFORE_OUTPUT_HOOKINFO
}
],
'html-logo-attributes': HTML_LOGO_ATTRIBUTES
},
{
Portal: portalTemplate
}
);

View File

@ -124,29 +124,25 @@ class VectorTemplateTest extends \MediaWikiTestCase {
}
/**
* @covers ::renderViewsComponent
* @covers ::buildViewsProps
*/
public function testRenderViewsComponent() {
public function testbuildViewsProps() {
$langAttrs = 'LANG_ATTRIBUTES';
$templateParserMock = $this->createMock( \TemplateParser::class );
$templateParserMock->expects( $this->once() )
->method( 'processTemplate' )
->with( 'VectorTabs', $this->callback( function ( $data ) use ( $langAttrs ){
if ( !array_key_exists( 'empty-portlet', $data ) ) {
return false;
}
return $data['empty-portlet'] == 'emptyPortlet' &&
$data['html-userlangattributes'] == $langAttrs;
} ) );
$vectorTemplate = new \VectorTemplate( \GlobalVarConfig::newInstance() );
$vectorTemplate->setTemplateParser( $templateParserMock );
$vectorTemplate->set( 'view_urls', [] );
$vectorTemplate->set( 'skin', new \SkinVector() );
$vectorTemplate->set( 'userlangattributes', $langAttrs );
$openVectorTemplate = TestingAccessWrapper::newFromObject( $vectorTemplate );
$openVectorTemplate->renderViewsComponent();
$props = $openVectorTemplate->buildViewsProps();
$this->assertSame( $props, [
'tabs-id' => 'p-views',
'empty-portlet' => 'emptyPortlet',
'label-id' => 'p-views-label',
'msg-label' => 'Views',
'html-userlangattributes' => $langAttrs,
'html-items' => '',
] );
}
}