diff --git a/includes/RefreshedTemplate.php b/includes/RefreshedTemplate.php index 8cb3804..a010461 100644 --- a/includes/RefreshedTemplate.php +++ b/includes/RefreshedTemplate.php @@ -1,1437 +1,1385 @@ ' ', 'citethispage' => ' ', 'close' => ' ', 'delete' => ' ', 'edit' => ' ', + 'ellipsis' => ' + + + + ', 'emailuser' => ' ', 'feeds' => ' ', 'history' => ' ', 'info' => ' ', 'more' => ' ', 'move' => ' ', 'permalink' => ' ', 'print' => ' ', 'protect' => ' ', 'purge' => ' ', 'recentchangeslinked' => ' ', 'refreshed-collapsible-collapse' => ' ', 'refreshed-collapsible-expand' => ' ', 'refreshed-dropdown-expand' => ' ', 'refreshed-menu' => ' ', 'search' => ' ', 'undelete' => ' ', 'unprotect' => ' ', - 'unwatch' => ' - - ', 'upload' => ' ', 'user-anon' => ' ', 'user-loggedin' => ' ', 'userrights' => ' ', 'viewsource' => ' ', - 'watch' => ' - - ', 'wikilove' => ' ' ]; private static $iconListLTR = [ 'addsection' => ' ', 'contributions' => ' ', 'log' => ' ', 'nstab' => ' ', 'refreshed-explore' => ' ', 'refreshed-submenu-expand' => ' ', 'report-problem' => ' ', 'smwbrowserlink' => ' ', 'talk' => ' ', 'view' => ' ', 'whatlinkshere' => ' ' ]; private static $iconListRTL = [ 'addsection' => ' ', 'contributions' => ' ', 'log' => ' ', 'nstab' => ' ', 'refreshed-explore' => ' ', 'refreshed-submenu-expand' => ' ', 'report-problem' => ' ', 'smwbrowserlink' => ' ', 'talk' => ' ', 'view' => ' ', 'whatlinkshere' => ' ' ]; /** * 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 parseSiteNavigationMenu( $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->parseSiteNavigationItem( $line ); for ( $x = $i; $x >= 0; $x-- ) { if ( $x == 0 ) { break; } } $nodes[$i + 1] = $node; $i++; } } return $nodes; } /** * Helper method for parseSiteNavigationMenu. * Parse one pipe-separated line from MediaWiki message to an array with * indexes "wikiName" (string), "logoURL" (string|null), * "wikiURL" (string|null) * (This array will eventually be used to construct a link in the site * dropdown via renderSiteNavigationItems.) * 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 parseSiteNavigationItem( $line ) { // 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 ( 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 ) { // 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'; } // Get the icon if it is in the list of all icons. // If not, get the icon if it is in the list of the LTR/RTL icons // (depending on the user interface language). if ( array_key_exists( $iconName, self::$iconListAllDirections ) ) { return self::$iconListAllDirections[$iconName]; } else { $languageDirection = $this->getSkin()->getLanguage()->getDir(); if ( $languageDirection === 'ltr' && array_key_exists( $iconName, self::$iconListLTR ) ) { return self::$iconListLTR[$iconName]; } elseif ( $languageDirection === 'rtl' && array_key_exists( $iconName, self::$iconListRTL ) ) { return self::$iconListRTL[$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 * @return string string representing the list item */ private function makeListItemWithIconAtStart( $iconName = '', $key, $item, $options = [] ) { return $this->makeElementWithIconHelper( 'list item', $iconName, $key, $item, $options, 'start' ); } /** * 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 * @return string string representing the link */ private function makeLinkWithIconAtStart( $iconName = '', $key, $item, $options = [] ) { return $this->makeElementWithIconHelper( 'link', $iconName, $key, $item, $options, 'start' ); } /** * Generate a list item using BaseTemplate::makeListItem() that contains the * inline SVG icon specified by $iconName just after 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 * @return string string representing the list item */ private function makeListItemWithIconAtEnd( $iconName = '', $key, $item, $options = [] ) { return $this->makeElementWithIconHelper( 'list item', $iconName, $key, $item, $options, 'end' ); } /** * Generate a link using BaseTemplate::makeLink() that contains the * inline SVG icon specified by $iconName just after 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 * @return string string representing the link */ private function makeLinkWithIconAtEnd( $iconName = '', $key, $item, $options = [] ) { return $this->makeElementWithIconHelper( 'link', $iconName, $key, $item, $options, 'end' ); } /** * Helper method for makeListItemWithIconAtStart, makeLinkWithIconAtStart, * makeListItemWithIconAtEnd, and makeLinkWithIconAtEnd. * * Depending on $mode, generate a) a list item containing a link using * BaseTemplate::makeListItem() or b) just a link using * BaseTemplate::makeLink(). Before or after (depending on settings) 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 $iconPosition where to put the icon within the list item or * link; expects either 'start' or 'end' * @return string string representing the list item/link */ private function makeElementWithIconHelper( $mode, $iconName, $key, $item, $options, $iconPosition ) { // Based on the $mode, either make a list item or link without any icon // added yet. 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 first/last 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. if ( $iconPosition === 'start' ) { $firstATagChild = $firstATagInListItemOrLink->firstChild; } elseif ( $iconPosition === 'end' ) { $firstATagChild = $firstATagInListItemOrLink->lastChild; } $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/end of the first a tag depending on // config settings if ( $firstATagChild === null ) { $firstATagInListItemOrLink->appendChild( $iconInListItemOrLinkDOM ); } else { if ( $iconPosition === 'start' ) { $firstATagInListItemOrLink->insertBefore( $iconInListItemOrLinkDOM, $firstATagChild ); } elseif ( $iconPosition === 'end' ) { $firstATagInListItemOrLink->appendChild( $iconInListItemOrLinkDOM ); } } 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) * @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 $personalTools */ private function renderUserDropdownItems( $dropdownPersonalTools ) { foreach ( $dropdownPersonalTools as $keyAndIconName => $item ) { $item['class'] = 'refreshed-dropdown-item header-dropdown-item user-info-dropdown-item'; echo $this->makeListItemWithIconAtStart( $keyAndIconName, $keyAndIconName, $item, [ 'text-wrapper' => [ 'tag' => 'span' ] ] ); } } /** * Render the items of the site navigation dropdown/collapsible to appear * in the header/sidebar. * @param array $siteNavigation an array containing info for the site * navigation colapsible * @param string $mode whether generating for a 'dropdown' or a 'collapsible' * (affects class names) */ private function renderSiteNavigationItems( $siteNavigation, $mode ) { // (each item in $siteNavigation was an output of // parseSiteNavigationItem) // we're making a bunch of list items here (
  • elements, but NOT ones // created via makeListItem or makeListItemWithIconAtStart...) // the classes to add to each of the dropdown anchors $logoClassList = 'refreshed-logo refreshed-logo-other'; $listItemClassList = ''; if ( $mode === 'dropdown' ) { $listItemClassList = 'refreshed-dropdown-item header-dropdown-item site-navigation-item'; } elseif ( $mode === 'collapsible' ) { $listItemClassList = 'refreshed-collapsible-item site-navigation-item'; } foreach ( $siteNavigation as $wikiLogoInfo ) { // send each of the parsed pieces of wiki logo info to renderWikiLogo // for rendering echo Html::rawElement( 'li', [ 'class' => 'refreshed-' . $mode . '-item site-navigation-item' ], $this->makeWikiLinkWithLogo( $wikiLogoInfo['wikiName'], $wikiLogoInfo['logoURL'], $wikiLogoInfo['wikiURL'], $logoClassList ) ); } } /** * 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 $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 * @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 ) { - $text = Html::element( 'span', [ 'class' => 'header-text' ], $wikiName ); + $text = Html::element( 'span', [ 'class' => 'header-text site-navigation-logo-text' ], $wikiName ); return Html::rawElement( 'a', $anchorAttribs, $text ); } else { $image = Html::element( 'img', [ 'src' => $logoURL, 'alt' => $wikiName, 'class' => 'site-navigation-logo-img site-navigation-logo-full' ] ); return Html::rawElement( 'a', $anchorAttribs, $image ); } } /** * Render the items of the header category dropdown/collapsible to appear in * the header/sidebar. * @param array $headerCategoryDropdown an array containing info for a header * category dropdown * @param string $prefix a prefix to attach to some of the classes of each li * (such as 'header-category') * @param string $mode whether generating for a 'dropdown' or a 'collapsible' * (affects class names) * @param int $index which number header category's dropdown is being * generated */ private function renderHeaderCategoryItems( $headerCategoryDropdown, $index, $mode, $prefix ) { $commonClassList = ''; if ( $mode === 'dropdown' ) { $commonClassList = 'refreshed-dropdown-item header-dropdown-item '; } elseif ( $mode === 'collapsible' ) { $commonClassList = 'refreshed-collapsible-item '; } $prefixedClassList = $prefix . '-dropdown-item ' . $prefix . '-' . strval( $index ) . '-dropdown-item'; $classList = $commonClassList . $prefixedClassList; foreach ( $headerCategoryDropdown as $key => $value ) { // Since the header category items appear multiple times on the page, // they shouldn't have any IDs (otherwise multiple elements would have // the same ID) unset( $value['id'] ); echo Html::rawElement( 'li', [ 'class' => $classList, ], $this->makeLink( $key, $value, [ 'text-wrapper' => [ 'tag' => 'span' ] ] ) ); } } /** * Sort all of the content actions into categories for easier use. * Inspired by and adapted from Skin:Timeless. * @param array $tools the tools to sort * @return array where each key is a category and each item is an array of * the page tools in that category (if nothing is in a category, that * category's key corresponds to an empty array) */ private function sortPageTools( $tools ) { // which category each tool belongs in $categories = [ 'namespaces' => [ 'talk' ], // also anything starting with "nstab-" 'main-actions' => [ 've-edit', 'edit', 'view', 'history', 'addsection', 'viewsource' ], 'page-tools' => [ 'delete', 'rename', 'protect', 'unprotect', 'move', 'whatlinkshere', 'recentchangeslinked', 'print', 'permalink', 'info', 'citethispage', 'feeds' ], 'user-tools' => [ 'contributions', 'blockip', 'userrights', 'log', 'wikilove' ], 'watch' => [ 'watch', 'unwatch' ], 'other' => [ 'upload', 'specialpages' ] // and anything that doesn't fit in other categories ]; // populate the output array with an empty array for each category $output = []; foreach ( $categories as $category => $toolNamesInCurrentCategory ) { $output[$category] = []; } // populate the output array with tools foreach ( $tools as $toolName => $toolDetails ) { + // special case: if a key starts with "nstab-" then put it in namespaces if ( strpos( $toolName, 'nstab-' ) === 0 ) { + if ( isset( $toolDetails['class'] ) ) { + $toolDetails['class'] .= ' ' . 'ca-subject'; + } else { + $toolDetails['class'] = 'ca-subject'; + } $output['namespaces'][$toolName] = $toolDetails; } else { // otherwise place the tool in its correct category foreach ( $categories as $category => $toolNamesInCurrentCategory ) { foreach ( $toolNamesInCurrentCategory as $toolNameInCurrentCategory ) { if ( $toolName == $toolNameInCurrentCategory ) { $output[$category][$toolName] = $toolDetails; $toolPlaced = true; // once tool is placed, move on to next tool (next iteration of // outermost foreach) continue 3; } } } // if tool hasn't been placed anywhere after all that, put it in 'other' $output['other'][$toolName] = $toolDetails; } } return $output; } /** * Render the page tools in the toolbox dropdown. * @param array $pageTools an array of page tools generated by sortPageTools() */ private function renderToolboxDropdownItems( $pageTools ) { $toolboxCategories = [ 'page-tools', 'user-tools' ]; foreach ( $toolboxCategories as $category ) { if ( !empty( $pageTools[$category] ) ) { - echo Html::element( 'dt', [ 'class' => 'refreshed-dropdown-header' ], $this->getMsg( 'refreshed-' . $category )->text() ); + //echo Html::element( 'dt', [ 'class' => 'refreshed-dropdown-header' ], $this->getMsg( 'refreshed-' . $category )->text() ); $this->renderPageToolsInCategory( 'list item', $pageTools, $category ); } } } /** - * Render the page tools that are in the given category, either as list items - * (dd NOT li) or as links (a). - * @param string $mode expects either 'list item' or 'link' + * Render a link for talk pages pointing back to the corresponding subject page + * @param object $title the article's title + */ + private function renderBackToSubjectLink( $title ) { + echo Linker::link( + $title, + $this->getMsg( 'backlinksubtitle', $title->getPrefixedText() )->escaped(), + [ 'id' => 'back-to-subject' ] + ); + } + + /** + * Render the page tools that are in the given category, either as list items, + * description, or as links (a). + * @param string $mode expects 'list item', 'link', or 'other tag' * @param array $pageTools an array of page tools generated by sortPageTools() * @param string $category the category of list items being generated + * @param string $tag (optional) for 'other tag' mode, the type of wrapper tag + * to use + * @param string $itemType (optional) for 'other tag' mode, the type of item + * ('dropdown') or ('inline') that the items inside the tag will be; this + * determines whether items will have the 'dropdown-tool-text' or + * 'inline-tool-text' classes added; note if this is not specified, or if + * its value is neither 'dropdown' nor 'inline', 'other tag' mode will not + * render anything. */ - private function renderPageToolsInCategory( $mode, $pageTools, $category ) { + private function renderPageToolsInCategory( $mode, $pageTools, $category, $tag = '', $itemType = '' ) { // if category is invalid, do nothing if ( !array_key_exists( $category, $pageTools ) ) { return; } - $options = [ 'text-wrapper' => [ 'tag' => 'span' ] ]; + $options = [ 'text-wrapper' => [ 'tag' => 'span', 'attributes' => [ ] ] ]; if ( $mode == 'list item' ) { + $options['text-wrapper']['attributes']['class'] = 'dropdown-tool-text'; foreach ( $pageTools[$category] as $keyAndIconName => $item ) { echo $this->makeListItemWithIconAtStart( $keyAndIconName, $keyAndIconName, $item, $options ); } } elseif ( $mode == 'link' ) { + $options['text-wrapper']['attributes']['class'] = 'inline-tool-text'; foreach ( $pageTools[$category] as $keyAndIconName => $item ) { echo $this->makeLinkWithIconAtStart( $keyAndIconName, $keyAndIconName, $item, $options ); } + } elseif ( $mode == 'other tag' && ( $itemType == 'dropdown' || $itemType == 'inline' ) ) { + $options['text-wrapper']['attributes']['class'] = $itemType . '-tool-text'; + $options['tag'] = $tag; + foreach ( $pageTools[$category] as $keyAndIconName => $item ) { + echo $this->makeListItemWithIconAtStart( $keyAndIconName, $keyAndIconName, $item, $options ); + } } } public function execute() { global $wgMemc, $wgRefreshedUseExploreWithoutHeaderCategories; $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' ); $headerCategories = $wgMemc->get( $key ); if ( !$headerCategories ) { $headerCategories = []; $skin->addToSidebar( $headerCategories, 'refreshed-navigation' ); $wgMemc->set( $key, $headerCategories, 60 * 60 * 24 ); // 24 hours } $dropdownCacheKey = $wgMemc->makeKey( 'refreshed', 'dropdownmenu' ); $siteNavigation = $wgMemc->get( $dropdownCacheKey ); if ( !$siteNavigation ) { $siteNavigation = $this->parseSiteNavigationMenu( 'Refreshed-wiki-dropdown' ); $wgMemc->set( $dropdownCacheKey, $siteNavigation, 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 header-button', $skin->msg( 'Tooltip-p-logo' ) ); $thisWikiLinkWithSidebarLogo = $this->makeWikiLinkWithLogo( $thisWikiName, $thisLogoURL, $thisWikiURL, 'refreshed-logo refreshed-logo-current refreshed-logo-sidebar-current header-button', $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' => 'site-navigation-logo-img site-navigation-logo-icon' ] ); } $personalTools = $this->getAndRearrangePersonalTools(); $dropdownPersonalTools = $personalTools['dropdown']; $extraPersonalTools = $personalTools['extra']; // @TODO remove toolbox - $toolbox = $this->getToolbox(); $pageTools = array_merge( $this->data['content_navigation']['views'], $this->data['content_actions'], $this->getToolbox() ); $pageTools = $this->sortPageTools( $pageTools ); + unset( $this->data['sidebar']['SEARCH'] ); + unset( $this->data['sidebar']['TOOLBOX'] ); + unset( $this->data['sidebar']['LANGUAGES'] ); + $sidebarContentsWikiTools = []; + if ( !empty( $pageTools['other'] ) ) { + $sidebarContentsWikiTools = [ $this->getMsg( 'refreshed-wiki-tools' )->text() => $pageTools['other'] ]; + } + $sidebarContentsLanguages = []; + if ( !empty( $this->data['language_urls'] ) ) { + $sidebarContentsLanguages = [ $this->getMsg( 'otherlanguages' )->text() => $this->data['language_urls'] ]; + } + $sidebarContents = array_merge( $this->data['sidebar'], $sidebarContentsWikiTools, $sidebarContentsLanguages ); + // 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' ); ?>
      renderExtraPersonalTools( $extraPersonalTools )?>
      renderUserDropdownItems( $dropdownPersonalTools ) ?>
    $headerCategoryDropdown ) { ?>
      renderHeaderCategoryItems( $headerCategoryDropdown, $headerCategoryDropdownIndex, 'dropdown', 'header-category' ); ?>
    data['sitenotice'] ) { ?> data['newtalk'] ) { ?>
    html( 'newtalk' ) ?>
    getIndicators(); } ?>

    html( 'title' ) ?>

    msg( 'tagline' ) ?>
    data['subtitle'] || $this->data['undelete'] ) { ?>
    html( 'userlangattributes' ) ?>>html( 'subtitle' ) ?>html( 'undelete' ) ?>
    -
    + +
    html( 'catlinks' ); if ( $this->data['dataAfterContent'] ) { $this->html( 'dataAfterContent' ); } ?>
    printTrail(); echo Html::closeElement( 'body' ); echo Html::closeElement( 'html' ); } } diff --git a/refreshed/scripts/refreshed.js b/refreshed/scripts/refreshed.js index ae19efa..d29a302 100644 --- a/refreshed/scripts/refreshed.js +++ b/refreshed/scripts/refreshed.js @@ -1,302 +1,156 @@ -/* global $ */ -window.Refreshed = { - toolboxDistanceFromTopWhenStatic: $( '.standard-toolbox' ).offset().top, - toolboxIsFixed: false, - usingIOS: false, - thresholdForSmallCSS: 601, - windowStartedSmall: false, - thresholdForBigCSS: 1001, - windowIsBig: false, - windowIsSmall: false, - widthOfSpecialSearchBar: 0, - widthOfSpecialSearchPowerSearchBar: 0, - headerSearchIsOpen: false, - sidebarIsOpen: false, - - setCollapsibleMaxHeights: function() { - var currentHeight = 0; - $( '.refreshed-menu-collapsible' ).each( function () { - currentHeight = $( this )[0].scrollHeight; - //scrollHeight determines the height ignoring the max-height property - //(which is 0 if the dropdown is hidden on page load) - $( this ).css( { 'max-height': currentHeight + 'px' } ); - }); - }, - - shouldToggleFixedToolbox: function() { - if ( !Refreshed.toolboxIsFixed ) { - // reassign this variable every time so it doesn't break if the - // distance to the top changes somehow (e.g. sitenotice has animated height) - // --better safe than sorry - Refreshed.toolboxDistanceFromTopWhenStatic = $( '.standard-toolbox' ).offset().top; - } - distanceScrolled = $( window ).scrollTop(); - toolboxShouldBeToggled = ( Refreshed.toolboxDistanceFromTopWhenStatic - distanceScrolled <= $( '#header-wrapper' ).height() && !Refreshed.toolboxIsFixed ) || ( Refreshed.toolboxDistanceFromTopWhenStatic - distanceScrolled > $( '#header-wrapper' ).height() && Refreshed.toolboxIsFixed ); // true if 1) the page is scrolled enough for .standard-toolbox to be fixed and it isn't or 2) page isn't scrolled enough for it to be fixed and it is - return toolboxShouldBeToggled; - }, - - toggleFixedToolbox: function() { - $( '.standard-toolbox' ).toggleClass( 'fixed-toolbox' ); - if ( $( '.standard-toolbox' ).hasClass( 'fixed-toolbox' ) ) { - Refreshed.toolboxIsFixed = true; - } else { - Refreshed.toolboxIsFixed = false; - } - }, +/****** The code that runs in scroll events was written without jQuery for +faster performance. Speed isn't as important here, so this code uses +jQuery for readability. ******/ + +// based on https://stackoverflow.com/a/39993724 +if ( document.readyState === 'interactive' || document.readyState === 'complete' ) { + runRefreshedJS(); +} else { + document.addEventListener('DOMContentLoaded', function () { + runRefreshedJS(); + } ); +} - showHideOverflowingDropdowns: function() { - $( '.page-item-has-children' ).each( function() { - if ( $( this ).offset().top > $( '#header-wrapper' ).height() + $( '#header-wrapper' ).offset().top ) { // if the .page-item is beneath the bottom of the header (and so it's cut off by overflow:hidden) - $( this ).children( '.children' ).css( { 'display': 'none' } ); - $( this ).removeClass( 'header-button-active' ); - $( this ).children( '.header-button' ).children( '.arrow' ).removeClass( 'rotate' ); +function runRefreshedJS() { + + var Refreshed = { + toolbox: document.getElementById( 'refreshed-toolbox' ), + stuckClass: 'refreshed-toolbox-stuck', + toolboxHasStuckClass: false, + searchInput: document.getElementById( 'searchInput' ), + suggestionsClass: 'suggestions', + headerSuggestionsId: 'header-suggestions', + body: document.body, + sidebarTogglerCheckbox: document.getElementById( 'sidebar-toggler-checkbox' ), + sidebarToggler: document.getElementById( 'sidebar-toggler' ) + }; + + /******************************************************************************* + ************************************ TOOLBOX *********************************** + *******************************************************************************/ + + /******** Add/remove class from toolbox based if toolbox is stuck/ not ********/ + + if ( CSS.supports( 'position: sticky' ) || CSS.supports( 'position: -webkit-sticky' ) ) { + Refreshed.toolboxHasStuckClass = Refreshed.toolbox.classList.contains( Refreshed.stuckClass ); + + // returns true if the toolbox's top property (in pixels) is the distance that + // the toolbox is from the top of the viewport, as in that case, it is stuck + Refreshed.toolboxIsStuck = function() { + return parseFloat( window.getComputedStyle( this.toolbox ).getPropertyValue( 'top' ) ) == this.toolbox.getBoundingClientRect().top; + }; + + // remove stuckClass from toolbox if toolbox isn't stuck; + // add stuckClass to toolbox if toolbox is stuck + Refreshed.updateToolboxClasses = function() { + if ( this.toolboxIsStuck() != this.toolboxHasStuckClass ) { + this.toolbox.classList.toggle( this.stuckClass ); + this.toolboxHasStuckClass = !this.toolboxHasStuckClass; } - } ); - }, + }; - toggleCollapse: function( trigger ) { - $( trigger ).siblings( '.refreshed-menu-collapsible' ).addBack( '.refreshed-menu-collapsible' ).toggleClass( 'refreshed-menu-collapsed' ); - $( trigger ).children( '.arrow' ).toggleClass( 'rotate' ); - if ( $( trigger ).hasClass( 'header-button' ) ) { - $( trigger ).toggleClass( 'header-button-active' ); - } - $( trigger ).parent().toggleClass( 'open-collapsible-parent' ); - }, + // run once in case the toolbox starts out as stuck on page load + Refreshed.updateToolboxClasses(); - toggleFade: function( trigger ) { - $( trigger ).siblings( '.fadable' ).addBack( '.fadable' ).toggleClass( 'faded' ); - $( trigger ).children( '.arrow' ).toggleClass( 'rotate' ); - if ( $( trigger ).hasClass( 'header-button' ) ) { - $( trigger ).toggleClass( 'header-button-active' ); - } - $( trigger ).parent().toggleClass( 'open-fadable-parent' ); - }, + // attach/detach the toolbox to the top depending on scroll position + window.addEventListener( 'scroll', function() { + Refreshed.updateToolboxClasses(); + } ); + } - toggleHeaderSearch: function() { - $( '.sidebar-shower' ).toggleClass( 'sidebar-shower-hidden' ); - $( '#fade-overlay' ).toggleClass( 'fade-overlay-active fade-overlay-triggered-by-search' ); // toggle the fade overlay - if ( Refreshed.windowIsSmall ) { - // On small, because .search-shower is replaced by .search-closer and - // vice-versa instead of the buttons appearing active, they take on - // the .header-button-active class when they shouldn't; this gets rid of it. - // On medium there's only .search-shower, so it functions properly - // and the class shouldn't be removed. - $( '.search-shower' ).removeClass( 'header-button-active' ); - $( '.search-closer' ).removeClass( 'header-button-active' ); - } - if ( Refreshed.headerSearchIsOpen ) { - $( '#searchInput' ).val( '' ).blur(); - } else { - $( '#searchInput' ).focus(); - } - Refreshed.headerSearchIsOpen = !Refreshed.headerSearchIsOpen; - }, + /******************************************************************************* + ************************************ SEARCH ************************************ + *******************************************************************************/ - toggleSidebar: function() { - $( '#sidebar-wrapper' ).toggleClass( 'sidebar-open' ); - $( '#fade-overlay' ).toggleClass( 'fade-overlay-active fade-overlay-triggered-by-sidebar' ); // toggle the fade overlay - $( '.sidebar-shower' ).toggleClass( 'header-button-active' ); - Refreshed.sidebarIsOpen = !Refreshed.sidebarIsOpen; - } -}; + /***************** Clear the search bar when it's not focused; ****************/ + /*********** focus the search bar when the search button is opened ************/ -$( function() { - if ( navigator.userAgent.toLowerCase().match( /(iPad|iPhone|iPod)/i ) ) { // detect if on an iOS device - Refreshed.usingIOS = true; - } + Refreshed.clearTopSearchBar = function() { + this.searchInput.value = ''; + }; - if ( $( window ).width() < Refreshed.thresholdForSmallCSS ) { - Refreshed.windowStartedSmall = true; - } + Refreshed.focusTopSearchBar = function() { + this.searchInput.focus(); + }; - // test if window is running big.css - if ( $( window ).width() >= Refreshed.thresholdForBigCSS ) { - Refreshed.windowIsBig = true; - } else { - Refreshed.windowIsBig = false; - } + $( '#header-search-dropdown-checkbox' ).change( function() { + Refreshed.clearTopSearchBar(); + if ( this.checked ) { + Refreshed.focusTopSearchBar(); + } + } ); - // test if window is running small.css - if ( $( window ).width() <= Refreshed.thresholdForSmallCSS ) { - Refreshed.windowIsSmall = true; - } else { - Refreshed.windowIsSmall = false; - } + /*********** Add class to the suggestions box for the header search ***********/ + + Refreshed.labelHeaderSuggestionsBox = function( headerSuggestions ) { + headerSuggestions.id = Refreshed.headerSuggestionsId; + }; + + // find the 1st search suggestions element that's added, give it the id + // Refreshed.headerSuggestionsId + Refreshed.mutationCallback = function( mutationList, observer ) { + mutationList.forEach( function( mutation ) { + if ( mutation.type == 'childList' ) { + var lastSuggestionsElement = null; + // get the first added search suggestions element + mutation.addedNodes.forEach( function( element ) { + if ( element.classList.contains( Refreshed.suggestionsClass ) ) { + // give the element the id Refreshed.headerSuggestionsId + Refreshed.labelHeaderSuggestionsBox( element ); + // the goal is done; no need to keep observing/iterating + observer.disconnect(); + return; + } + } ); + } + } ); + }; - if ( Refreshed.shouldToggleFixedToolbox() ) { - Refreshed.toggleFixedToolbox(); - } + Refreshed.searchSuggestionsMutationObserver = new MutationObserver( Refreshed.mutationCallback ); + Refreshed.searchSuggestionsMutationObserver.observe( Refreshed.body, { childList: true, attributes: false, subtree: false } ); - Refreshed.setCollapsibleMaxHeights(); + /******************************************************************************* + *********************************** CHECKBOXES ********************************* + *******************************************************************************/ - $( window ).scroll( function() { - if ( Refreshed.shouldToggleFixedToolbox() ) { - Refreshed.toggleFixedToolbox(); - } - } ); + /******** Hide dropdowns if their corresponding button is hidden; ***********/ + /**************** hide sidebar when sidebar button is hidden ****************/ - $( window ).resize( function() { - // uncheck checkboxes that may have stayed checked despite their button - // disappearing (for example the header category checkboxes may stay - // checked, so their dropdowns may stay open, after the viewport shrinks - // and the header category buttons disappear) + // for example, the header category checkboxes may stay checked, so their + // dropdowns may stay open, after the viewport shrinks and the header category + // buttons disappear + Refreshed.hideDropdownsWithHiddenButtons = function() { var checkbox = null; var button = null; var headerTopCoord = 0; var buttonTopCoord = 0; var headerBottomCoord = 0; var buttonBottomCoord = 0; - $( '.refreshed-dropdown-tray:visible' ).each( function() { - checkbox = $( this ).siblings( '.refreshed-dropdown-checkbox' ).first(); - button = $( this ).siblings( '.refreshed-dropdown-toggle' ).first().children( '.refreshed-dropdown-button' ).first(); + $( '.refreshed-dropdown-tray' ).not(':visible').each( function() { + checkbox = $( this ).siblings( '.refreshed-checkbox' ).first(); + button = $( this ).siblings( '.refreshed-dropdown-button' ).first(); // if the button has overflowed, its bottom is above the top of the // header, or its top is above the bottom of the header headerTopCoord = $( '#header-wrapper' ).offset().top; buttonTopCoord = $( button ).offset().top; headerBottomCoord = headerTopCoord + $( '#header-wrapper' ).outerHeight(); buttonBottomCoord = buttonTopCoord + $( button ).outerHeight(); if ( buttonBottomCoord <= headerTopCoord || buttonTopCoord >= headerBottomCoord ) { checkbox.prop( 'checked', false ); } } ); + }; - if ( $( window ).width() >= Refreshed.thresholdForBigCSS ) { - Refreshed.windowIsBig = true; - } else { - Refreshed.windowIsBig = false; - } - - if ( $( window ).width() <= Refreshed.thresholdForSmallCSS ) { - Refreshed.windowIsSmall = true; - } else { - Refreshed.windowIsSmall = false; - } - - Refreshed.showHideOverflowingDropdowns(); - } ); - - $( '#header-search-dropdown-checkbox' ).change( function() { - // reset the contents of the search bar - $( '#searchInput' ).val( '' ); - // give search bar focus if opening - if ( this.checked ) { - $( '#searchInput' ).focus(); - } - } ); - - // working code for dropdowns. Note: simple code like this is much better than complicated like below :) - $( 'a.header-button.collapse-trigger' ).click( function( e ) { - Refreshed.toggleCollapse( this ); - } ); - - $( 'a.toolbox-link.collapse-trigger' ).click( function( e ) { - Refreshed.toggleCollapse( this ); - } ); - - $( document ).click( function( e ) { - $( '.open-collapsible-parent' ).each( function () { - // target each .open-collapsible-parent (i.e., each parent of an open .collapsible element) - if ( !$( e.target ).closest( $( this ) ).length ) { - // if starting from the element clicked and moving up the DOM, we don't - // run into that the current .open-collapsible-parent... - Refreshed.toggleCollapse( $( this ).children( '.collapse-trigger' ) ); - } - }); - } ); - - $( 'a.header-button.fade-trigger' ).click( function( e ) { - Refreshed.toggleFade( this ); - } ); - - $( 'a.toolbox-link.fade-trigger' ).click( function( e ) { - Refreshed.toggleFade( this ); - } ); - - $( document ).click( function( e ) { - $( '.open-fadable-parent' ).each( function () { - if ( !$( e.target ).closest( $( this ) ).length ) { - Refreshed.toggleFade( $( this ).children( '.fade-trigger' ) ); - } - }); - } ); - - $( 'a.sidebar-shower' ).click( function( e ) { - Refreshed.toggleSidebar(); - } ); - - $( 'a.search-shower' ).click( function( e ) { - Refreshed.toggleHeaderSearch(); - } ); - - $( 'a.search-closer' ).click( function( e ) { - Refreshed.toggleHeaderSearch(); - } ); - - $( 'a#fade-overlay' ).click( function( e ) { - // Unfortunately breaking this into two doesn't work. It might be - // because the .fade-overlay-triggered-by-SOMETHING classes aren't - // applied yet on $() (and this function is inside - // the $() ), so as far as the code is concerned - // elements with that class don't exist. - if ( $( this ).hasClass( 'fade-overlay-triggered-by-sidebar' ) ) { - Refreshed.toggleSidebar(); - } else if ( $( this ).hasClass( 'fade-overlay-triggered-by-search' ) ) { - Refreshed.toggleHeaderSearch(); + Refreshed.hideSidebarWhenSidebarTogglerHidden = function() { + if ( Refreshed.sidebarTogglerCheckbox.checked && $( Refreshed.sidebarToggler ).is( ':hidden' ) ) { + Refreshed.sidebarTogglerCheckbox.checked = false; } - } ); - - /* Useful to follow click propagation for debugging - $( '*' ).click( function ( e ) { - alert( '<' + this.nodeName + ' id="' + this.id + '" class="' + this.className + '">' ); - } ); - */ + }; - /* Temporarily disabled to fix front-end bugs - $( '#icon-ca-watch, #icon-ca-unwatch' ).parent().tap( function( e ) { - // AJAX for watch icons - var action, api, $link, title, otherAction; - - e.preventDefault(); - e.stopPropagation(); - - title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) ); - mw.loader.load( ['mediawiki.notification'], null, true ); - action = mw.util.getParamValue( 'action', this.href ); - otherAction = action === 'watch' ? 'unwatch' : 'watch'; - $link = $( this ); - $( 'div', this ).attr( 'id', 'icon-ca-' + otherAction ); - $( this ).attr( 'href', this.href.replace( action, otherAction ) ); - - api = new mw.Api(); - api[action]( title ) - .done( function ( watchResponse ) { - mw.notify( $.parseHTML( watchResponse.message ), { - tag: 'watch-self' - } ); - - $( '#wpWatchthis' ).prop( 'checked', watchResponse.watched !== undefined ); - } ); + window.addEventListener( 'resize', function() { + Refreshed.hideDropdownsWithHiddenButtons(); + Refreshed.hideSidebarWhenSidebarTogglerHidden(); } ); - */ - - /*$( '#sidebar-wrapper' ).on( 'swipeleft', function( e ) { - if ( Refreshed.sidebarIsOpen ) { - e.preventDefault(); // prevent user from accidentally clicking link on swipe - Refreshed.toggleSidebar(); - } - } );*/ - setTimeout( function () { // wait a bit so the .suggestions elements can be added in (if we don't wait we'll be targeting nothing and it won't work)... - $( '.suggestions' ).last().addClass( 'header-suggestions' ); // add class to first .suggestions element - }, 100 ); -} ); - -/* Fix for Echo in Refreshed */ -if ( document.getElementById( 'echo' ) ) { - $( '#pt-notifications-alert' ).prependTo( '#echo' ); - $( '#pt-notifications-notice' ).prependTo( '#echo' ); -} -if ( $( '.mw-echo-notifications-badge' ).hasClass( 'mw-echo-unread-notifications' ) ) { - $( '#pt-notifications-personaltools a' ).addClass( 'pt-notifications-personaltools-unread' ); } diff --git a/refreshed/styles/screen/big.less b/refreshed/styles/screen/big.less index 0d5ab47..b1949ed 100644 --- a/refreshed/styles/screen/big.less +++ b/refreshed/styles/screen/big.less @@ -1,114 +1,128 @@ @import 'variables.less'; @import 'mediawiki.mixins'; +/******************************************************************************* +**************************** DROPDOWNS/COLLAPSIBLES **************************** +*******************************************************************************/ + +/* Make right: 0; stick dropdown trays to the right edge of the dropdown, +not the viewport */ +#toolbox-dropdown { + position: relative; +} + +/* toolbox */ +a#back-to-subject { /* a for specificity */ + display: none; +} + /******************************************************************************* ************************************ HEADER ************************************ *******************************************************************************/ .header-height( @big-header-height ); .header-button-textless, .header-button-textless-big { width: @big-header-height; padding: 0; } .refreshed-dropdown-tray { width: @big-dropdown-tray-width; } .single-wiki .refreshed-logo-current { min-width: @big-sidebar-width; } .site-navigation-logo-img { max-width: @big-site-navigation-logo-img-max-width; } #site-navigation-header { .site-navigation-icon-logos { display: none; } } #explore-header-categories-dropdowns { margin-top: -( @big-header-height ); } #sidebar-toggler { display: none; } #header-search-dropdown-button { display: none; } #header-search-dropdown-tray { /* override normal search dropdown styles */ background: none; margin: inherit; padding: inherit; top: 0; height: inherit; z-index: initial; .box-shadow( none ); .flex-display( inline-flex ); .flex-justify-content( center ); .flex-align-items( center ); /* ensure .searchButton is positioned relative to this */ position: relative; /* search dropdown should be wide enough to fit search bar */ width: @big-header-search-width; } .searchButton { /* take up full height and stick to right side of #searchInput */ top: 0.5em; bottom: 0.5em; right: 0; /* ooui-icon-search */ background-image: url( data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M19%2017l-5.15-5.15a7%207%200%201%200-2%202L17%2019zM3.5%208A4.5%204.5%200%201%201%208%2012.5%204.5%204.5%200%200%201%203.5%208z%22%2F%3E%3C%2Fsvg%3E ); } -#user-info-search-wrapper { - margin-right: @big-content-margin-right; -} - #user-info-dropdown-button { .extra-header-button-padding(); } /******************************************************************************* ************************************ SIDEBAR *********************************** *******************************************************************************/ #sidebar-wrapper { width: @big-sidebar-width; } #site-navigation-sidebar-header-categories-sidebar-wrapper { display: none; } /******************************************************************************* ************************************ CONTENT *********************************** *******************************************************************************/ /* @TODO */ -#content-wrapper { - left: @big-sidebar-width; - right: 1em; +.content-wrapper-width( @big-sidebar-width, @big-content-right ); +.use-refreshed-toolbox-sticky( @big-header-height ); + +#back-to-subject { + display: none; } + /******************************************************************************* ************************************ FOOTER ************************************ *******************************************************************************/ /* @TODO */ /******************************************************************************* ************************************** OLD ************************************* *******************************************************************************/ diff --git a/refreshed/styles/screen/common.less b/refreshed/styles/screen/common.less index b604c26..7858993 100644 --- a/refreshed/styles/screen/common.less +++ b/refreshed/styles/screen/common.less @@ -1,870 +1,813 @@ @import 'variables.less'; body { font-family: @heading-font-stack; background: @body-background; .font-color( @body-font-color ); margin: 0; } .refreshed-icon { - height: 1em; + width: auto; /* in execution, often same as @refreshed-icon-height */ + height: @refreshed-icon-height; fill: currentColor; } /******************************************************************************* **************************** DROPDOWNS/COLLAPSIBLES **************************** *******************************************************************************/ /**** "checkbox hack" for dropdowns without JS ****/ /* hide checkboxes */ .refreshed-checkbox { position: fixed; top: -9999px; left: -9999px; } /*** styles of things that are opened by the checkboxes ***/ /** dropdowns **/ /* style */ .refreshed-dropdown-button { position: relative; /* place dropdowns relative to matching .header-button */ + cursor: pointer; } -/* Make right: 0; sticks dropdown trays to the right edge of the dropdown, -not the viewport */ -.toolbox-dropdown { - position: relative; -} - -.toolbox-dropdown-tray { +#toolbox-dropdown-tray { right: 0; } -.content-dropdown-button { +#toolbox-dropdown-button { .flex-display( inline-flex ); .flex-align-items( center ); background: @content-dropdown-button-background; padding: 0.25em; + vertical-align: middle; + color: @content-dropdown-button-font-color; &:hover, + &:focus, &:active { background: @content-dropdown-button-hover-background; } } /* 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 { display: none; width: 50%; height: 0; position: absolute; bottom: 0; left: 0; margin: 0; z-index: @z-index-dropdown-triangle; /* Since the dropdown triangle is above the dropdown tray, which is not covered by the huge pseudoelement, hovering over it when the pseudoelement is open causes the cursor to turn into a pointer (since the triangle is inside a label element). Force the cursor to be the default one instead. */ cursor: auto; } .refreshed-dropdown-triangle::after { content: ""; border-left: .4em solid transparent; border-right: .4em solid transparent; border-bottom: .4em solid @dropdown-tray-background-color; position: absolute; bottom: -( @dropdown-tray-margin-top ); left: 100%; transform: translateX( -50% ); } .refreshed-dropdown-tray { padding-top: @dropdown-tray-vertical-padding; padding-bottom: @dropdown-tray-vertical-padding; margin: 0; position: absolute; margin-top: @dropdown-tray-margin-top; line-height: 1.2em; background: @dropdown-tray-background-color; z-index: @z-index-dropdown-tray; list-style-type: none; color: @dropdown-tray-font-color; + max-height: 60vh; + overflow-y: auto; .stack-box-shadow(); a { .pretty-anchor( @active-background-color: @dropdown-tray-hover-background-color ); /* center any icons relative to the link text */ .flex-display( ); .flex-align-items( center ); } .refreshed-icon { .flex( @grow: 0, @shrink: 0 ); - margin-right: 0.4em; + margin-right: @dropdown-tray-refreshed-icon-margin-left; } .refreshed-icon-refreshed-submenu-expand { margin: 0; } span { .flex( @grow: 1, @shrink: 1 ); } dd { margin: 0; } dt { margin: 0; margin-left: @dropdown-tray-vertical-padding; margin-bottom: 0.1em; } } +/* giving the explore dropdown tray overflow: scroll would prevent the submenu +dropdown trays from appearing */ +#explore-dropdown-tray { + max-height: initial; + overflow: initial; +} +/* if an item in the toolbox has no icon, push it left to align with the items +that do have icons */ +#toolbox-dropdown-tray span:first-child { + margin-left: @refreshed-icon-height + @dropdown-tray-refreshed-icon-margin-left; +} + /* when the dropdown is open, give the label a huge child so clicking anywhere outside the dropdown closes the box. The first selector is for when checkboxes can be placed as a prior sibling to their contents. The second selector is useful for the sidebar toggle checkbox, because it is not a sibling of the elements it affects (it can't be, since the two elements are in different containers). The checkbox should affect the sidebar button (in #header-wrapper) and the sidebar (in #sidebar-wrapper). */ -.refreshed-dropdown-checkbox ~ .refreshed-dropdown-button::after, -#sidebar-toggler-checkbox ~ #header-wrapper #sidebar-toggler-button::after { +.refreshed-dropdown-button::after, +#sidebar-toggler-button::after { content: ""; position: fixed; top: 0; bottom: 0; left: 0; right: 0; cursor: default; /* z-index so this pseudoelement covers all dropdown buttons (the dropdown buttons don't have a z-index, so the pseudoelement will cover them). */ z-index: @z-index-close-overlay; pointer-events: none; .transition( background-color 0.1s ease ); /* include the box-sizing so we can be confident that jQuery's outerHeight() matches the actual height of the header */ .box-sizing( border-box ); } /* a for specificity note these rules are overridden by .pretty-anchor() in .refreshed-checkbox:checked ~ .refreshed-dropdown-button .explore-submenu-dropdown-anchor so another copy is there too */ a.explore-submenu-dropdown-anchor { .flex-display(); .flex-justify-content( space-between ); .flex-align-items( center ); } .explore-submenu-dropdown { position: relative; /* for proper submenu placement */ } .explore-submenu-dropdown-tray { left: 100%; top: -( @dropdown-tray-vertical-padding ); margin-top: 0; } .refreshed-checkbox:checked ~ .header-button.refreshed-dropdown-button, #sidebar-toggler-checkbox:checked ~ #header-wrapper #sidebar-toggler-button { background: @button-selected-background; } -.refreshed-checkbox:checked ~ .content-dropdown-button { +#toolbox-dropdown-checkbox:checked ~ #refreshed-toolbox #toolbox-dropdown-button { background: @content-dropdown-button-hover-background; } /* reset CSS of feed links */ a.feedlink { background-image: none; margin: 0; } /* functionality */ .refreshed-dropdown-tray { display: none; } .refreshed-checkbox:checked ~ .refreshed-dropdown-tray, -.refreshed-checkbox:checked ~ .refreshed-dropdown-button .refreshed-dropdown-triangle { +.refreshed-checkbox:checked ~ .refreshed-dropdown-button .refreshed-dropdown-triangle, +#toolbox-dropdown-checkbox:checked ~ #refreshed-toolbox #toolbox-dropdown-tray, +#toolbox-dropdown-checkbox:checked ~ #refreshed-toolbox #toolbox-dropdown-triangle { display: block; } +/* see variable definition in variables.less for explanation */ +#toolbox-dropdown-checkbox:checked ~ #refreshed-toolbox { + z-index: @z-index-refreshed-toolbox-stuck-when-dropdown-open; +} + .refreshed-checkbox:checked ~ .refreshed-dropdown-button::after, -#sidebar-toggler-checkbox:checked ~ #header-wrapper #sidebar-toggler-button::after { +#sidebar-toggler-checkbox:checked ~ #header-wrapper #sidebar-toggler-button::after, +#toolbox-dropdown-checkbox:checked ~ #refreshed-toolbox #toolbox-dropdown-button::after { background-color: @close-overlay-shadow-color; pointer-events: auto; } #explore-dropdown-tray .refreshed-checkbox:checked ~ .refreshed-dropdown-button::after { background: transparent; } .refreshed-checkbox:checked ~ .refreshed-dropdown-button .explore-submenu-dropdown-anchor { .pretty-anchor( @active: true, @active-background-color: @dropdown-tray-hover-background-color ); /* override .pretty-anchor() styles (the rules below are copied from a.explore-submenu-dropdown-anchor) */ .flex-display(); } /** collapsibles **/ /* style */ .refreshed-collapsible-button { width: 100%; box-sizing: border-box; } .header-category-collapsible-button { padding: @header-category-collapsible-padding; } .header-category-sidebar-name { .flex( @grow: 1, @shrink: 1 ); text-align: left; } .header-categories-sidebar-collapsible-icons-wrapper { .flex( @grow: 0, @shrink: 0 ); } -/* remove the margin that's applied to adjacent svgs */ -.header-button svg.refreshed-icon { - margin: 0; -} +// /* remove the margin that's applied to adjacent svgs */ +// .header-button svg.refreshed-icon { +// margin: 0; +// } .refreshed-checkbox:checked ~ .refreshed-collapsible-button, .refreshed-checkbox:checked ~ #site-navigation-sidebar-buttons-wrapper .refreshed-collapsible-button, .refreshed-collapsible-tray { background: @button-selected-background; } /* functionality */ .refreshed-icon-refreshed-collapsible-collapse, .refreshed-collapsible-tray { display: none; } .refreshed-icon-refreshed-collapsible-expand { display: block; } .refreshed-checkbox:checked ~ .refreshed-collapsible-button .refreshed-icon-refreshed-collapsible-expand, .refreshed-checkbox:checked ~ #site-navigation-sidebar-buttons-wrapper .refreshed-icon-refreshed-collapsible-expand { display: none; } .refreshed-checkbox:checked ~ .refreshed-collapsible-tray, .refreshed-checkbox:checked ~ .refreshed-collapsible-button .refreshed-icon-refreshed-collapsible-collapse, .refreshed-checkbox:checked ~ #site-navigation-sidebar-buttons-wrapper .refreshed-icon-refreshed-collapsible-collapse { display: block; } /******************************************************************************* ************************************ HEADER ************************************ *******************************************************************************/ /**** Header layout ****/ #header-wrapper { position: fixed; width: 100%; background: @header-background; z-index: @z-index-header; .flex-display(); .flex-direction( row ); .flex-justify-content( space-between ); .stack-box-shadow(); /* height: SIZE-header-height; */ .give-children-height(); } .header-button { .flex-display( inline-flex ); .flex-justify-content( center ); .flex-align-items( center ); cursor: pointer; text-align: center; /* remove default anchor styling (for text wiki logos) */ a& { color: inherit; text-decoration: none; } &:hover { background: @button-hover-background; } &:active { background: @button-selected-background; } } /* if multiple items inside a header button that's actually in the header (as opposed to the sidebar), space them out */ -#header-wrapper .header-button { +#header-wrapper .header-button, +#refreshed-toolbox { img, svg, - .header-text { + .header-text, + .inline-tool-text { &:not(:only-child):not(:first-child) { margin-left: 0.25em; } } } .header-section { display: inline-block; } #sidebar-toggler { .flex( @grow: 0, @shrink: 0 ); } #site-navigation-header { .flex( @grow: 0, @shrink: 0 ); } -.site-navigation-logo-img { +.site-navigation-logo-img, +.site-navigation-logo-text { /* width: SIZE-logo-img-width; */ .extra-header-button-padding(); .box-sizing( border-box ); /* padding should be included within the 100% width */ } #site-navigation-header-dropdown .refreshed-dropdown-button { width: @site-navigation-toggle-width; .site-navigation-button { width: 100%; } } .site-navigation-tray, #sidebar-wrapper .site-navigation-tray { /* #sidebar-wrapper for specificity */ text-align: center; padding-left: 0; padding-right: 0; a { border: 0; } } #header-categories-user-info-search-wrapper { .flex( @grow: 1, @shrink: 1 ); .flex-display(); .flex-direction( row-reverse ); } #user-info-search-wrapper { /* when user info hits the header categories, the header categories should shrink, but the user info shouldn't */ .flex( @grow: 0, @shrink: 0 ); } #extra-personal-tools, #user-info-dropdown { display: inline-block; } #extra-personal-tools-tray { .flex-display( inline-flex ); .flex-align-items( center ); margin: 0; list-style: none; height: 100%; } #pt-notifications-alert, #pt-notifications-notice { margin: 0; padding-right: 0.75em; } #pt-notifications-alert { margin-right: 0.25em; } #user-info-dropdown-button { .avatar { width: @avatar-width; height: @avatar-height; } } #header-search-dropdown-tray { /* width: SIZE-header-search-width; */ box-sizing: border-box; } #searchInput { border: 0; /*font-size: 1em; padding: 0.25em; /* .searchButton's background-size + #searchInput's padding-top + #searchInput's padding-bottom = #searchInput's height */ .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 */ font-size: @header-search-font-size; height: @header-search-height; padding: @header-search-padding; padding-right: @header-search-height; /* make room for search button */ width: 100%; .mixin-placeholder( { .searchInput-placeholder(); } ); /* prevent Safari from styling input box (adding text indent) per https://stackoverflow.com/a/11128262 */ &::-webkit-search-decoration { -webkit-appearance: none; } } .searchButton { background-color: transparent; border: 0; margin: 0; padding: 0; position: absolute; font-size: inherit; text-indent: -99999px; width: @header-search-height; height: @header-search-height; background-position: center center; background-size: 1.5em 1.5em; background-repeat: no-repeat; cursor: pointer; } #explore-header-categories { .flex( @grow: 1, @shrink: 1 ); } /* 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. For more see https://css-tricks.com/popping-hidden-overflow/. */ #explore-header-categories-overflow-hider { overflow: hidden; } /* Small element so that #explore-header-categories-dropdowns isn't the first non-zero-width child of #explore-header-categories-overflow-hider. That way when the viewport is too narrow for both #explore-header-categories-sibling and #explore-header-categories-dropdowns, the entirety of #explore-header-categories-dropdowns will wrap onto the next line, revealing the explore button which is at the top of it. */ #explore-header-categories-sibling { display: inline-block; width: @explore-header-categories-sibling-width; } #explore-header-categories-dropdowns { display: inline-block; margin-left: -( @explore-header-categories-sibling-width ); /* Appears pushed up 100% of its height, so the second row (header categories) is visible in the header. When the viewport gets too narrow, the element moves down a line, making its first row (the explore button) visible. */ } #explore-dropdown { margin-left: @explore-header-categories-sibling-width; } #explore-dropdown-button { .extra-header-button-padding(); } .header-category-dropdown { display: inline-block; } .header-category-dropdown-button { .extra-header-button-padding(); } -.header-suggestions { +#header-suggestions { .stack-box-shadow(); .suggestions-results { border: 0 !important; max-height: calc(30vh); overflow-y: auto; } .suggestions-special { border: 0; .stack-box-shadow(); } } /******************************************************************************* ************************************ SIDEBAR *********************************** *******************************************************************************/ #sidebar-wrapper { position: fixed; top: 0; bottom: 0; overflow-y: auto; padding-top: 1em; /* width: @SIZE-sidebar-width; */ ul { list-style-type: none; margin: 0; padding: @sidebar-ul-padding-top 0 @sidebar-ul-padding-bottom 0; a { .pretty-anchor( @font-color: currentColor ); } } } .sidebar-header { margin-left: @sidebar-header-padding; } .sidebar-section { padding-bottom: @sidebar-section-padding-bottom; } #site-navigation-sidebar-buttons-wrapper { text-align: center; height: @big-header-height; .give-children-height(); } #site-navigation-sidebar-collapsible-button { width: @site-navigation-toggle-width; } /******************************************************************************* ************************************ CONTENT *********************************** *******************************************************************************/ -/* @TODO */ - #content-wrapper { + position: absolute; background-color: @content-backgound-color; color: @content-font-color; + padding: @content-wrapper-padding; } -.standard-toolbox a { -} - -#toolbox-main-actions-toolbox-dropdowns-wrapper { - float: right; +#main-title-messages { + border-bottom: 1px solid @firstHeading-border-color; } -.toolbox-namespaces, -.toolbox-main-actions, -.toolbox-dropdown { - display: inline-block; +#refreshed-toolbox { + z-index: @z-index-refreshed-toolbox; + padding-top: @pretty-anchor-border-width; + padding-bottom: @pretty-anchor-border-width; + .flex-display(); + .flex-align-items( initial ); } -.toolbox-namespaces a, -.toolbox-main-actions a { - .pretty-anchor( @border-side: vertical ); - display: inline-block; +#toolbox-namespaces { + .flex( @grow: 1, @shrink: 1 ); } -/******************************************************************************* -************************************ FOOTER ************************************ -*******************************************************************************/ -/* @TODO */ - -#footer { - display: none; +#toolbox-main-actions, +#toolbox-dropdown { + .flex( @grow: 0, @shrink: 0 ); } -/******************************************************************************* -************************************** OLD ************************************* -*******************************************************************************/ - -.standard-toolbox-dropdown { - left: 0.5em; - z-index: 3; - background: #fff; - position: absolute; - width: 12em; - .stack-box-shadow(); - border: 1px solid #ddd; - margin-top: 0.25em; +.toolbox-section { + display: inline-block; + vertical-align: top; } -.standard-toolbox-dropdown ul { - list-style-type: none; - margin-left: 0; - margin-top: 0.25em; - margin-bottom: 0.25em; +/* so #back-to-subject collapses correctly, per +https://css-tricks.com/flexbox-truncated-text/ */ +.toolbox-namespaces { + min-width: 5em; } -.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; +#back-to-subject { + border-bottom: initial; + font-size: 1.1em; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 100%; } -.standard-toolbox-dropdown a { - width: 100%; - height: 100%; +#toolbox-namespaces a, +#toolbox-main-actions a { + .pretty-anchor( @border-side: vertical ); + margin-bottom: -( @pretty-anchor-border-width ); + padding-top: 0; 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; -} +#ca-watch, +#ca-unwatch { + display: inline; + margin: 0; + vertical-align: middle; + padding-bottom: @pretty-anchor-border-width; -.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 */ + a { + border: 0; + display: inline-block; + text-indent: -9999px; + width: 1.2em; + height: 1.2em; + vertical-align: middle; + background-position: top right; + background-size: 100% 100%; + background-repeat: no-repeat; + } } -.standard-toolbox-dropdown a:before { - position: absolute; +[dir = ltr] { + #ca-watch, + #ca-unwatch { + .loading, + :hover { + /* ooui-icon-halfStar-ltr */ + background-image: url( data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%230645ad'%3E%3Cpath d='M20 7h-7L10 .5 7 7H0l5.46 5.47-1.64 7 6.18-3.7 6.18 3.73-1.63-7zm-10 6.9V4.6l1.9 3.9h4.6l-3.73 3.4 1 4.28z'/%3E%3C/svg%3E ); + } + } } -.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 */ +[dir = rtl] { + #ca-watch, + #ca-unwatch { + .loading, + :hover { + /* ooui-icon-halfStar-rtl */ + background-image: url( data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%230645ad'%3E%3Cpath d='M5.4 12.5l-1.6 7 6.2-3.7 6.2 3.7-1.6-7L20 7h-7L10 .5 7 7H0l5.4 5.5zm.8 3.7l1-4.3-3.7-3.4h4.6L10 4.6v9.3l-3.8 2.3z'/%3E%3C/svg%3E ); + } + } } -.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: @big-header-height; /* 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 */ +#ca-unwatch a { + /* ooui-icon-unStar */ + background-image: url( data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%230645ad'%3E%3Cpath d='M20 7h-7L10 .5 7 7H0l5.46 5.47-1.64 7 6.18-3.7 6.18 3.73-1.63-7z'/%3E%3C/svg%3E%0A ); } -#main-title-messages { - border-bottom: 1px solid #ddd; +#ca-watch a { + /* ooui-icon-star */ + background-image: url( data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%230645ad'%3E%3Cpath d='M20 7h-7L10 .5 7 7H0l5.46 5.47-1.64 7 6.18-3.7 6.18 3.73-1.63-7zm-10 6.9l-3.76 2.27 1-4.28L3.5 8.5h4.61L10 4.6l1.9 3.9h4.6l-3.73 3.4 1 4.28z'/%3E%3C/svg%3E%0A ); } -#back-to-subject { +/******************************************************************************* +************************************ FOOTER ************************************ +*******************************************************************************/ + +/* @TODO */ + +#footer { display: none; - font-size: 1.25em; - padding-bottom: 0.25em; } +/******************************************************************************* +************************************** OLD ************************************* +*******************************************************************************/ + #small-toolbox-wrapper { display: none; } #site-notice { width: 100%; text-align: center; } #new-talk { margin-left: 2em; } -#content-wrapper { - position: absolute; -} - -#content { - background-color: #fff; - font-family: sans-serif; - border-bottom: 0.25em solid #eee; - padding-bottom: 1em; -} - #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; -} - -#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; padding: 0; margin-bottom: 0; border: 0; + display: inline; } .mw-indicators { float: right; } .mw-indicator { display: inline; } -.scroll-shadow { - background: none; -} - -.toolbox-container { - display: inline-block; - position: relative; -} - -.standard-toolbox { - //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; -} - -/* @TODO reimplement page tools and remove these rules */ -.refreshed-menu-collapsible { - -ms-transition: max-height 0.4s ease; - transition: max-height 0.4s ease; - visibility: visible; - overflow: hidden; - height: auto; -} - -/* @TODO reimplement page tools and remove these rules */ -.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; -} - -.toolbox-link { - cursor: pointer; /* doesn't have href so must be explicitly defined */ -} - -.standard-toolbox a { - 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; -} - /********** Footer **********/ #footer { position: relative; top: 3.5em; text-align: center; padding-bottom: 15px; color: #ccc; line-height: 1.5em; } #footer a { color: #fff; } #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; } /* dropdowns */ #site-navigation-header-dropdown { position: relative; width: 100%; /* so tray can fit width of sidebar area rather than default width */ } .site-navigation-button { width: 2em; } #site-navigation-header-dropdown-tray { width: 100%; /* ensure tray is as wide as #site-navigation-header */ } .mw-echo-ui-overlay { - z-index: @z-index-header !important; /* same as #header-wrapper and .header-suggestions */ + z-index: @z-index-header !important; /* same as #header-wrapper and #header-suggestions */ } diff --git a/refreshed/styles/screen/medium.less b/refreshed/styles/screen/medium.less index ff1e334..dc77715 100644 --- a/refreshed/styles/screen/medium.less +++ b/refreshed/styles/screen/medium.less @@ -1,86 +1,85 @@ @import 'variables.less'; +/******************************************************************************* +**************************** DROPDOWNS/COLLAPSIBLES **************************** +*******************************************************************************/ + +/* Make right: 0; stick dropdown trays to the right edge of the dropdown, +not the viewport */ +#toolbox-dropdown { + position: relative; +} + /******************************************************************************* ************************************ HEADER ************************************ *******************************************************************************/ .header-height( @medium-header-height ); .header-button-textless, .header-button-textless-medium { width: @medium-header-height; padding: 0; } .refreshed-dropdown-tray { width: @medium-dropdown-tray-width; } -.single-wiki .refreshed-logo-current { - min-width: @big-sidebar-width; -} - .site-navigation-logo-img { max-width: @medium-site-navigation-logo-img-max-width; } #site-navigation-header { .site-navigation-icon-logos { display: none; } } #explore-header-categories-dropdowns { margin-top: -( @medium-header-height ); } -#sidebar-toggler { - display: none; -} - .dropdown-searchInput( @medium-header-search-width ); -.header-suggestions { +#header-suggestions { right: 0 !important; } #user-info-dropdown-button { .extra-header-button-padding(); } -#user-info-dropdown-tray { - right: @medium-content-margin-right; -} - /******************************************************************************* ************************************ SIDEBAR *********************************** *******************************************************************************/ -#sidebar-wrapper { - width: @medium-sidebar-width; -} + +.use-sidebar-menu( @medium-sidebar-width, @sidebar-menu-background ); #site-navigation-sidebar-header-categories-sidebar-wrapper { display: none; } /******************************************************************************* ************************************ CONTENT *********************************** *******************************************************************************/ /* @TODO */ -#content-wrapper { - left: @medium-sidebar-width; - right: 0; +.use-refreshed-toolbox-sticky( @medium-header-height ); + +/* toolbox */ +a#back-to-subject { /* a for specificity */ + display: none; } /******************************************************************************* ************************************ FOOTER ************************************ *******************************************************************************/ /* @TODO */ /******************************************************************************* ************************************** OLD ************************************* *******************************************************************************/ diff --git a/refreshed/styles/screen/small.less b/refreshed/styles/screen/small.less index cd0a737..67e6479 100644 --- a/refreshed/styles/screen/small.less +++ b/refreshed/styles/screen/small.less @@ -1,105 +1,125 @@ @import 'variables.less'; +/******************************************************************************* +**************************** DROPDOWNS/COLLAPSIBLES **************************** +*******************************************************************************/ + +.refreshed-dropdown-tray { + left: 0; + right: 0; + font-size: @small-dropdown-tray-sidebar-font-size; +} + /******************************************************************************* ************************************ HEADER ************************************ *******************************************************************************/ body { overflow-x: hidden; } .header-height( @small-header-height ); .header-button-textless, .header-button-textless-small { width: @small-header-height; padding: 0; } -.refreshed-dropdown-tray { - left: 0; - right: 0; -} - .site-navigation-logo-img { max-width: @small-site-navigation-logo-img-max-width; } #site-navigation-header { .site-navigation-full-logos { display: none; } } #explore-header-categories { display: none; } /* in case they are re-enabled using Refreshed.css */ #explore-header-categories-dropdowns { margin-top: -( @small-header-height ); } #searchInput { width: 100%; } .dropdown-searchInput( @small-header-search-width ); -.header-suggestions { +#header-suggestions { left: 0 !important; right: 0 !important; } #user-info-dropdown-button { .refreshed-username, .refreshed-icon-refreshed-dropdown-expand { display: none; } } -#user-info-dropdown-tray { - right: @small-content-margin-right; -} - /******************************************************************************* ************************************ SIDEBAR *********************************** *******************************************************************************/ -#sidebar-wrapper { - width: @small-sidebar-width; - left: -( @small-sidebar-width ); - background: @small-sidebar-background; - z-index: @small-z-index-sidebar; - .transition( left 0.2s ease ); -} +.use-sidebar-menu( @small-sidebar-width, @sidebar-menu-background ); -#sidebar-toggler-checkbox:checked ~ #sidebar-wrapper { - left: 0; - /* only apply the box shadow when the sidebar is opened so the shadow doesn't - appear on the edge of the screen when the sidebar is closed (just off the edge - of the screen) */ - box-shadow: 0 3px 9px 0 rgba(75,75,75,0.4); +#sidebar-wrapper { + font-size: @small-dropdown-tray-sidebar-font-size; } /******************************************************************************* ************************************ CONTENT *********************************** *******************************************************************************/ /* @TODO */ -#content-wrapper { - left: 0; - right: 0; +#firstHeading, +#main-title-messages { + text-align: center; +} + +#main-title-messages, +#refreshed-toolbox { + font-size: 1.25em; +} + +/* toolbox */ + +#refreshed-toolbox { + overflow-x: auto; +} + +#toolbox-namespaces .ca-subject { /* #toolbox-namespaces for specificity */ + display: none; +} + +.selected#ca-talk { + display: none; +} + + +.inline-tool-text { + display: none; +} + +.toolbox-main-actions > a, +.combined-tools-dropdown { + margin-left: 0.75em; } /******************************************************************************* ************************************ FOOTER ************************************ *******************************************************************************/ /* @TODO */ /******************************************************************************* ************************************** OLD ************************************* *******************************************************************************/ diff --git a/refreshed/styles/screen/variables.less b/refreshed/styles/screen/variables.less index 1c7a709..94088c0 100644 --- a/refreshed/styles/screen/variables.less +++ b/refreshed/styles/screen/variables.less @@ -1,289 +1,370 @@ @import 'mediawiki.mixins'; -@z-index-header: 100; /* see https://www.mediawiki.org/wiki/Manual:Coding_conventions/CSS#z-index */ +/* see https://www.mediawiki.org/wiki/Manual:Coding_conventions/CSS#z-index; +we assign the header 101 not 100 so the toolbox can be beneath the header +and still be above content */ +@z-index-header: 101; +/* if there are any elements in #bodyContent that have their own stacking +context, they will appear higher than #refreshed-toolbox since they appear +later in the DOM. To prevent this, we give #refreshed-toolbox a positive +z-index. Specifically, we assume the header has a high enough z-index to sit +above everything in #bodyContent, and we set up a guaranteed index right below +the header that's still above the content, so we use that. */ +@z-index-refreshed-toolbox: @z-index-header - 1; +/* When the toolbox is stuck to the top and the dropdown is open, the toolbox +should be placed higher than the header. Otherwise the close overlay for the +dropdown appears behind the header. */ +@z-index-refreshed-toolbox-stuck-when-dropdown-open: @z-index-header + 1; +/* close overlays should appear above the header */ @z-index-close-overlay: @z-index-header + 1; -/* More than @z-index-dropdown close-overlay so dropdowns appears above the close overlay. -Less than @z-index-dropdown-triangle to ensure the triangle appears above the -dropdown and thus is not underneath its drop shadow */ +/* More than @z-index-dropdown close-overlay so dropdowns appears above the +close overlay. Less than @z-index-dropdown-triangle to ensure the triangle +appears above the dropdown and thus is not underneath its drop shadow */ @z-index-dropdown-tray: @z-index-close-overlay + 1; /* 1 more than z-index of .refreshed-dropdown-tray to ensure the triangle appears above the dropdown and thus is not underneath its drop shadow */ @z-index-dropdown-triangle: @z-index-dropdown-tray + 1; -@small-z-index-sidebar: @z-index-header + 1; /* so appears above all header stuff */ +/* so it appears above all header stuff and all close overlays */ +@z-index-sidebar-menu: @z-index-close-overlay + 1; @heading-font-stack: sans-serif; /* sidebar, header, content headings */ -@content-font-stack: sans-serif; /* dropdowns */ +@content-font-stack: 'Helvetica Neue', Helvetica, Arial, sans-serif; /* dropdowns */ /* content colors/backgrounds */ @body-background: #194a8d; @body-font-color: #fff; @header-background: linear-gradient( to left, #040f28 0, #103ca2 50%, #040f28 100% ); @button-hover-background: midnightblue; @button-selected-background: @button-hover-background; -@small-sidebar-background: @body-background; +@sidebar-menu-background: @body-background; @content-backgound-color: #fff; -@content-font-color: #000; +@content-font-color: #252525; + +@refreshed-icon-height: 1em; +@dropdown-tray-refreshed-icon-margin-left: 0.4em; +@pretty-anchor-border-width: 0.3em; @close-overlay-shadow-color: rgba( 0, 0, 0, 0.2 ); @content-shadow-color: rgba( 75, 75, 75, 0.4 ); @dropdown-tray-background-color: @content-backgound-color; @dropdown-tray-hover-background-color: #eee; @dropdown-tray-font-color: @content-font-color; +@small-dropdown-tray-sidebar-font-size: 1.2em; @content-dropdown-button-background: #eee; @content-dropdown-button-hover-background: #ddd; +@content-dropdown-button-font-color: inherit; @header-category-collapsible-padding: 0.25em 0.25em 0.25em 0; @search-placeholder-text-color: #808080; @search-suggestions-border-color: #ddd; +@firstHeading-border-color: #ddd; + /* height of header */ @big-header-height: 3em; @medium-header-height: 3em; @small-header-height: 2.5em; -/* width of sidebar */ -@big-sidebar-width: 12em; -@medium-sidebar-width: 10em; -@small-sidebar-width: 10em; - /* width of site navigation header section (wiki homepage link) */ @site-navigation-toggle-width: 2em; /* this is also used for the sidebar */ @big-site-navigation-logo-img-max-width: @big-sidebar-width - @site-navigation-toggle-width; @medium-site-navigation-logo-img-max-width: @big-site-navigation-logo-img-max-width; @small-site-navigation-logo-img-max-width: @big-site-navigation-logo-img-max-width; /* width for dropdown trays */ @big-dropdown-tray-width: @big-sidebar-width; @medium-dropdown-tray-width: @big-dropdown-tray-width; @dropdown-tray-margin-top: -3px; @dropdown-tray-vertical-padding: 0.5em; /* dimensions of avatar (SocialProfile or default icons) */ @avatar-width: 30px; @avatar-height: auto; /* width of placeholder that ensures proper explore button behavior on resize */ @explore-header-categories-sibling-width: 1px; /* header search box */ @header-search-font-size: 1em; @header-search-height: 2em; @header-search-padding: 0.5em; @big-header-search-width: 15em; @medium-header-search-width: 20em; @small-header-search-width: 100%; /* sidebar */ @big-sidebar-width: 12em; @medium-sidebar-width: @big-sidebar-width; @small-sidebar-width: 75%; @sidebar-header-padding: 0.25em; @sidebar-section-padding-bottom: 0.25em; @sidebar-ul-padding-top: 0.25em; @sidebar-ul-padding-bottom: 0.5em; -/* content position */ -@big-content-margin-right: 1em; -@medium-content-margin-right: @big-content-margin-right; -@small-content-margin-right: 0; +/* content position, padding */ +@big-content-right: 1em; +@content-wrapper-padding: 1em; + +/* toolbox */ +@refreshed-toolbox-height: 2em; /** mixins **/ .font-color( @color ) { color: @color; fill: @color; } .stack-box-shadow() { .box-shadow( 0 3px 9px 0 @content-shadow-color ); } .header-height( @height ) { #header-wrapper { height: @height; } - #content-wrapper { + #content-wrapper, + .refreshed-toolbox-stuck { top: @height; } #sidebar { margin-top: @height; } .resize-header-icons( @height / 2 ); } /* explicitly inherit height so the text/images/svg inside have access to the element's full height, and make sure they're all vertically aligned the same way (especially labels)--for example this ensures all .refreshed-dropdown-triangle elements are set to the same height */ .give-children-height() { div, nav, a, label { height: 100%; vertical-align: top; } } /* the dropdown/sidebar links where a side border appears on hover */ .pretty-anchor( @active: false, @font-color: auto, @active-background-color: initial, @border-side: horizontal ) { fill: currentColor; & when not ( @font-color = auto ) { .font-color( @font-color ); } display: block; padding-top: 0.2em; padding-bottom: 0.2em; padding-left: 1em; padding-right: 1em; - border-left: 0.3em solid transparent; - border-right: 0.3em solid transparent; /* border-right for symmetry */ + border-left: @pretty-anchor-border-width solid transparent; + border-right: @pretty-anchor-border-width solid transparent; /* border-right for symmetry */ & when ( @border-side = vertical ) { border-left: 0; border-right: 0; - border-top: 0.3em solid transparent; - border-bottom: 0.3em solid transparent; /* border-bottom for symmetry */ + border-top: @pretty-anchor-border-width solid transparent; + border-bottom: @pretty-anchor-border-width solid transparent; /* border-bottom for symmetry */ padding-left: 0.1em; padding-right: 0.1em; - margin-right: 0.3em; + margin-right: @pretty-anchor-border-width; } .box-sizing( border-box ); /* padding/border should be included within the 100% width */ text-decoration: none; &:hover, &:focus, &:active, &.selected { /* use currentColor not @font-color because, for example, if @font-color = auto, then setting border-color to @font-color would set border-color to auto... that is, border-color would be the auto value for border-color, not the auto value for color */ border-left-color: currentColor; & when ( @border-side = vertical ) { border-left-color: transparent; border-bottom-color: currentColor; } } &:focus, &:active, &.selected { background-color: @active-background-color; } & when ( @active = true ) { border-left-color: currentColor; & when ( @border-side = vertical ) { border-left-color: transparent; border-bottom-color: currentColor; } background-color: @active-background-color; } svg { vertical-align: middle; } } /* extra padding for some header buttons with text */ .extra-header-button-padding() { padding-left: 0.5em; padding-right: 0.5em; } /* style the default text for the search bar */ .searchInput-placeholder() { color: @search-placeholder-text-color; font-weight: 700; } /* style the search bar when it's inside a dropdown (medium, small) */ .dropdown-searchInput( @header-search-width ) { + #user-info-dropdown-tray { + right: 0; + } + #header-search-dropdown-tray { padding-left: @header-search-padding; padding-right: @header-search-padding; width: @header-search-width; right: 0; } #searchInput { /* header search box */ outline: 0; border-bottom: 3px solid transparent; /* padding-right is 0.5em more than padding-right of .searchButton to leave room for it */ padding-left: @header-search-padding / 2; padding-right: @header-search-height + @header-search-padding; width: 100%; } #searchInput:focus { border-color: currentColor; } .searchButton { /* take up full height and stick to right side of #searchInput */ top: 50%; transform: translateY( -50% ); right: @header-search-padding; } - [dir="ltr"] .searchButton { + [dir = ltr] .searchButton { /* ooui-icon-arrowNext-ltr */ - background-image: url( data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M10%202L8.59%203.42%2014.17%209H2v2h12.17l-5.58%205.59L10%2018l8-8-8-8z%22%2F%3E%3C%2Fsvg%3E%0A ); + background-image: url( data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M10 2L8.59 3.42 14.17 9H2v2h12.17l-5.58 5.59L10 18l8-8-8-8z'/%3E%3C/svg%3E ); } - [dir="rtl"] .searchButton { + [dir = rtl] .searchButton { /* ooui-icon-arrowNext-rtl */ - background-image: url( data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%3E%3Cpath%20d%3D%22M2%2010l8%208%201.4-1.4L5.8%2011H18V9H5.8l5.6-5.6L10%202z%22%2F%3E%3C%2Fsvg%3E ); + background-image: url( data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath d='M2 10l8 8 1.4-1.4L5.8 11H18V9H5.8l5.6-5.6L10 2z'/%3E%3C/svg%3E ); } - .header-suggestions { + #header-suggestions { margin-top: @header-search-padding !important; width: @header-search-width !important; + .suggestions-result, + .suggestions-special { + padding: 0.75em 0.5em !important; + } + .suggestions-result { - padding: @header-search-padding (@header-search-padding / 2) !important; border-bottom: 1px solid @search-suggestions-border-color; } .suggestions-special { - padding: @header-search-padding (@header-search-padding / 2) !important; margin-top: 0 !important; } } } /* adjust size of header icons */ .resize-header-icons( @width ) { .refreshed-icon-refreshed-menu, .refreshed-icon-search, .refreshed-icon-refreshed-explore { width: @width; height: auto; } } +/* sidebar as a menu */ +.use-sidebar-menu( @sidebar-width, @sidebar-background ) { + #sidebar-wrapper { + width: @sidebar-width; + left: -( @sidebar-width ); + background: @sidebar-background; + z-index: @z-index-sidebar-menu; + } + + #sidebar-toggler-checkbox:checked ~ #sidebar-wrapper { + left: 0; + /* only apply the box shadow when the sidebar is opened so the shadow doesn't + appear on the edge of the screen when the sidebar is closed (just off the edge + of the screen) */ + .stack-box-shadow(); + } + + .content-wrapper-width( 0, 0 ); +} + +.content-wrapper-width( @left, @right ) { + #content-wrapper, + .refreshed-toolbox-stuck { + left: @left; + right: @right; + } + + #user-info-search-wrapper { + margin-right: @right; + } +} + +.use-refreshed-toolbox-sticky( @header-height ) { + #refreshed-toolbox { + .position-sticky(); + top: @header-height; + } + + .refreshed-toolbox-stuck#refreshed-toolbox { + background-color: @content-backgound-color; + padding-left: @content-wrapper-padding; + padding-right: @content-wrapper-padding; + margin: 0 -( @content-wrapper-padding ); + .stack-box-shadow(); + } +} + +.position-sticky() { + position: -webkit-sticky; + position: sticky; +} + /* additional flex mixins */ .flex-direction( @value ) { -webkit-flex-direction: @value; // Chrome 21.0-, Safari 6.1- -moz-flex-direction: @value; // Firefox 18.0- flex-direction: @value; } .flex-justify-content( @value ) { -webkit-justify-content: @value; // Chrome 21.0-, Safari 6.1- -moz-justify-content: @value; // Firefox 18.0- justify-content: @value; } .flex-align-items( @value ) { -webkit-align-items: @value; // Safari 7.0- align-items: @value; } diff --git a/skin.json b/skin.json index c03d9c2..9723150 100644 --- a/skin.json +++ b/skin.json @@ -1,99 +1,99 @@ { "name": "Refreshed", "version": "4.0.0 alpha", "author": [ "Adam Carter", "Drew1200", "George Barnick", "Jack Phoenix", "Lewis Cawte", "MacFan4000", "MtMNC", "Samantha Nguyen", "Seaside98", "ShermanTheMythran", "SirComputer" ], "url": "https://www.mediawiki.org/wiki/Skin:Refreshed", "descriptionmsg": "refreshed-desc", "license-name": "GPL-2.0-or-later", "type": "skin", "ValidSkinNames": { "refreshed": "Refreshed" }, "MessagesDirs": { "SkinRefreshed": [ "i18n" ] }, "AutoloadClasses": { "SkinRefreshed": "includes/SkinRefreshed.php", "RefreshedTemplate": "includes/RefreshedTemplate.php" }, "Hooks": { "PageContentSaveComplete": "SkinRefreshed::onPageContentSaveComplete" }, "ResourceFileModulePaths": { "localBasePath": "", "remoteSkinPath": "Refreshed" }, "ResourceModules": { "skins.refreshed": { "styles": { "refreshed/wikifont/wikiglyphs.css": { "media": "screen" }, "refreshed/styles/screen/variables.less": { "media": "screen" }, "refreshed/styles/screen/common.less": { "media": "screen" }, "refreshed/styles/screen/small.less": { - "media": "(max-width: 600px)" + "media": "(max-width: 719px)" }, "refreshed/styles/screen/medium.less": { - "media": "(min-width: 601px) and (max-width: 1000px)" + "media": "(min-width: 720px) and (max-width: 1000px)" }, "refreshed/styles/screen/big.less": { "media": "(min-width: 1001px)" }, "refreshed/styles/print/print.css": { "media": "print" } } }, "skins.refreshed.js": { "scripts": [ "refreshed/scripts/refreshed.js" ], "dependencies": [ "mediawiki.api", "mediawiki.util" ] } }, "ResourceModuleSkinStyles": { "refreshed": { "ext.echo.styles.alert": "refreshed/extensions/Echo/echo.alert.less", "ext.echo.styles.badge": "refreshed/extensions/Echo/echo.badge.less", "ext.echo.styles.notifications": "refreshed/extensions/Echo/echo.notifications.less", "ext.echo.ui.desktop": "refreshed/extensions/Echo/echo.ui.NotificationBadgeWidget.less", "ext.echo.ui": [ "refreshed/extensions/Echo/echo.ui.overlay.less", "refreshed/extensions/Echo/echo.ui.less" ], "+mediawiki.action.history.styles": "refreshed/mediawiki/action.history.styles.css", "+mediawiki.action.view.filepage": "refreshed/mediawiki/action.view.filepage.css", "+mediawiki.action.view.postEdit": "refreshed/mediawiki/action.view.postEdit.css", "+mediawiki.diff.styles": "refreshed/mediawiki/diff.styles.css", "+mediawiki.feedlink": "refreshed/mediawiki/feedlink.css", "+mediawiki.skinning.elements": "refreshed/mediawiki/skinning.elements.css", "+mediawiki.skinning.interface": "refreshed/mediawiki/skinning.interface.css", "+mediawiki.special.changeslist.enhanced": "refreshed/mediawiki/special.changeslist.enhanced.css", "+mediawiki.special.preferences.styles": "refreshed/mediawiki/special.preferences.styles.css", "+mediawiki.special.userlogin.common.styles": "refreshed/mediawiki/special.userlogin.common.css" } }, "manifest_version": 1 }