Full Mustache conversion

This commit is contained in:
alistair3149 2020-06-05 20:12:39 -04:00
parent 0b28c95507
commit 0ade51a384
No known key found for this signature in database
GPG key ID: 94D081060FD3DD9C
7 changed files with 200 additions and 355 deletions

View file

@ -45,12 +45,15 @@ class CitizenTemplate extends BaseTemplate {
'msg-citizen-header-menu-toggle' => $this->getMsg( 'citizen-header-menu-toggle' )->text(), 'msg-citizen-header-menu-toggle' => $this->getMsg( 'citizen-header-menu-toggle' )->text(),
'data-menu' => $this->buildMenu(), 'data-menu' => $this->buildMenu(),
'msg-citizen-header-search-toggle' => $this->getMsg( 'citizen-header-search-toggle' )->text(), 'msg-citizen-header-search-toggle' => $this->getMsg( 'citizen-header-search-toggle' )->text(),
'data-extratools' => $this->buildExtraTools(), 'data-extratools' => $this->getExtraTools(),
'data-searchbox' => $this->buildSearchbox(), 'data-searchbox' => $this->buildSearchbox(),
], ],
'html-sitenotice' => $this->get( 'sitenotice', null ), 'html-sitenotice' => $this->get( 'sitenotice', null ),
'html-indicators' => $this->getIndicators(), 'html-indicators' => $this->getIndicators(),
'data-pagetools' => $this->buildPageTools(),
// From Skin::getNewtalks(). Always returns string, cast to null if empty // From Skin::getNewtalks(). Always returns string, cast to null if empty
'html-newtalk' => $this->get( 'newtalk', '' ) ?: null, 'html-newtalk' => $this->get( 'newtalk', '' ) ?: null,
'page-langcode' => $this->getSkin()->getTitle()->getPageViewLanguage()->getHtmlCode(), 'page-langcode' => $this->getSkin()->getTitle()->getPageViewLanguage()->getHtmlCode(),
@ -74,6 +77,9 @@ class CitizenTemplate extends BaseTemplate {
'html-bodycontent' => $this->get( 'bodycontent' ), 'html-bodycontent' => $this->get( 'bodycontent' ),
'html-printfooter' => $this->get( 'printfooter', null ), 'html-printfooter' => $this->get( 'printfooter', null ),
'data-pagelinks' => $this->buildPageLinks(),
'html-catlinks' => $this->get( 'catlinks', '' ), 'html-catlinks' => $this->get( 'catlinks', '' ),
'html-dataAfterContent' => $this->get( 'dataAfterContent', '' ), 'html-dataAfterContent' => $this->get( 'dataAfterContent', '' ),
// From MWDebug::getHTMLDebugLog (when $wgShowDebug is enabled) // From MWDebug::getHTMLDebugLog (when $wgShowDebug is enabled)
@ -95,23 +101,6 @@ class CitizenTemplate extends BaseTemplate {
'data-bottombar' => $this->buildBottombar(), 'data-bottombar' => $this->buildBottombar(),
]; ];
// TODO: Convert to Mustache
ob_start();
$html = $this->getPageTools();
echo $html;
$params['html-unported-pagetools'] = ob_get_contents();
ob_end_clean();
ob_start();
$html = $this->getPageLinks();
echo $html;
$params['html-unported-pagelinks'] = ob_get_contents();
ob_end_clean();
// Prepare and output the HTML response // Prepare and output the HTML response
$templates = new TemplateParser( __DIR__ . '/templates' ); $templates = new TemplateParser( __DIR__ . '/templates' );
echo $templates->processTemplate( 'skin', $params ); echo $templates->processTemplate( 'skin', $params );
@ -233,10 +222,10 @@ class CitizenTemplate extends BaseTemplate {
} }
/** /**
* Render notification badges and ULS button * Echo notification badges and ULS button
* @return array * @return array
*/ */
private function buildExtratools() { private function getExtratools() {
$personalTools = $this->getPersonalTools(); $personalTools = $this->getPersonalTools();
// Create the Echo badges and ULS // Create the Echo badges and ULS
@ -251,12 +240,12 @@ class CitizenTemplate extends BaseTemplate {
$extraTools['uls'] = $personalTools['uls']; $extraTools['uls'] = $personalTools['uls'];
} }
$extraToolsPortal = $this->getMenuData( 'personal-extra', $extraTools ); $html = $this->getMenuData( 'personal-extra', $extraTools );
// Hide label for extra tools // Hide label for extra tools
$extraToolsPortal[ 'label-class' ] .= 'screen-reader-text'; $html[ 'label-class' ] .= 'screen-reader-text';
return $extraToolsPortal; return $html;
} }
/** /**
@ -281,6 +270,88 @@ class CitizenTemplate extends BaseTemplate {
return $props; return $props;
} }
/**
* Render page-related tools
* Possible visibility conditions:
* * true: always visible (bool)
* * false: never visible (bool)
* * 'login': only visible if logged in (string)
* * 'permission-*': only visible if user has permission
* e.g. permission-edit = only visible if user can edit pages
* @return string html
*/
protected function buildPageTools() {
$config = $this->config;
$skin = $this->getSkin();
$condition = $config->get( 'CitizenShowPageTools' );
$contentNavigation = $this->data['content_navigation'];
$props = [];
// Login-based condition, return true if condition is met
if ( $condition === 'login' ) {
$condition = $skin()->getUser()->isLoggedIn();
}
// Permission-based condition, return true if condition is met
if ( is_string( $condition ) && strpos( $condition, 'permission' ) === 0 ) {
$permission = substr( $condition, 11 );
try {
$condition = MediaWikiServices::getInstance()->getPermissionManager()->userCan(
$permission, $skin->getUser(), $skin->getTitle() );
} catch ( Exception $e ) {
$condition = false;
}
}
if ( $condition === true ) {
$actionhtml = $this->getMenuData( 'views', $contentNavigation[ 'views' ] ?? [] );
$actionmorehtml = $this->getMenuData( 'actions', $contentNavigation[ 'actions' ] ?? [] );
if ( $actionhtml ) {
$actionhtml[ 'label-class' ] .= 'screen-reader-text';
}
if ( $actionmorehtml ) {
$actionmorehtml[ 'label-class' ] .= 'screen-reader-text';
}
$props = [
'data-page-actions' => $actionhtml,
'data-page-actions-more' => $actionmorehtml,
];
}
return $props;
}
/**
* Render page-related links at the bottom
* @return string html
*/
private function buildPageLinks() : array {
$contentNavigation = $this->data['content_navigation'];
$namespaceshtml = $this->getMenuData( 'namespaces', $contentNavigation[ 'namespaces' ] ?? [] );
$variantshtml = $this->getMenuData( 'variants', $contentNavigation[ 'variants' ] ?? [] );
if ( $namespaceshtml ) {
$namespaceshtml[ 'label-class' ] .= 'screen-reader-text';
}
if ( $variantshtml ) {
$variantshtml[ 'label-class' ] .= 'screen-reader-text';
}
$props = [
'data-namespaces' => $namespaceshtml,
'data-variants' => $variantshtml,
];
return $props;
}
/** /**
* Render the bottom bar * Render the bottom bar
* TODO: Convert button text to i18n message. * TODO: Convert button text to i18n message.
@ -301,261 +372,6 @@ class CitizenTemplate extends BaseTemplate {
return $props; return $props;
} }
/**
* Language variants. Displays list for converting between different scripts in the same language,
* if using a language where this is applicable (such as latin vs cyric display for serbian).
*
* @return string html
*/
protected function getVariants() {
$html = '';
if ( count( $this->data['content_navigation']['variants'] ) > 0 ) {
$html .= $this->getPortlet( 'variants', $this->data['content_navigation']['variants'] );
}
return $html;
}
/**
* Generates page-related tools
* Possible visibility conditions:
* * true: always visible (bool)
* * false: never visible (bool)
* * 'login': only visible if logged in (string)
* * 'permission-*': only visible if user has permission
* e.g. permission-edit = only visible if user can edit pages
* @return string html
*/
protected function getPageTools() {
$html = '';
try {
$condition = $this->config->get( 'CitizenShowPageTools' );
} catch ( ConfigException $e ) {
$condition = false;
}
if ( $condition === 'login' ) {
$condition = $this->getSkin()->getUser()->isLoggedIn();
}
if ( is_string( $condition ) && strpos( $condition, 'permission' ) === 0 ) {
$permission = substr( $condition, 11 );
try {
$condition = MediaWikiServices::getInstance()->getPermissionManager()->userCan(
$permission, $this->getSkin()->getUser(), $this->getSkin()->getTitle() );
} catch ( Exception $e ) {
$condition = false;
}
}
// Only display if user is logged in
if ( $condition === true ) {
$html .= Html::openElement( 'div', [ 'class' => 'mw-side', 'id' => 'page-tools' ] );
// 'View' actions for the page: view, edit, view history, etc
$html .= $this->getPortlet( 'views', $this->data['content_navigation']['views'] );
// Other actions for the page: move, delete, protect, everything else
$html .= $this->getPortlet( 'actions', $this->data['content_navigation']['actions'] );
$html .= Html::closeElement( 'div' );
}
return $html;
}
/**
* Generates page-related links at the bottom
* @return string html
*/
protected function getPageLinks() {
// Namespaces: links for 'content' and 'talk' for namespaces with talkpages.
// Otherwise is just the content.
// Usually rendered as tabs on the top of the page.
$html = $this->getPortlet( 'namespaces', $this->data['content_navigation']['namespaces'] );
// Language variant options
return $html . $this->getVariants();
}
/**
* 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
*/
protected function getPortlet( $name, $content, $msg = null, $setOptions = [] ) {
// random stuff to override with any provided options
$options = $setOptions + [
// extra classes/ids
'id' => 'p-' . $name,
'class' => 'mw-portlet',
'extra-classes' => '',
// what to wrap the body list in, if anything
'body-wrapper' => 'nav',
'body-id' => null,
'body-class' => 'mw-portlet-body',
// makeListItem options
'list-item' => [ 'text-wrapper' => [ 'tag' => 'span' ] ],
// option to stick arbitrary stuff at the beginning of the ul
'list-prepend' => '',
// old toolbox hook support (use: [ 'SkinTemplateToolboxEnd' => [ &$skin, true ] ])
'hooks' => '',
];
// Handle the different $msg possibilities
if ( $msg === null ) {
$msg = $name;
} elseif ( is_array( $msg ) ) {
$msgString = array_shift( $msg );
$msgParams = $msg;
$msg = $msgString;
}
$msgObj = $this->getMsg( $msg );
if ( $msgObj->exists() ) {
if ( isset( $msgParams ) && !empty( $msgParams ) ) {
$msgString = $this->getMsg( $msg, $msgParams )->parse();
} else {
$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' ) ] );
$contentText .= $options['list-prepend'];
foreach ( $content as $key => $item ) {
$contentText .= $this->makeListItem( $key, $item, $options['list-item'] );
}
// Compatibility with extensions still using SkinTemplateToolboxEnd or similar
if ( is_array( $options['hooks'] ) ) {
foreach ( $options['hooks'] as $hook ) {
if ( is_string( $hook ) ) {
$hookOptions = [];
} else {
// it should only be an array otherwise
$hookOptions = array_values( $hook )[0];
$hook = array_keys( $hook )[0];
}
$contentText .= $this->deprecatedHookHack( $hook, $hookOptions );
}
}
$contentText .= Html::closeElement( 'ul' );
} else {
$contentText = $content;
}
// Special handling for role=search and other weird things
$divOptions = [
'role' => 'navigation',
'id' => Sanitizer::escapeIdForAttribute( $options['id'] ),
'title' => Linker::titleAttrib( $options['id'] ),
'aria-labelledby' => $labelId,
];
if ( !is_array( $options['class'] ) ) {
$class = [ $options['class'] ];
}
if ( !is_array( $options['extra-classes'] ) ) {
$extraClasses = [ $options['extra-classes'] ];
}
$divOptions['class'] =
array_merge( $class ?? $options['class'], $extraClasses ?? $options['extra-classes'] );
$labelOptions = [
'id' => $labelId,
'lang' => $this->get( 'userlang' ),
'dir' => $this->get( 'dir' ),
];
if ( $options['body-wrapper'] !== 'none' ) {
$bodyDivOptions = [ 'class' => $options['body-class'] ];
if ( is_string( $options['body-id'] ) ) {
$bodyDivOptions['id'] = $options['body-id'];
}
$body =
Html::rawElement( $options['body-wrapper'], $bodyDivOptions,
$contentText . $this->getAfterPortlet( $name ) );
} else {
$body = $contentText . $this->getAfterPortlet( $name );
}
return Html::rawElement( 'div', $divOptions,
Html::rawElement( 'h3', $labelOptions, $msgString ) . $body );
}
/**
* Wrapper to catch output of old hooks expecting to write directly to page
* We no longer do things that way.
*
* @param string $hook event
* @param array $hookOptions args
*
* @return string html
*/
protected function deprecatedHookHack( $hook, $hookOptions = [] ) {
ob_start();
try {
Hooks::run( $hook, $hookOptions );
} catch ( Exception $e ) {
// Do nothing
}
$hookContents = ob_get_clean();
if ( !trim( $hookContents ) ) {
$hookContents = '';
}
return $hookContents;
}
/**
* @param string $label to be used to derive the id and human readable label of the menu
* If the key has an entry in the constant MENU_LABEL_KEYS then that message will be used for the
* human readable text instead.
* @param array $urls to convert to list items stored as string in html-items key
* @param array $options (optional) to be passed to makeListItem
* @return array
*/
private function getMenuData(
string $label,
array $urls = [],
array $options = []
) : array {
// For some menu items, there is no language key corresponding with its menu key.
// These inconsitencies are captured in MENU_LABEL_KEYS
$msgObj = $this->getMsg( self::MENU_LABEL_KEYS[ $label ] ?? $label );
$props = [
'id' => "p-$label",
'label-class' => '',
'label-id' => "p-{$label}-label",
// If no message exists fallback to plain text (T252727)
'label' => $msgObj->exists() ? $msgObj->text() : $label,
'html-items' => '',
'html-tooltip' => Linker::tooltip( 'p-' . $label ),
];
foreach ( $urls as $key => $item ) {
$props['html-items'] .= $this->makeListItem( $key, $item, $options );
}
$props['html-after-portal'] = $this->getAfterPortlet( $label );
// Mark the portal as empty if it has no content
$class = ( count( $urls ) == 0 && !$props['html-after-portal'] )
? 'mw-portal-empty' : '';
$props['class'] = $class;
return $props;
}
/** /**
* Get last modified message * Get last modified message
* @return string html * @return string html
@ -659,4 +475,43 @@ class CitizenTemplate extends BaseTemplate {
return $footerRows; return $footerRows;
} }
/**
* @param string $label to be used to derive the id and human readable label of the menu
* If the key has an entry in the constant MENU_LABEL_KEYS then that message will be used for the
* human readable text instead.
* @param array $urls to convert to list items stored as string in html-items key
* @param array $options (optional) to be passed to makeListItem
* @return array
*/
private function getMenuData(
string $label,
array $urls = [],
array $options = []
) : array {
// For some menu items, there is no language key corresponding with its menu key.
// These inconsitencies are captured in MENU_LABEL_KEYS
$msgObj = $this->getMsg( self::MENU_LABEL_KEYS[ $label ] ?? $label );
$props = [
'id' => "p-$label",
'label-class' => null,
'label-id' => "p-{$label}-label",
// If no message exists fallback to plain text (T252727)
'label' => $msgObj->exists() ? $msgObj->text() : $label,
'html-items' => '',
'html-tooltip' => Linker::tooltip( 'p-' . $label ),
];
foreach ( $urls as $key => $item ) {
$props['html-items'] .= $this->makeListItem( $key, $item, $options );
}
$props['html-after-portal'] = $this->getAfterPortlet( $label );
// Mark the portal as empty if it has no content
$class = ( count( $urls ) == 0 && !$props['html-after-portal'] )
? ' mw-portal-empty' : '';
$props['class'] = $class;
return $props;
}
} }

