diff --git a/includes/RefreshedTemplate.php b/includes/RefreshedTemplate.php
index 90418f2..c89f124 100644
--- a/includes/RefreshedTemplate.php
+++ b/includes/RefreshedTemplate.php
@@ -1,1047 +1,1114 @@
'',
'user-loggedin' => '',
'user-anon' => '',
'menu' => '',
'more' => '',
'close' => '',
'nstab' => '',
'talk' => '',
'viewsource' => '',
'edit' => '',
'addsection' => '',
'history' => '',
'delete' => '',
'undelete' => '',
'move' => '',
'protect' => '',
'unprotect' => '',
'watch' => '',
'unwatch' => '',
'wikilove' => '',
'purge' => '',
'report-problem' => '',
'whatlinkshere' => '',
'recentchangeslinked' => '',
'contributions' => '',
'blockip' => '',
'log' => '',
'emailuser' => '',
'userrights' => '',
'upload' => '',
'print' => '',
'permalink' => '',
'info' => '',
'smwbrowserlink' => ''
];
+
/**
* Parses MediaWiki:Refreshed-wiki-dropdown.
* Forked from Games' parseSidebarMenu(), which in turn was forked from
* Monaco's parseSidebarMenu(), but none of these three methods are
* identical.
*
* @param string $messageKey Message name
* @return array
*/
private function parseSiteNavigationDropdownMenu( $messageKey ) {
$lines = $this->getLines( $messageKey );
$nodes = [];
$i = 0;
if ( is_array( $lines ) ) {
foreach ( $lines as $line ) {
# ignore empty lines
if ( strlen( $line ) == 0 ) {
continue;
}
$node = $this->parseSiteNavigationDropdownItem( $line );
for ( $x = $i; $x >= 0; $x-- ) {
if ( $x == 0 ) {
break;
}
}
$nodes[$i + 1] = $node;
$i++;
}
}
return $nodes;
}
/**
* Helper method for parseSiteNavigationDropdownMenu.
* Parse one pipe-separated line from MediaWiki message to an array with
- * indexes "wikiName" (string), "logoURL" (string|null), "wikiURL" (string|null)
+ * indexes "wikiName" (string), "logoURL" (string|null),
+ * "wikiURL" (string|null)
* (This array will eventually be used to construct a link in the site
* dropdown via renderSiteNavigationDropdownItems.)
* Each line follows this format of text seperated by pipe symbols:
* name|logo URL|wiki URL.
* Special cases:
* - If no logo URL is provided (name||wiki URL), 'logoURL' => null.
* - If no wiki URL is provided (name|logo URL|badly formed wiki URL, or
* name|logo URL|, or name|logo URL), 'wikiURL' => '#'.
* - Finally if neither is provided (name or name||) then both of the above
* apply.
* @param string $line Line (beginning with a *) from a MediaWiki: message
* @return array attributes for the resulting link
*/
public static function parseSiteNavigationDropdownItem( $line ) {
- // trim spaces and asterisks from line and then split it to maximum three chunks
+ // trim spaces and asterisks from line and split it to maximum three chunks
$line_temp = explode( '|', trim( $line, '* ' ), 3 );
// Likewise we assume the logoURL will be null and the wiki URL will be #,
// but if we find alternatives when parsing, we'll switch to them.
$logoURL = null;
$wikiURL = '#';
$wikiName = $line_temp[0];
// has logo URL if at least 2 chunks and the 2nd isn't empty
if ( count( $line_temp ) >= 2 && $line_temp[1] !== '' ) {
$logoURL = trim( $line_temp[1] );
}
// get link from third chunk if it exists and is a URL
if (
isset( $line_temp[2] ) &&
preg_match( '/^(?:' . wfUrlProtocols() . ')/', $line_temp[2] )
)
{
$wikiURL = $line_temp[2];
}
return [
'wikiName' => $wikiName,
'logoURL' => $logoURL,
'wikiURL' => $wikiURL,
];
}
/**
* @param string $messageKey Name of a MediaWiki: message
* @return array|null Array if $messageKey has been given, otherwise null
*/
private function getMessageAsArray( $messageKey ) {
$messageObj = $this->getSkin()->msg( $messageKey )->inContentLanguage();
if ( !$messageObj->isDisabled() ) {
$lines = explode( "\n", $messageObj->text() );
if ( count( $lines ) > 0 ) {
return $lines;
}
}
return null;
}
/**
* @param string $messageKey Name of a MediaWiki: message
* @return array
*/
private function getLines( $messageKey ) {
$title = Title::newFromText( $messageKey, NS_MEDIAWIKI );
$revision = Revision::newFromTitle( $title );
if ( is_object( $revision ) ) {
$contentText = ContentHandler::getContentText( $revision->getContent() );
if ( trim( $contentText ) != '' ) {
$temp = $this->getMessageAsArray( $messageKey );
if ( count( $temp ) > 0 ) {
wfDebugLog( 'Refreshed', sprintf( 'Get LOCAL %s, which contains %s lines', $messageKey, count( $temp ) ) );
$lines = $temp;
}
}
}
if ( empty( $lines ) ) {
$lines = $this->getMessageAsArray( $messageKey );
- // if $lines isn't countable, should log a different debug message that does not include count( $lines )
- // since in PHP 7.2 and beyond, counting non-countable objects prompts a warning that will break the
- // page
+ // if $lines isn't countable, should log a different debug message that
+ // does not include count( $lines ) since in PHP 7.2 and beyond, counting
+ // non-countable objects prompts a warning that will break the page
if ( is_array( $lines ) || $lines instanceof Countable ) {
wfDebugLog( 'Refreshed', sprintf( 'Get %s, which contains %s lines', $messageKey, count( $lines ) ) );
} else {
wfDebugLog( 'Refreshed', sprintf( 'Get %s, which is empty', $messageKey ) );
}
}
return $lines;
}
/**
* Return an inline SVG containing the inputted icon, as a string.
* @param string|null $iconName string or null if no icon
* @return string
*/
private function makeIcon( $iconName ) {
// print_r($iconList);
// return null if $iconName isn't a string or is the empty string
if ( !is_string( $iconName ) || $iconName === '' ) {
return '';
}
// Sometimes $iconName may be of the form "nstab-something" if it represents
// an article button (like "user page"). In this case, there are many
// possible suffixes like "-user", "-project", etc. We can't possibly
// predict all those suffixes since some of them may represent namespaces
// that one wiki in particular has defined. As such, we will strip the
// suffix to leave just "nstab" for every namespace. That way article
// buttons always use the same icon.
if ( strpos( $iconName, 'nstab' ) === 0 ) {
$iconName = 'nstab';
}
if ( array_key_exists( $iconName, self::$iconList ) ) {
return self::$iconList[$iconName];
}
return '';
}
/**
* Render an inline SVG containing the inputted icon to the page.
* @param string|null $iconName string or null if no icon
* @return string
*/
private function renderIcon( $iconName ) {
echo $this->makeIcon( $iconName );
}
/**
* Generate a list item using BaseTemplate::makeListItem() that contains the
* inline SVG icon specified by $iconName just before the actual link text,
* assuming $iconName is specified.
* (If the icon name isn't recognized, or the list item or icon HTML can't
* be parsed for whatever reason, the list item is returned without
* adding the icon.)
* @param string $iconName the name of the icon
- * @param string $key the "$key" for the standard makeLink/makeListItem (see docs)
- * @param array $item the "$item" for the standard makeLink/makeListItem (see docs)
- * @param array $options the "$options" for the standard makeLink/makeListItem (see docs); optional
+ * @param string $key the "$key" for the standard makeLink/makeListItem
+ * (see docs)
+ * @param array $item the "$item" for the standard makeLink/makeListItem
+ * (see docs)
+ * @param array $options the "$options" for the standard makeLink/makeListItem
+ * (see docs); optional
* @return string string representing the list item
*/
private function makeListItemWithIcon( $iconName = '', $key, $item, $options = [] ) {
return $this->makeElementWithIconHelper( 'list item', $iconName, $key, $item, $options );
}
/**
* Generate a link using BaseTemplate::makeLink() that contains the
* inline SVG icon specified by $iconName just before the actual link text,
* assuming $iconName is specified.
* (If the icon name isn't recognized, or the link or icon HTML can't
* be parsed for whatever reason, the link is returned without
* adding the icon.)
* @param string $iconName the name of the icon
- * @param string $key the "$key" for the standard makeLink/makeListItem (see docs)
- * @param array $item the "$item" for the standard makeLink/makeListItem (see docs)
- * @param array $options the "$options" for the standard makeLink/makeListItem (see docs); optional
+ * @param string $key the "$key" for the standard makeLink/makeListItem
+ * (see docs)
+ * @param array $item the "$item" for the standard makeLink/makeListItem
+ * (see docs)
+ * @param array $options the "$options" for the standard makeLink/makeListItem
+ * (see docs); optional
* @return string string representing the link
*/
private function makeLinkWithIcon( $iconName = '', $key, $item, $options = [] ) {
return $this->makeElementWithIconHelper( 'link', $iconName, $key, $item, $options );
}
/**
* Helper method for makeListItemWithIcon and makeLinkWithIcon.
*
* Depending on $mode, generate a) a list item containing a link using
* BaseTemplate::makeListItem() or b) just a link using
* BaseTemplate::makeLink(). Before the actual link text, there is the inline
* SVG icon specified by $iconName, assuming $iconName is specified.
* (If the icon name isn't recognized, or the list item/link or icon HTML
* can't be parsed for whatever reason, the list item/link is returned without
* adding the icon.)
* @param string $mode Expects either 'list item' or 'link'
* @param string $iconName the name of the icon
- * @param string $key the "$key" for the standard makeLink/makeListItem (see docs)
- * @param array $item the "$item" for the standard makeLink/makeListItem (see docs)
- * @param array $options the "$options" for the standard makeLink/makeListItem (see docs); optional
+ * @param string $key the "$key" for the standard makeLink/makeListItem
+ * (see docs)
+ * @param array $item the "$item" for the standard makeLink/makeListItem
+ * (see docs)
+ * @param array $options the "$options" for the standard makeLink/makeListItem
+ * (see docs); optional
* @return string string representing the list item/link
*/
private function makeElementWithIconHelper( $mode, $iconName, $key, $item, $options ) {
// Based on the $mode, either make a list item or link without any icon
// added yet. If the $mode is invalid, return null.
if ( $mode === 'list item' ) {
$outputUnedited = $this->makeListItem( $key, $item, $options );
} elseif ( $mode === 'link' ) {
$outputUnedited = $this->makeLink( $key, $item, $options );
}
// Get the HTML of the icon we want to add (returns empty string if no icon)
$icon = $this->makeIcon( $iconName );
// if there is no icon to add, don't bother doing more processing; just
// return the list item/link without the icon
if ( $icon === '' ) {
return $outputUnedited;
}
// Now we know there actually is an icon we want to insert. We want to find
// where it belongs. To do this, we will parse the HTML of the list item/
// link.
// (As of MW 1.31) BaseTemplate::makeListItem and BaseTemplate::makeLink
// allow angle brackets in attributes. In case this (or something else)
// breaks the HTML parser, rather than deal with the breaking, we will just
// not add images/icons in that case.
// (two separate DOMs: one for the list item/link, one for the icon)
$listItemOrLinkDOM = $this->loadHTMLHandleErrors( $outputUnedited );
$iconDOM = $this->loadHTMLHandleErrors( $icon );
if ( !$listItemOrLinkDOM || !$iconDOM ) {
return $outputUnedited;
}
// otherwise insert the icon into our list item/link
// (read variable names for explanation of what's going on below)
$xpath = new DOMXPath( $listItemOrLinkDOM );
// Find the first a tag in the link. We know such a tag exists because one
// is produced by makeListItem or makeLink, which we haven't modified.
$firstATagInListItemOrLink = $xpath->query( '(//a)[1]' )->item( 0 );
// Find the child of the first a tag from the last line. Note this may not
// exist (if a tag is empty), in which case it's null.
$firstATagChild = $firstATagInListItemOrLink->firstChild;
$iconInIconDOM = $iconDOM->documentElement;
// Currently the icon is in the iconDOM. We have to put a copy of it in
// $listItemOrLinkDOM so we can add the icon to the list item/link
$iconInListItemOrLinkDOM = $listItemOrLinkDOM->importNode( $iconInIconDOM, true );
// add the icon to the very beginning of the first a tag
if ( $firstATagChild === null ) {
$firstATagInListItemOrLink->appendChild( $iconInListItemOrLinkDOM );
} else {
$firstATagInListItemOrLink->insertBefore( $iconInListItemOrLinkDOM, $firstATagChild );
}
return $listItemOrLinkDOM->saveHTML();
}
/**
* Helper method for makeElementWithIconHelper.
* Given $text, load it into a DOMDocument as HTML. If all goes as planned
* (the input doesn't break the parser), return the resulting DOMDocument.
* Otherwise, echo errors and return false.
- * @param string $text the text to interpret as HTML (shouldn't contain html or body tags)
+ * @param string $text the text to interpret as HTML (shouldn't contain html
+ * or body tags)
* @return DOMDocument|bool DOMDocument if no errors, otherwise false
*/
private function loadHTMLHandleErrors( $text ) {
// error handling per https://secure.php.net/manual/en/simplexml.examples-errors.php
$doc = new DOMDocument();
// config doesn't include doctype, html, or body tags per
// https://stackoverflow.com/a/22490902
$html = $doc->loadHTML( $text, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
if ( $html === false ) {
foreach ( libxml_get_errors() as $error ) {
echo "\n", $error->message;
}
return false;
}
return $doc;
}
/**
* Return the user's avatar element as a string (if using SocialProfile).
* Otherwise, return the appropriate placeholder element as a string.
* @param User $user
* @return string
*/
private function makeAvatar( $user ) {
// if using SocialProfile (logged in or not), return SocialProfile avatar
if ( class_exists( 'wAvatar' ) ) {
$avatar = new wAvatar( $user->getId(), 'l' );
return $avatar->makeAvatarURL( [
'class' => 'avatar avatar-image'
] );
} elseif ( $this->data['loggedin'] ) { // if no SocialProfile and user is logged in...
// if wiki has not set custom image for logged in users...
if ( $this->getMsg( 'refreshed-icon-logged-in' )->isDisabled() ) {
return $this->makeIcon( 'user-loggedin' );
} else { // if wiki has set custom image for logged in users...
return Html::element( 'img', [
'src' => $this->getMsg( 'refreshed-icon-logged-in' )->escaped(),
'class' => 'avatar avatar-no-socialprofile avatar-image'
] );
}
} else { // if no SocialProfile and user is not logged in...
// if wiki has not set a custom image for logged out users
if ( $this->getMsg( 'refreshed-icon-logged-out' )->isDisabled() ) {
return $this->makeIcon( 'user-anon' );
} else { // if wiki has set custom image for logged out users
return Html::element( 'img', [
'src' => $this->getMsg( 'refreshed-icon-logged-out' )->escaped(),
'class' => 'avatar avatar-no-socialprofile avatar-image'
] );
}
}
}
/**
* Get the username text (string) to be displayed in the header.
* @param User $user
* @return string
*/
private function makeUsernameText( $user ) {
// if logged in...
if ( $this->data['loggedin'] ) {
return $user->getName();
}
// if not logged in...
return $this->getMsg( 'login' )->text();
}
+ /**
+ * Get the personal tools and rearrange them into "dropdown" and "extra"
+ * tools. The "dropdown" tools are the ones that should go into the user info
+ * dropdown, and the "extra" tools (like Echo ones) are ones that should be
+ * placed next to the user dropdown.
+ * Inspired by and partially adapted from the Timeless skin's getUserLinks
+ * function.
+ * @return array $rearrangedPersonalTools where the key "dropdown" contains
+ * the dropdown tools, and the key "extra" contains the extra tools.
+ */
+ private function getAndRearrangePersonalTools() {
+ $dropdownTools = $this->getPersonalTools();
+ $extraTools = [];
+ // list of tool names that should be removed from the dropdown tools and be
+ // added to the extra tools
+ // (these tools are echo badges)
+ $toolsToMove = [ 'notifications-alert', 'notifications-notice' ];
+
+ foreach ( $toolsToMove as $currentToolToMove ) {
+ if ( isset( $dropdownTools[$currentToolToMove] ) ) {
+ $extraTools[$currentToolToMove] = $dropdownTools[$currentToolToMove];
+ unset( $dropdownTools[$currentToolToMove] );
+ }
+ }
+ return [ 'dropdown' => $dropdownTools, 'extra' => $extraTools ];
+ }
+
+ /**
+ * Render the list items to be displayed next to the user dropdown
+ * (e.g., for Echo).
+ * Inspired by how Timeless handles Echo.
+ * @param array $extraPersonalTools
+ */
+ private function renderExtraPersonalTools( $extraPersonalTools ) {
+ foreach ( $extraPersonalTools as $key => $item ) {
+ echo $this->makeListItem( $key, $item );
+ }
+ }
/**
* Render the list items to be displayed in the header's user dropdown.
+ * @param array $dropdownPersonalTools
*/
- private function renderUserDropdownItems() {
- foreach ( $this->getPersonalTools() as $keyAndIconName => $item ) {
+ private function renderUserDropdownItems( $dropdownPersonalTools ) {
+ foreach ( $dropdownPersonalTools as $keyAndIconName => $item ) {
echo $this->makeListItemWithIcon( $keyAndIconName, $keyAndIconName, $item );
}
}
+
/**
* Render the items of the site navigation dropdown to appear in the header.
- * @param array $siteNavigationDropdown an array containing info for the site navigation dropdown
+ * @param array $siteNavigationDropdown an array containing info for the site
+ * navigation dropdown
*/
private function renderSiteNavigationDropdownItems( $siteNavigationDropdown ) {
// (each item in $siteNavigationDropdown was an output of
// parseSiteNavigationDropdownItem)
// we're making a bunch of list items here (
elements, but NOT ones
// created via makeListItem or makeListItemWithIcon...)
// the classes to add to each of the dropdown anchors
$classList = 'refreshed-logo refreshed-logo-other';
foreach ( $siteNavigationDropdown as $wikiLogoInfo ) {
// send each of the parsed pieces of wiki logo info to renderWikiLogo
// for rendering
echo Html::rawElement( 'li', [
'class' => 'refreshed-dropdown-item header-dropdown-item site-navigation-dropdown-item'
], $this->makeWikiLinkWithLogo( $wikiLogoInfo['wikiName'], $wikiLogoInfo['logoURL'], $wikiLogoInfo['wikiURL'], $classList ) );
}
}
/**
* Render the items of the header category dropdown to appear in the header.
- * @param array $headerCategoryDropdown an array containing info for a header category dropdown
+ * @param array $headerCategoryDropdown an array containing info for a header
+ * category dropdown
*/
private function renderHeaderCategoryDropdownItems( $headerCategoryDropdown ) {
foreach ( $headerCategoryDropdown as $key => $value ) {
echo Html::rawElement( 'li', [
'class' => 'refreshed-dropdown-item header-dropdown-item header-category-dropdown-item'
], $this->makeLink( $key, $value ) );
}
}
/**
* Output as a string an anchor for a wiki, with the wiki's logo inside.
* @param string $wikiName the wiki's name
- * @param string|null $logoURL URL to the wiki's logo image (if null, render logo as text)
+ * @param string|null $logoURL URL to the wiki's logo image (if null, render
+ * logo as text)
* @param string $wikiURL the URL the anchor goes to
- * @param string $classList a list of the classes to add to the outputted anchor element
- * @param string $wikiTitle (optional) text to use as the anchor's title attribute instead of $wikiName
+ * @param string $classList a list of the classes to add to the outputted
+ * anchor element
+ * @param string $wikiTitle (optional) text to use as the anchor's title
+ * attribute instead of $wikiName
* @return string HTML of the logo anchor
*/
private function makeWikiLinkWithLogo( $wikiName, $logoURL, $wikiURL, $classList, $wikiTitle = '' ) {
$anchorAttribs = [
'href' => $wikiURL,
'title' => $wikiTitle !== '' ? $wikiTitle : $wikiName,
'class' => $classList
];
// If wikiURL is null, we're making a text logo. Otherwise, we're making an
// image logo.
if ( $logoURL === null ) {
return Html::element( 'a', $anchorAttribs, $wikiName );
} else {
$image = Html::element( 'img', [
'src' => $logoURL,
'alt' => $wikiName,
] );
return Html::rawElement( 'a', $anchorAttribs, $image );
}
}
public function execute() {
global $wgMemc;
$skin = $this->getSkin();
$config = $skin->getConfig();
$user = $skin->getUser();
// Title processing
$titleBase = $skin->getTitle();
$title = $titleBase->getSubjectPage();
$titleNamespace = $titleBase->getNamespace();
$key = $wgMemc->makeKey( 'refreshed', 'header' );
$headerCategoriesDropdowns = $wgMemc->get( $key );
if ( !$headerCategoriesDropdowns ) {
$headerCategoriesDropdowns = [];
$skin->addToSidebar( $headerCategoriesDropdowns, 'refreshed-navigation' );
$wgMemc->set( $key, $headerCategoriesDropdowns, 60 * 60 * 24 ); // 24 hours
}
$dropdownCacheKey = $wgMemc->makeKey( 'refreshed', 'dropdownmenu' );
$siteNavigationDropdown = $wgMemc->get( $dropdownCacheKey );
if ( !$siteNavigationDropdown ) {
$siteNavigationDropdown = $this->parseSiteNavigationDropdownMenu( 'Refreshed-wiki-dropdown' );
$wgMemc->set( $dropdownCacheKey, $siteNavigationDropdown, 60 * 60 * 24 ); // 24 hours
}
// url to this wiki's homepage/page you visit when logo is clicked;
// to be used with renderCurrentWikiLogoAndLink
$thisWikiURLMsg = $skin->msg( 'refreshed-this-wiki-url' );
if ( $thisWikiURLMsg->isDisabled() ) {
$thisWikiURL = htmlspecialchars( Title::newMainPage()->getFullURL() );
} else {
$thisWikiURL = $skin->msg( 'refreshed-this-wiki-url' )->escaped();
}
// url to this wiki's logo image (or null if no such image);
// to be used with renderCurrentWikiLogoAndLink
$thisLogoURLMsg = $skin->msg( 'refreshed-this-wiki-wordmark' );
if ( $thisLogoURLMsg->isDisabled() ) {
$thisLogoURL = null;
} else {
$thisLogoURL = $skin->msg( 'refreshed-this-wiki-wordmark' )->escaped();
}
// this wiki's name; to be used with renderCurrentWikiLogoAndLink
$thisWikiName = $config->get( 'Sitename' );
// anchor containing this wiki's logo
$thisWikiLinkWithLogo = $this->makeWikiLinkWithLogo( $thisWikiName, $thisLogoURL, $thisWikiURL, 'refreshed-logo refreshed-logo-current main header-button', $skin->msg( 'Tooltip-p-logo' ) );
$thisWikiLinkWithSidebarLogo = $this->makeWikiLinkWithLogo( $thisWikiName, $thisLogoURL, $thisWikiURL, 'refreshed-logo refreshed-logo-current main', $skin->msg( 'Tooltip-p-logo' ) );
$thisWikiMobileLogo = $skin->msg( 'refreshed-this-wiki-mobile-logo' );
$thisWikiMobileLogoImgElement = '';
if ( !$thisWikiMobileLogo->isDisabled() ) {
$thisWikiMobileLogoImgElement = Html::element( 'img', [
'src' => $thisWikiMobileLogo->escaped(),
'alt' => $config->get( 'Sitename' ),
'class' => 'refreshed-logo'
] );
}
- // allow error handling in makeElementWithIconHelper: see https://secure.php.net/manual/en/simplexml.examples-errors.php
+ $personalTools = $this->getAndRearrangePersonalTools();
+ $dropdownPersonalTools = $personalTools['dropdown'];
+ $extraPersonalTools = $personalTools['extra'];
+
+ // allow error handling in makeElementWithIconHelper:
+ // see https://secure.php.net/manual/en/simplexml.examples-errors.php
libxml_use_internal_errors( true );
// Output the tag and whatnot
$this->html( 'headelement' );
?>
isDisabled() ) { // if a mobile logo has been defined
?>
getToolbox();
// if there are actions like "edit," etc.
// (not counting generic toolbox tools like "upload file")
// in addition to non-page-specific ones like "page" (so a "more..." link is needed)
if ( sizeof( $this->data['content_actions'] ) > 1 ) {
foreach ( $this->data['content_actions'] as $key => $action ) {
if ( !$lastLinkOutsideOfStandardToolboxDropdownHasBeenGenerated ) { // this runs until all the actions outside the dropdown have been generated (generates actions outside dropdown)
// echo $key;
echo $this->makeLinkWithIcon( $key, $key, $action, [
'text-wrapper' => [
'tag' => 'span'
]
] );
$amountOfToolsGenerated++;
if (
sizeof( $this->data['content_actions'] ) == $amountOfToolsGenerated ||
$key == 'history' || $key == 'addsection' ||
$key == 'protect' || $key == 'unprotect'
)
{
// if this is the last action or it is the
// history, new section, or protect/unprotect action
// (whichever comes first)
$lastLinkOutsideOfStandardToolboxDropdownHasBeenGenerated = true;
?>
data['content_actions'] as $key => $action ) { // generates first link (i.e. "page" button on the mainspace, "special page" on Special namespace, etc.); the foreach loop should once run once since there should only be one link
echo $this->makeLinkWithIcon( $key, $key, $action );
}
?>
$toolData ) { // generates toolbox tools inside dropdown (e.g. "upload file")
if ( $tool == 'feeds' ) {
// HACK! Technically this should
// use $wgAdvertisedFeedTypes, which
// *can* include 'rss' in addition
// to 'atom', but only 'atom'
// is enabled by default.
// I wasn't able to get 'rss' working
// locally either, so...
$dataForLink = $toolData['links']['atom'];
} else {
$dataForLink = $toolData;
}
?>
getMsg( 'backlinksubtitle', $title->getPrefixedText() )->escaped(),
[ 'id' => 'back-to-subject' ]
);
}
?>
data['content_actions'] );
$pageTab = key( $this->data['content_actions'] );
$isEditing = in_array(
$skin->getRequest()->getText( 'action' ),
[ 'edit', 'submit' ]
);
// determining how many tools need to be generated
$totalSmallToolsToGenerate = 0;
$listOfToolsToGenerate = [
'wikiglyph wikiglyph-speech-bubbles' => 'ca-talk',
'wikiglyph wikiglyph-pencil-lock-full' => 'ca-viewsource',
'wikiglyph wikiglyph-pencil' => 'ca-edit',
'wikiglyph wikiglyph-clock' => 'ca-history',
'wikiglyph wikiglyph-trash' => 'ca-delete',
'wikiglyph wikiglyph-move' => 'ca-move',
'wikiglyph wikiglyph-lock' => 'ca-protect',
'wikiglyph wikiglyph-unlock' => 'ca-unprotect',
'wikiglyph wikiglyph-star' => 'ca-watch',
'wikiglyph wikiglyph-unstar' => 'ca-unwatch'
];
foreach ( $this->data['content_actions'] as $action ) {
if ( in_array( $action['id'], $listOfToolsToGenerate ) ) { // if the icon in question is one of the listed ones
$totalSmallToolsToGenerate++;
}
}
if ( MWNamespace::isTalk( $titleNamespace ) ) { // if talk namespace
$totalSmallToolsToGenerate--; // remove a tool (the talk page tool) if the user is on a talk page
}
if ( $totalSmallToolsToGenerate > 0 && !$isEditing ) { // if there's more than zero tools to be generated and the user isn't editing a page
?>
data['content_actions'] as $action ) {
if ( $smallToolBeingTested > $amountOfSmallToolsToSkipInFront ) { // if we're not supposed to skip this tool (e.g. if we're supposed to skip the first 2 tools and we're at the 3rd tool, then the boolean is true)
// @todo Maybe write a custom makeLink()-like function for generating this code?
if ( in_array( $action['id'], $listOfToolsToGenerate ) ) { // if the icon being rendered is one of the listed ones (if we're supposed to generate this tool)
?>
printTrail();
echo Html::closeElement( 'body' );
echo Html::closeElement( 'html' );
}
}
diff --git a/refreshed/styles/screen/main.css b/refreshed/styles/screen/main.css
index 2857b89..1dbb7a2 100644
--- a/refreshed/styles/screen/main.css
+++ b/refreshed/styles/screen/main.css
@@ -1,770 +1,791 @@
.refreshed-menu-collapsible {
-ms-transition: max-height 0.4s ease;
transition: max-height 0.4s ease;
/*max-height: 50vh; /* capping the sidebar height at half the height of the window */
visibility: visible;
overflow: hidden;
height: auto;
}
.refreshed-menu-collapsed {
-ms-transition: max-height 0.2s ease, visibility 0.2s;
transition: max-height 0.2s ease, visibility 0.2s;
max-height: 0 !important;
visibility: hidden;
overflow: hidden;
}
.fadable {
-ms-transition: opacity 0.2s ease;
transition: opacity 0.2s ease;
opacity: 1;
visibility: visible;
}
.faded {
-ms-transition: opacity 0.2s ease, visibility 0.2s;
transition: opacity 0.2s ease, visibility 0.2s;
opacity: 0;
visibility: hidden;
}
body {
font-family: "Lato", sans-serif;
margin: 0;
background-color: #194a8d;
-webkit-tap-highlight-color: transparent; /* preventing gray overlay when pressed on mobile webkit browsers (e.g. mobile Safari) */
}
#fade-overlay {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: block;
opacity: 0;
-ms-transition: opacity 0.2s ease;
transition: opacity 0.2s ease;
background: #fff;
}
.fade-overlay-active {
opacity: 0.5 !important;
z-index: 10001; /* very high value to guarantee no elements on wikis will appear above it (except the sidebar) */
cursor: pointer;
}
.fade-overlay-triggered-by-search {
z-index: 10000 !important; /* below header but above page content (used for search) */
cursor: pointer;
}
#header-wrapper {
-webkit-font-smoothing: antialiased;
width: 100%;
height: 3em;
line-height: 3em;
z-index: 10000; /* very high value to guarantee no elements on wikis will appear above it */
position: fixed;
background-color: #103ca2;
-ms-filter: "progid:DXImageTransform.Microsoft.Gradient(GradientType=1,StartColorStr=#040f28,EndColorStr=#103ca2)"; /* IE9+ */
background-image: linear-gradient(to left, #040f28 0, #103ca2 50%, #040f28 100%);
display: flex;
flex-direction: row;
justify-content: space-between;
}
#header-wrapper img {
margin-bottom: 0.3em; /* images aren't perfectly vertically centered, this corrects it. Better fix in future? */
}
.header-button { /* defined at top so individual elements can override these properties */
cursor: pointer;
font-family: "Lato Body", sans-serif;
color: #fff;
display: inline-block;
text-align: center;
}
a.header-button { /* double selectors to override default a element text-decoration without use of !important */
text-decoration: none;
}
.header-dropdown-item a {
color: #fff;
}
.header-dropdown-item a.new {
color: #fdad9d;
}
.header-button:hover,
.header-button-active,
.header-dropdown-item:hover { /* defined at bottom so this overrides individual elements' background-color properties */
background-color: midnightblue;
}
-.header-section {
+.header-section,
+.header-category-dropdown,
+#user-info-dropdown,
+#extra-personal-tools {
display: inline-block; /* sections don't fill up whole header width, blocking each other */
}
.header-button .wikiglyph-user-sleep,
.header-button .wikiglyph-user-smile,
.header-button .wikiglyph-magnifying-glass {
display: inline-block;
vertical-align: middle;
font-size: 2em;
line-height: 1em; /* That is, the line-height is the font size. The line-height and the vertical-align together vertically center the icon. */
}
.no-show {
display: none;
}
.header-menu {
padding: 0 0.2em 0.2em 0.2em;
margin: 0;
z-index: 4;
list-style: none;
background-color: #000;
background-color: rgba(0, 0, 0, 0.8);
position: absolute;
line-height: 1em;
}
.header-menu a {
font-size: 1.125em;
text-decoration: none;
display: block;
padding: 0.2em 0.4em 0.2em 0.4em;
}
#header-categories-user-info-search-wrapper {
display: flex;
flex-direction: row-reverse; /* when only one item inside (#user-info-search-wrapper), push it to the right side (LTR) or left (RTL) */
justify-content: space-between;
width: 100%; /* fill all the space not occupied by #site-info */
}
#user-info-search-wrapper {
flex-shrink: 0; /* when this user info hits the header categories, the header categories should shrink, but the user info shouldn't */
}
+#extra-personal-tools-tray {
+ margin: 0;
+ display: inline-block;
+}
+
+#extra-personal-tools-tray li {
+ display: inline-block;
+ margin: 0 0.75em 0 0; /* adapted from Timeless's #personal-extra li rule */
+}
+
+#pt-notifications-notice,
+#pt-notifications-alert {
+ vertical-align: middle; /* (more or less) center the echo badges */
+}
+
#user-info .header-menu {
right: 0;
width: 11em;
}
#user-info .header-menu a {
padding-right: 0.7em;
text-align: right;
}
.sidebar-shower {
position: absolute;
top: 0;
right: -3em;
background-color: #194a8d;
display: none;
-ms-transition: right 0.2s ease 0s;
transition: right 0.2s ease 0s;
}
#site-info .header-menu {
width: 11.6em;
}
#site-info-main {
float: left;
width: 12em;
}
#site-info-main.multiple-wikis .main {
width: 10em;
float: left; /* prevent arrow from being bumped down */
}
#site-info-main a.main {
width: 12em;
color: #fff;
font-weight: bold;
}
#site-info-main.multiple-wikis .header-menu {
text-align: center;
}
#site-info-mobile {
display: none;
height: 2.75em;
width: 2.75em;
margin-left: 3em; /* 2.75em for sidebar opener, 0.25em of padding */
padding: 0 0.25em;
}
#site-info-mobile .header-button {
display: block;
}
.search-shower {
position: relative;
float: right;
text-align: center;
display: none;
}
#header-wrapper .search {
- width: 11em;
+ width: 15em; /* same as #searchInput */
height: 2em;
display: inline-block;
float: right;
padding-right: 1em;
padding-left: 0.5em;
}
#header-wrapper .search-form {
height: 100%;
}
.search-closer {
position: absolute;
top: 0;
right: 0;
display: none;
z-index: 1;
}
#searchInput {
border: none;
font-size: 1em;
padding: 0.25em;
font-family: "Lato", sans-serif;
box-sizing: border-box;
-webkit-appearance: none; /* prevent Safari from styling input box */
border-radius: 0; /* prevent iOS from adding rounded corners to input box */
- width: 11em;
+ width: 15em; /* same as #header-wrapper .search */
height: 100%;
}
#searchInput::-webkit-input-placeholder {
color: #808080;
font-weight: 700;
}
#searchInput::-moz-placeholder {
color: #808080;
font-weight: 700;
}
#searchInput::-ms-input-placeholder {
color: #808080;
font-weight: 700;
}
#header-categories {
position: relative;
height: 100%;
}
/* Dummy wrapper so the header categories experience overflow-x: hidden and overflow-y: visible.
Inspired by https://stackoverflow.com/a/29687454. Since .refreshed-header-category-dropdown-tray
has position: absolute, it'll pop out of the overflow: hidden. Since .refreshed-dropdown
does not have position: absolute, it will stay hidden if the screen is too narrow and it wraps
out of the header. */
#header-categories-overflow-wrapper {
overflow: hidden;
height: 100%;
}
-.header-category-dropdown {
- display: inline-block; /* so header category dropdowns don't fill up the whole space in #header-categories */
-}
-
.page-item {
display: inline-block;
}
#header-categories .header-button,
#user-info .header-button {
padding-left: 0.5em;
padding-right: 0.5em;
}
#header-categories a:hover,
#header-categories a:focus {
text-decoration: none;
}
#header-categories li img {
margin: 0.5em 3px 4px;
}
#header-categories .header-menu {
width: 10em;
}
#content-heading {
/* padding-left/right: 1em (#bodyContent padding-left) * 0.9em (#bodyContent font-size);
padding-bottom: 0.25em (padding on top of .standard-toolbox) + 1.5em (height of .standard-toolbox) + 0.75em (padding on bottom of .standard-toolbox) */
padding: 0.5em 0.9em 2.5em 0.9em;
position: relative;
}
#firstHeading {
font-size: 2.68em;
font-family: "Lato Body", sans-serif;
padding: 0;
margin-bottom: 0;
border: 0;
}
.mw-indicators {
float: right;
}
.mw-indicator {
display: inline;
}
.scroll-shadow {
background: none;
}
.standard-toolbox {
font-family: "Lato", sans-serif; /* assigning here so em values match em values of the links inside */
height: 1.5em; /* 1em for the text, 0.25em for text padding-top, 0.25em for text padding-bottom; meanwhile the links take up 1.75em when you include their 0.25em border-bottom */
position: absolute;
bottom: 0.75em;
}
.toolbox-link {
cursor: pointer; /* doesn't have href so must be explicitly defined */
}
.standard-toolbox a {
text-transform: lowercase;
font-weight: normal;
text-decoration: none;
}
.standard-toolbox > a,
.standard-toolbox .toolbox-link {
/* page action links outside of the dropdown and the "more..."/"tools" button */
margin-left: 0.5em;
padding-top: 0.25em;
padding-bottom: 0.25em;
}
.standard-toolbox > a:first-of-type {
margin-left: 0;
}
.standard-toolbox > a:hover,
.standard-toolbox > a.selected,
.standard-toolbox .toolbox-link:hover {
border-bottom: 0.25em solid;
}
.refreshed-icon {
width: auto;
height: 1em;
fill: currentColor;
vertical-align: text-bottom;
}
.avatar {
width: 30px;
height: auto;
vertical-align: middle;
}
.avatar-image {
padding-left: 0.25em;
padding-right: 0.25em; /* this class is added to image avatars because they should have padding but the default WikiFont ones shouldn't */
}
.refreshed-icon-menu {
width: 1.5em;
height: auto;
vertical-align: top;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.standard-toolbox-dropdown {
left: 0.5em;
z-index: 3;
background: #fff;
position: absolute;
width: 12em;
box-shadow: 0 3px 9px 0 rgba(75, 75, 75, 0.4);
border: 1px solid #ddd;
margin-top: 0.25em;
}
.standard-toolbox-dropdown ul {
list-style-type: none;
margin-left: 0;
margin-top: 0.25em;
margin-bottom: 0.25em;
}
.dropdown-triangle {
width: 0;
height: 0;
border-left: 0.5em solid transparent;
border-right: 0.5em solid transparent;
border-bottom: 0.5em solid #ccc;
position: absolute;
top: -.5em;
left: 0.1em;
}
.standard-toolbox-dropdown a {
width: 100%;
height: 100%;
display: inline-block;
padding-left: 0.25em;
padding-top: 0.1em;
padding-bottom: 0.1em;
box-sizing: border-box; /* padding/border should be included within the 100% width */
}
.standard-toolbox-dropdown a:hover {
padding-left: 0;
border-left: 0.25em solid;
background-color: #eee;
}
.standard-toolbox-dropdown .toolbox-item-text {
display: inline-block;
margin-left: 2.5em;
text-indent: -1em; /* pulls first line of text 1em to the left, so if you add 1em of padding to the right subsequent lines will look indented */
}
.standard-toolbox-dropdown a:before {
position: absolute;
}
.standard-toolbox .toolbox-dropdown-page-action + .toolbox-dropdown-tool {
/* first tool in the dropdown that is after a page action (so if no page actions are in the dropdown, this CSS doesn't target anything) */
margin-top: .25em;
padding-top: .25em;
border-top: 1px solid #ccc; /* divide the tools from the page actions */
}
.fixed-toolbox {
padding-left: 0.9em; /* 1em (#bodyContent padding-left) * 0.9em (#bodyContent font-size) */
position: fixed;
border-bottom: 0.25em solid #eee;
background: #fff;
top: 3em; /* height of #header */
left: 12em; /* margin-left of #content-wrapper */
right: 1em; /* margin-right of #content-wrapper */
z-index: 9999; /* very high value to guarantee no elements on wikis will appear above it */
}
.dropdown-open {
-ms-transform: translateY(3em);
transform: translateY(3em);
}
#main-title-messages {
border-bottom: 1px solid #ddd;
}
#back-to-subject {
display: none;
font-size: 1.25em;
padding-bottom: 0.25em;
}
#small-toolbox-wrapper {
display: none;
}
#site-notice {
width: 100%;
text-align: center;
}
#new-talk {
margin-left: 2em;
}
#content-wrapper {
min-height: 100%;
position: relative;
top: 3em;
background-color: #fff;
z-index: 1;
font-family: sans-serif;
border-bottom: 0.25em solid #eee;
padding-bottom: 1em;
}
#content-wrapper * {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.4);
}
#bodyContent {
padding: 0 1em;
line-height: 1.5em;
font-size: 0.875em;
word-wrap: break-word;
font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
color: #252525;
overflow: auto;
}
#sidebar-wrapper {
width: 12em;
position: fixed;
top: 0;
color: #fff;
z-index: 2;
height: 100%;
}
/*
* Hide the list bullet icons. This is not terribly useful by default, but there
* are extensions which can add entire portlets to the sidebarwrapper, and said portlets
* might be using the
HTML tag...
*/
#sidebar-wrapper li {
list-style-type: none;
margin: 0;
}
#sidebar-wrapper ul {
margin: 0;
padding: 0;
}
#sidebar-logo {
display: none;
}
#sidebar {
overflow-y: auto;
width: 100%;
height: calc( 100% - 3.5em );
left: 0;
line-height: 1.5em;
position: absolute;
top: 3em;
padding-top: 0.5em;
}
#sidebar a {
display: block;
color: white;
text-decoration: none;
padding-left: 1em;
margin-left: 5px; /* px instead of em because of 5px border on hover */
}
#sidebar a:hover {
color: #ccc;
cursor: pointer;
border-left: 5px solid;
margin-left: 0;
}
.sidebar-section .main {
border: 0;
margin: 0;
margin-left: 0.5em;
margin-top: 0.5em;
padding: 0;
font-size: 1em;
color: inherit;
line-height: 1.5;
}
.sidebar-section:first-of-type h1 {
margin-top: 0;
}
.toolbox-container {
display: inline-block;
position: relative;
}
/********** Footer **********/
#footer {
position: relative;
top: 3.5em;
text-align: center;
padding-bottom: 15px;
color: #ccc;
}
#footer a {
color: #fff;
}
#footer * {
line-height: 1.5;
}
#footer img {
vertical-align: text-top;
}
#footer #advert {
margin-bottom: 1em; /* enough spacing after ad */
}
#footer #advert p {
margin: 0;
}
.footer-row {
margin: 0;
}
.footer-row-item {
margin: 0 0.5em;
display: inline;
}
/******************************** CONTENT/OTHER THINGS BEYOND CONTROL ******************************/
#cats {
padding: 1em;
}
#content-wrapper p:first-of-type {
margin-top: 0;
}
/* Search suggestions */
.header-suggestions { /* there are two .suggestions elements on Special:Search; we only want to apply the CSS to the .header-suggestions element that appears beneath the header, which is first */
z-index: 10000 !important;
position: fixed !important;
top: 2.5em !important; /* there is some JS doing odd stuff, this forces our value */
}
/* dropdowns */
#site-navigation-dropdown {
position: relative;
z-index: 1000; /* 1 less than .refreshed-logo-current to ensure that logo appears in front of the site navigation dropdown */
width: 100%; /* so tray can fit width of sidebar area rather than default width */
}
#site-navigation-dropdown-button {
width: 2em;
}
#site-navigation-dropdown-tray {
width: 100%; /* ensure tray is as wide as #site-info */
}
.refreshed-dropdown:hover .refreshed-dropdown-tray,
.refreshed-dropdown:hover .refreshed-dropdown-triangle,
.refreshed-dropdown:active .refreshed-dropdown-tray,
.refreshed-dropdown:active .refreshed-dropdown-triangle {
display: block; /* show the tray and dropdown arrow */
}
.refreshed-dropdown-tray {
background: #fff; /* same as .refrehed-dropdown-button:after border-bottom color */
position: absolute;
box-shadow: 0 3px 9px 0 rgba(75,75,75,0.4);
padding: .5em 0;
display: none;
/* This width notably more than the max-width of .refreshed-dropdown-triangle
(see its CSS for explanation).
NOTE: This assumes .refreshed-dropdown-triangle and .refreshed-dropdown-tray
have the same font size... */
width: 12em;
margin: 0;
margin-top: -3px; /* opposite of bottom of .refreshed-dropdown-button:after to ensure arrow lines up with tray */
line-height: 1.2em;
- z-index: 999; /* so top part of dropdown doesn't get covered by another .refreshed-dropdown-button */
+ /* so top part of dropdown doesn't get covered by another
+ .refreshed-dropdown-button; 1 less than z-index of
+ .refreshed-dropdown-triangle to ensure the triangle appears above the
+ dropdown and thus is not underneath its drop shadow */
+ z-index: 999;
}
.refreshed-dropdown-button {
position: relative;
}
/*
Why this element exists:
Originally the triangle was a pseudoelement attached to .refreshed-dropdown-button.
We'd center it relative to the button using left: 50%; and transform: translateX(-50%).
This doesn't work when the button width is more than twice dropdown width:
the triangle is so far right it looks disconnected from the dropdown.
One solution is to set a maximum amount left the triangle can be.
To set a maximum left amount we could use max-width.
However the way the pseudoelement was set up, it needs width: 0 for the triangle to actually be a triangle.
So we couldn't solve this problem by changing the pseudoelement's width.
Instead we customize the width of a dummy element, then make the triangle at its top center with a pseudoelement.
*/
.refreshed-dropdown-triangle {
width: 50%;
/* This max-width is notably less than width of .refreshed-dropdown-tray
so the triangle doesn't appear disconnected from tray. Having the max-width
equal the width wouldn't work: the triangle is centered on the right (LTR) or
left (RTL) edge of .refreshed-dropdown-triangle, so if
.refreshed-dropdown-triangle is the same width as a dropdown, the arrow will
peek off of it. We just make the max-width less, without figuringĀ out the
exact number where the triangle would be on the edge of the dropdown, because
we don't want the triangle to be on the exact edge of the dropdown anyway if
possible (it looks weird).
NOTE: This assumes .refreshed-dropdown-triangle and .refreshed-dropdown-tray
have the same font size... */
max-width: 11em;
height: 0;
position: relative;
bottom: 0;
left: 0;
margin: 0;
+ /* 1 more than z-index of .refreshed-dropdown-triangle to ensure the triangle
+ appears above the dropdown and thus is not underneath its drop shadow */
+ z-index: 1000;
display: none;
}
.refreshed-dropdown-triangle:after {
content: "";
border-left: .4em solid transparent;
border-right: .4em solid transparent;
border-bottom: .4em solid #fff; /* same as .refreshed-dropdown-tray background-color */
position: absolute;
bottom: 3px; /* opposite of margin-top of .refreshed-dropdown-tray to ensure arrow lines up with tray */
left: 100%;
transform: translateX(-50%);
}
.refreshed-dropdown-tray a {
color: #000;
font-size: .9em;
padding: .4em;
text-align: initial;
text-align: center;
}
.refreshed-dropdown-tray img {
max-width: 100%;
}
.refreshed-dropdown-tray a:hover {
color: #000;
}
.refreshed-dropdown-tray li {
margin: 0;
list-style: none;
padding: 0;
}
.refreshed-logo-current {
position: relative;
z-index: 1001; /* 1 more than .refreshed-dropdown to ensure that logo appears in front of the site navigation dropdown */
}
.refreshed-logo img {
/* keep the logo from exceeding the space of the header (for current logo)
or being inconveniently big (for logos in dropdown) */
max-height: 2em;
}