View file

@ -36,7 +36,12 @@
{{/html-sitenotice}} {{/html-sitenotice}}
{{#html-newtalk}}<div class="usermessage">{{{html-newtalk}}}</div>{{/html-newtalk}} {{#html-newtalk}}<div class="usermessage">{{{html-newtalk}}}</div>{{/html-newtalk}}
{{{html-indicators}}} {{{html-indicators}}}
{{{html-unported-pagetools}}} {{#data-pagetools}}
<div class="mw-side" id="page-tools">
{{#data-page-actions}}{{>Portal}}{{/data-page-actions}}
{{#data-page-actions-more}}{{>Portal}}{{/data-page-actions-more}}
</div>
{{/data-pagetools}}
<h1 id="firstHeading" class="firstHeading" lang="{{page-langcode}}">{{{html-title}}}</h1> <h1 id="firstHeading" class="firstHeading" lang="{{page-langcode}}">{{{html-title}}}</h1>
<div id="siteSub">{{msg-tagline}}</div> <div id="siteSub">{{msg-tagline}}</div>
{{{html-prebodyhtml}}} {{{html-prebodyhtml}}}
@ -47,7 +52,10 @@
{{#html-printfooter}} {{#html-printfooter}}
<div class="printfooter">{{{html-printfooter}}}</div> <div class="printfooter">{{{html-printfooter}}}</div>
{{/html-printfooter}} {{/html-printfooter}}
{{{html-unported-pagelinks}}} {{#data-pagelinks}}
{{#data-namespaces}}{{>Portal}}{{/data-namespaces}}
{{#data-variants}}{{>Portal}}{{/data-variants}}
{{/data-pagelinks}}
{{{html-catlinks}}} {{{html-catlinks}}}
{{{html-dataAfterContent}}} {{{html-dataAfterContent}}}
{{{html-debuglog}}} {{{html-debuglog}}}

View file

@ -741,10 +741,6 @@ a {
margin: 0 @negative-margin; margin: 0 @negative-margin;
padding: @margin-side; padding: @margin-side;
&-label {
.mixin-screen-reader-text;
}
ul { ul {
margin: 1.6rem 0 0 0; margin: 1.6rem 0 0 0;
display: flex; display: flex;
@ -758,6 +754,7 @@ a {
align-items: center; align-items: center;
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
border: 1px solid @base-80; border: 1px solid @base-80;
color: @base-20;
background-color: @base-90; background-color: @base-90;
transition: @transition-background-quick, @transition-box-shadow-quick; transition: @transition-background-quick, @transition-box-shadow-quick;
.boxshadow(1); .boxshadow(1);
@ -767,10 +764,6 @@ a {
.boxshadow(2); .boxshadow(2);
} }
span {
color: @base-20;
}
&:after { &:after {
order: -1; order: -1;
content: ''; content: '';

View file

@ -99,7 +99,7 @@
background: @dark-bg-40; background: @dark-bg-40;
} }
#page-tools #p-actions > nav ul { #page-tools #p-actions ul {
background: @dark-bg-50; background: @dark-bg-50;
} }
@ -180,7 +180,7 @@
} }
#page-tools #p-views li > a:after, #page-tools #p-views li > a:after,
#page-tools #p-actions > nav:before, #page-tools #p-actions:before,
.mw-header-menu-drawer-container .mw-nav-links a:after, .mw-header-menu-drawer-container .mw-nav-links a:after,
.mw-header-menu-drawer-container .mw-user-links a:after, .mw-header-menu-drawer-container .mw-user-links a:after,
.mw-editsection > a:before, .mw-editsection > a:before,

View file

@ -207,10 +207,12 @@
margin-bottom: @margin-side / 2; margin-bottom: @margin-side / 2;
a { a {
justify-content: unset; flex-direction: row-reverse;
justify-content: flex-end;
&:after { &:after {
margin: 0; margin: 0;
margin-right: @margin-side;
width: @icon-box-size; width: @icon-box-size;
height: @icon-box-size; height: @icon-box-size;
} }

View file

@ -5,7 +5,7 @@
// //
// Hide selected item // Hide selected item
.mw-portlet li.selected { .mw-portal .selected {
.mixin-screen-reader-text; .mixin-screen-reader-text;
} }
@ -16,10 +16,6 @@
display: flex; display: flex;
transform: translateX( ~'calc( (100vw - @{page-width}) / 2 - @{margin-side} * 2 - 100%)' ); // magic transform: translateX( ~'calc( (100vw - @{page-width}) / 2 - @{margin-side} * 2 - 100%)' ); // magic
h3 {
.mixin-screen-reader-text;
}
ul { ul {
margin: 0; margin: 0;
display: flex; display: flex;
@ -53,74 +49,65 @@
} }
#p-actions { #p-actions {
> nav { .resource-loader-icon-link;
.resource-loader-icon-link; padding: 5px;
padding: 5px; cursor: pointer;
cursor: pointer; // transition: @transition-opacity-quick; - Hidden behind the menu anyways
// transition: @transition-opacity-quick; - Hidden behind the menu anyways
// TODO: Need to make value more flexible // TODO: Need to make value more flexible
ul { ul {
z-index: -1; z-index: -1;
pointer-events: none; pointer-events: none;
.menu-container; .menu-container;
position: absolute; position: absolute;
opacity: 0; opacity: 0;
.boxshadow(4); .boxshadow(4);
transition: @transition-opacity-quick, @transition-box-shadow-quick; transition: @transition-opacity-quick, @transition-box-shadow-quick;
a { a {
.menu-item-link; .menu-item-link;
justify-content: space-between; justify-content: space-between;
font-size: @ui-menu-text; font-size: @ui-menu-text;
padding: @padding-menu-item; padding: @padding-menu-item;
&:after {
.resource-loader-list-icon;
margin-left: @icon-padding;
opacity: @opacity-icon;
}
&:hover,
&:active,
&:focus {
&:after { &:after {
.resource-loader-list-icon; opacity: @opacity-icon-active;
margin-left: @icon-padding;
opacity: @opacity-icon;
}
&:hover,
&:active,
&:focus {
&:after {
opacity: @opacity-icon-active;
}
}
&:hover {
.menu-item-link-hover;
}
&:active {
.menu-item-link-active;
}
&:focus {
.menu-item-link-focus;
} }
} }
}
&:before { &:hover {
.resource-loader-menu-icon; .menu-item-link-hover;
opacity: @opacity-icon; }
}
/* &:active {
* Hidden behind the menu anyways .menu-item-link-active;
* &:hover:after { }
* opacity: @opacity-icon-active;
* }
*/
&:hover ul { &:focus {
z-index: 5; .menu-item-link-focus;
opacity: 1; }
pointer-events: auto;
} }
} }
&:before {
.resource-loader-menu-icon;
opacity: @opacity-icon;
}
&:hover ul {
z-index: 5;
opacity: 1;
pointer-events: auto;
}
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"name": "Citizen", "name": "Citizen",
"namemsg": "skinname-citizen", "namemsg": "skinname-citizen",
"version": "0.8.1", "version": "0.8.2",
"author": [ "author": [
"[https://www.mediawiki.org/wiki/User:Alistair3149 Alistair3149]", "[https://www.mediawiki.org/wiki/User:Alistair3149 Alistair3149]",
"[https://www.mediawiki.org/wiki/User:Octfx Octfx]" "[https://www.mediawiki.org/wiki/User:Octfx Octfx]"
@ -260,7 +260,7 @@
}, },
"skins.citizen.icons.p": { "skins.citizen.icons.p": {
"class": "ResourceLoaderImageModule", "class": "ResourceLoaderImageModule",
"selector": "#p-{name} > *:before", "selector": "#p-{name}:before",
"defaultColor": "#000", "defaultColor": "#000",
"useDataURI": false, "useDataURI": false,
"images": { "images": {