diff --git a/.jshintignore b/.jshintignore index 82eaa05..1395b35 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,2 +1,3 @@ node_modules/** vendor/** +resources/libraries/** diff --git a/Timeless.skin.php b/Timeless.skin.php index aa595a6..a35d32c 100644 --- a/Timeless.skin.php +++ b/Timeless.skin.php @@ -9,19 +9,30 @@ class SkinTimeless extends SkinTemplate { $template = 'TimelessTemplate', $useHeadElement = true; /** - * Add CSS via ResourceLoader - * * @param $out OutputPage */ - function setupSkinUserCss( OutputPage $out ) { - parent::setupSkinUserCss( $out ); + public function initPage( OutputPage $out ) { + parent::initPage( $out ); - $out->addMeta( 'viewport', 'width=device-width, initial-scale=1.0' ); + $out->addMeta( 'viewport', 'width=device-width, initial-scale=1, maximum-scale=1' ); $out->addModuleStyles( array( 'mediawiki.skinning.content.externallinks', - 'skins.timeless' + 'skins.timeless', + 'skins.timeless.misc' ) ); - $out->addModules( array( 'skins.timeless.js' ) ); + $out->addModules( array( + 'skins.timeless.js', + 'skins.timeless.mobile' + ) ); + } + + /** + * Add CSS via ResourceLoader + * + * @param $out OutputPage + */ + function setupSkinUserCss( OutputPage $out ) { + parent::setupSkinUserCss( $out ); } } diff --git a/TimelessTemplate.php b/TimelessTemplate.php index a2c19af..099073a 100644 --- a/TimelessTemplate.php +++ b/TimelessTemplate.php @@ -23,56 +23,66 @@ class TimelessTemplate extends BaseTemplate { $this->outputLogo( 'p-logo-text', 'text' ); $this->outputSearch(); ?> -
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
-

getMsg( 'navigation-heading' )->parse() ?>

outputLogo( 'p-logo', 'image' ); - echo ''; - $this->outputPortlet( array( + $this->outputSiteNavigation(); + + $siteTools = $this->assemblePortlet( array( 'id' => 'p-sitetools', 'headerMessage' => 'timeless-sitetools', - 'content' => $pileOfTools['general'], - 'class' => 'sidebar-chunk' + 'content' => $pileOfTools['general'] ) ); + $this->outputSidebarChunk( 'site-tools', 'timeless-sitetools', $siteTools ); ?>
+ data['catlinks'] ) { + $this->html( 'catlinks' ); + } + $this->html( 'dataAfterContent' ); + ?>
@@ -186,9 +200,9 @@ class TimelessTemplate extends BaseTemplate { } /** - * Outputs a single sidebar portlet of any kind. + * Returns a single sidebar portlet of any kind (monobook style) */ - private function outputPortlet( $box ) { + private function assemblePortlet( $box ) { if ( !$box['content'] ) { return; } @@ -198,35 +212,85 @@ class TimelessTemplate extends BaseTemplate { $box['class'] .= ' mw-portlet'; } - ?> - - '; + echo '

' . $this->getMsg( $headerMessage )->escaped() . '

'; + echo ''; } /** @@ -305,24 +369,30 @@ class TimelessTemplate extends BaseTemplate { */ private function outputSiteNavigation() { $sidebar = $this->getSidebar(); + $content = ''; $sidebar['SEARCH'] = false; // Already hardcoded into header $sidebar['TOOLBOX'] = false; // Parsed as part of pageTools - $sidebar['LANGUAGES'] = false; // PUT THIS ON THE OTHER SIDE + $sidebar['LANGUAGES'] = false; // Forcibly removed to separate chunk foreach ( $sidebar as $boxName => $box ) { if ( $boxName === false ) { continue; } - $this->outputPortlet( $box, true ); + $content .= $this->assemblePortlet( $box, true ); } + + $this->outputSidebarChunk( 'site-navigation', 'navigation', $content ); } + /** + * Outputs user links portlet for header + */ private function outputUserLinks() { $user = $this->getSkin()->getUser(); ?> - getSkin()->getTitle(); @@ -399,6 +482,18 @@ class TimelessTemplate extends BaseTemplate { 'id' => 't-pagelog' ); } + $pileOfTools['more'] = array( + 'text' => $this->getMsg( 'timeless-more' )->escaped(), + 'id' => 'ca-more', + 'class' => 'dropdown-toggle' + ); + if ( $this->data['language_urls'] ) { + $pileOfTools['languages'] = array( + 'text' => $this->getMsg( 'timeless-languages' )->escaped(), + 'id' => 'ca-languages', + 'class' => 'dropdown-toggle' + ); + } /* This is really dumb, but there is no sane way to do this. */ foreach ( $pileOfTools as $navKey => $navBlock ) { @@ -406,7 +501,7 @@ class TimelessTemplate extends BaseTemplate { if ( in_array( $navKey, array( 'watch', 'unwatch' ) ) ) { $currentSet = 'namespaces'; - } elseif ( in_array( $navKey, array( 'edit', 'view', 'history', 'contributions', 'addsection' ) ) ) { + } elseif ( in_array( $navKey, array( 'edit', 'view', 'history', 'contributions', 'addsection', 'more', 'languages' ) ) ) { $currentSet = 'page-primary'; } elseif ( in_array( $navKey, array( 'delete', 'rename', 'protect', 'unprotect', 'viewsource', 'move' ) ) ) { $currentSet = 'page-secondary'; @@ -486,23 +581,27 @@ class TimelessTemplate extends BaseTemplate { /* Assemble the html because why not... */ if ( $count ) { - $catList = ''; } } if ( $catList ) { - echo $catList; + $this->outputSidebarChunk( 'catlinks-sidebar', $catHeader, $catList ); } } private function assembleCatList( $list, $id, $message ) { - $catList = '

' . $this->getMsg( $message )->escaped() . '

'; - $catList .= ''; return $catList; } + /* + * Output interlanguage links block + */ private function outputInterlanguageLinks() { if ( $this->data['language_urls'] ) { - $msgObj = $this->getMsg( 'otherlanguages' ); - $this->outputPortlet( array( - 'id' => 'p-lang', - 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages', - 'generated' => false, - 'content' => $this->data['language_urls'], - 'class' => 'sidebar-chunk' - ) ); + $msgObj = $this->getMsg( 'otherlanguages' )->escaped(); + $content = $this->assemblePortlet( array( + 'id' => 'p-lang', + 'header' => $msgObj, + 'generated' => false, + 'content' => $this->data['language_urls'] + ) ); + + $this->outputSidebarChunk( 'other-languages', 'timeless-languages', $content ); } } } diff --git a/i18n/en.json b/i18n/en.json index a05e9c2..770b61e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3,19 +3,23 @@ "authors": [ "Isarra" ] }, "skinname-timeless": "Timeless", - "timeless-desc": "A timeless skin designed after the Winter prototype by Brandon Harris.", + "timeless-desc": "A timeless skin designed after the Winter prototype by Brandon Harris, and various WMFy styles.", "timeless-search-placeholder": "Search approximately {{NUMBEROFPAGES}} pages", "timeless-loggedin": "Your account", "timeless-anonymous": "Anonymous", + "timeless-loggedinas": "Logged in as $1", + "timeless-notloggedin": "Not logged in", "timeless-userpage": "User page", "timeless-talkpage": "User talk", "timeless-pagelog": "Page logs", + "timeless-more": "More", "timeless-sitetools": "Wiki tools", "timeless-pageactions": "Page tools", "timeless-userpagetools": "User tools", "timeless-pagemisc": "More", "timeless-namespaces": "Namespaces", "timeless-pagetools": "Page actions", + "timeless-languages": "Languages", "timeless-sitetitle": "{{MediaWiki:Sitetitle}}" } diff --git a/i18n/qqq.json b/i18n/qqq.json index db69086..65a0af4 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -10,14 +10,18 @@ "timeless-search-placeholder": "Main search input placeholder text", "timeless-loggedin": "Label for logged in users' user menu (if their username is too long to fit)", "timeless-anonymous": "Label for anonymous (not logged in) user menu\n{{Identical|Anonymous}}", + "timeless-loggedinas": "Note for personal menu for logged in users noting their current username\n\nParameters:\n* $1 - username", + "timeless-notloggedin": "Note for personal menu for anonymous users clarifying that they are not logged in", "timeless-userpage": "User page/profile link label in the personal menu\n{{Identical|User page}}", "timeless-talkpage": "Talk page link label in the personal menu", "timeless-pagelog": "Label for special:log link for the page", + "timeless-more": "Label for the more tools dropdown menu in the page actions toolbar", "timeless-sitetools": "Label for general site tools menu in sidebar", "timeless-pageactions": "Label for some page actions", "timeless-userpagetools": "Label for actions associated with a userpage", "timeless-pagemisc": "Label for more tools\n{{Identical|More}}", "timeless-namespaces": "Label for page namespaces and watch tools\n{{Identical|Namespace}}", "timeless-pagetools": "Label for other page tools", + "timeless-languages": "Label for the other languages dropdown", "timeless-sitetitle": "{{ignore}}\nSite title for site banner" } diff --git a/resources/images/arrow-down-white.png b/resources/images/arrow-down-white.png deleted file mode 100644 index 5953d0e..0000000 Binary files a/resources/images/arrow-down-white.png and /dev/null differ diff --git a/resources/images/arrow-down-white.svg b/resources/images/arrow-down-white.svg deleted file mode 100644 index 39a3daf..0000000 --- a/resources/images/arrow-down-white.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/resources/images/cat-grey.png b/resources/images/cat-grey.png new file mode 100644 index 0000000..0179759 Binary files /dev/null and b/resources/images/cat-grey.png differ diff --git a/resources/images/cat-grey.svg b/resources/images/cat-grey.svg new file mode 100644 index 0000000..43f42d4 --- /dev/null +++ b/resources/images/cat-grey.svg @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/clock-grey.png b/resources/images/clock-grey.png new file mode 100644 index 0000000..1e5ef28 Binary files /dev/null and b/resources/images/clock-grey.png differ diff --git a/resources/images/clock-grey.svg b/resources/images/clock-grey.svg new file mode 100644 index 0000000..9793049 --- /dev/null +++ b/resources/images/clock-grey.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/images/editing icons.svg b/resources/images/editing icons.svg new file mode 100644 index 0000000..1007981 --- /dev/null +++ b/resources/images/editing icons.svg @@ -0,0 +1,220 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + diff --git a/resources/images/gear-grey.png b/resources/images/gear-grey.png new file mode 100644 index 0000000..3127f67 Binary files /dev/null and b/resources/images/gear-grey.png differ diff --git a/resources/images/gear-grey.svg b/resources/images/gear-grey.svg new file mode 100644 index 0000000..46644ae --- /dev/null +++ b/resources/images/gear-grey.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/resources/images/gear-large-grey.png b/resources/images/gear-large-grey.png new file mode 100644 index 0000000..0ffcf78 Binary files /dev/null and b/resources/images/gear-large-grey.png differ diff --git a/resources/images/gear-large-grey.svg b/resources/images/gear-large-grey.svg new file mode 100644 index 0000000..e3a1eaf --- /dev/null +++ b/resources/images/gear-large-grey.svg @@ -0,0 +1,76 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/images/languages-grey.png b/resources/images/languages-grey.png new file mode 100644 index 0000000..3c02c75 Binary files /dev/null and b/resources/images/languages-grey.png differ diff --git a/resources/images/languages-grey.svg b/resources/images/languages-grey.svg new file mode 100644 index 0000000..38cfd12 --- /dev/null +++ b/resources/images/languages-grey.svg @@ -0,0 +1,39 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/images/menu-large-grey.png b/resources/images/menu-large-grey.png new file mode 100644 index 0000000..91e3353 Binary files /dev/null and b/resources/images/menu-large-grey.png differ diff --git a/resources/images/menu-large-grey.svg b/resources/images/menu-large-grey.svg new file mode 100644 index 0000000..ae4fa39 --- /dev/null +++ b/resources/images/menu-large-grey.svg @@ -0,0 +1,95 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/resources/images/page-grey.png b/resources/images/page-grey.png new file mode 100644 index 0000000..b9895af Binary files /dev/null and b/resources/images/page-grey.png differ diff --git a/resources/images/page-grey.svg b/resources/images/page-grey.svg new file mode 100644 index 0000000..d720150 --- /dev/null +++ b/resources/images/page-grey.svg @@ -0,0 +1,35 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/resources/images/pencil-grey.png b/resources/images/pencil-grey.png new file mode 100644 index 0000000..a9b58a5 Binary files /dev/null and b/resources/images/pencil-grey.png differ diff --git a/resources/images/pencil-grey.svg b/resources/images/pencil-grey.svg new file mode 100644 index 0000000..2e2eea7 --- /dev/null +++ b/resources/images/pencil-grey.svg @@ -0,0 +1,39 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/images/plus-grey.png b/resources/images/plus-grey.png new file mode 100644 index 0000000..99fe7f3 Binary files /dev/null and b/resources/images/plus-grey.png differ diff --git a/resources/images/plus-grey.svg b/resources/images/plus-grey.svg new file mode 100644 index 0000000..f586712 --- /dev/null +++ b/resources/images/plus-grey.svg @@ -0,0 +1,48 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/resources/images/puzzle-grey.png b/resources/images/puzzle-grey.png new file mode 100644 index 0000000..707d3f1 Binary files /dev/null and b/resources/images/puzzle-grey.png differ diff --git a/resources/images/puzzle-grey.svg b/resources/images/puzzle-grey.svg new file mode 100644 index 0000000..d44e80f --- /dev/null +++ b/resources/images/puzzle-grey.svg @@ -0,0 +1,53 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/resources/images/star-filled.png b/resources/images/star-filled.png index b1b2318..b32f93c 100644 Binary files a/resources/images/star-filled.png and b/resources/images/star-filled.png differ diff --git a/resources/images/star.png b/resources/images/star.png index d27efa9..99339ce 100644 Binary files a/resources/images/star.png and b/resources/images/star.png differ diff --git a/resources/images/star.svg b/resources/images/star.svg index c8f519e..fa9642b 100644 --- a/resources/images/star.svg +++ b/resources/images/star.svg @@ -1,6 +1,60 @@ - - - - + + + + + image/svg+xml + + + + + + + + + diff --git a/resources/images/talk-grey.png b/resources/images/talk-grey.png new file mode 100644 index 0000000..c11e310 Binary files /dev/null and b/resources/images/talk-grey.png differ diff --git a/resources/images/talk-grey.svg b/resources/images/talk-grey.svg new file mode 100644 index 0000000..fdd1f74 --- /dev/null +++ b/resources/images/talk-grey.svg @@ -0,0 +1,77 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/resources/images/user-large-grey.png b/resources/images/user-large-grey.png new file mode 100644 index 0000000..edc30fb Binary files /dev/null and b/resources/images/user-large-grey.png differ diff --git a/resources/images/user-large-grey.svg b/resources/images/user-large-grey.svg new file mode 100644 index 0000000..35033f5 --- /dev/null +++ b/resources/images/user-large-grey.svg @@ -0,0 +1,82 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/resources/images/user.png b/resources/images/user.png deleted file mode 100644 index ff9858a..0000000 Binary files a/resources/images/user.png and /dev/null differ diff --git a/resources/images/user.svg b/resources/images/user.svg deleted file mode 100644 index 5df4d20..0000000 --- a/resources/images/user.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/resources/libraries/jquery.mobile.custom.js b/resources/libraries/jquery.mobile.custom.js new file mode 100644 index 0000000..376d183 --- /dev/null +++ b/resources/libraries/jquery.mobile.custom.js @@ -0,0 +1,864 @@ +/* +* jQuery Mobile v1.4.5 +* http://jquerymobile.com +* +* Copyright 2010, 2014 jQuery Foundation, Inc. and other contributors +* Released under the MIT license. +* http://jquery.org/license +* +*/ + +(function ( root, doc, factory ) { + if ( typeof define === "function" && define.amd ) { + // AMD. Register as an anonymous module. + define( [ "jquery" ], function ( $ ) { + factory( $, root, doc ); + return $.mobile; + }); + } else { + // Browser globals + factory( root.jQuery, root, doc ); + } +}( this, document, function ( jQuery, window, document, undefined ) {// This plugin is an experiment for abstracting away the touch and mouse +// events so that developers don't have to worry about which method of input +// the device their document is loaded on supports. +// +// The idea here is to allow the developer to register listeners for the +// basic mouse events, such as mousedown, mousemove, mouseup, and click, +// and the plugin will take care of registering the correct listeners +// behind the scenes to invoke the listener at the fastest possible time +// for that device, while still retaining the order of event firing in +// the traditional mouse environment, should multiple handlers be registered +// on the same element for different events. +// +// The current version exposes the following virtual events to jQuery bind methods: +// "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel" + +(function( $, window, document, undefined ) { + +var dataPropertyName = "virtualMouseBindings", + touchTargetPropertyName = "virtualTouchID", + virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ), + touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ), + mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [], + mouseEventProps = $.event.props.concat( mouseHookProps ), + activeDocHandlers = {}, + resetTimerID = 0, + startX = 0, + startY = 0, + didScroll = false, + clickBlockList = [], + blockMouseTriggers = false, + blockTouchTriggers = false, + eventCaptureSupported = "addEventListener" in document, + $document = $( document ), + nextTouchID = 1, + lastTouchID = 0, threshold, + i; + +$.vmouse = { + moveDistanceThreshold: 10, + clickDistanceThreshold: 10, + resetTimerDuration: 1500 +}; + +function getNativeEvent( event ) { + + while ( event && typeof event.originalEvent !== "undefined" ) { + event = event.originalEvent; + } + return event; +} + +function createVirtualEvent( event, eventType ) { + + var t = event.type, + oe, props, ne, prop, ct, touch, i, j, len; + + event = $.Event( event ); + event.type = eventType; + + oe = event.originalEvent; + props = $.event.props; + + // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280 + // https://github.com/jquery/jquery-mobile/issues/3280 + if ( t.search( /^(mouse|click)/ ) > -1 ) { + props = mouseEventProps; + } + + // copy original event properties over to the new event + // this would happen if we could call $.event.fix instead of $.Event + // but we don't have a way to force an event to be fixed multiple times + if ( oe ) { + for ( i = props.length, prop; i; ) { + prop = props[ --i ]; + event[ prop ] = oe[ prop ]; + } + } + + // make sure that if the mouse and click virtual events are generated + // without a .which one is defined + if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) { + event.which = 1; + } + + if ( t.search(/^touch/) !== -1 ) { + ne = getNativeEvent( oe ); + t = ne.touches; + ct = ne.changedTouches; + touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined ); + + if ( touch ) { + for ( j = 0, len = touchEventProps.length; j < len; j++) { + prop = touchEventProps[ j ]; + event[ prop ] = touch[ prop ]; + } + } + } + + return event; +} + +function getVirtualBindingFlags( element ) { + + var flags = {}, + b, k; + + while ( element ) { + + b = $.data( element, dataPropertyName ); + + for ( k in b ) { + if ( b[ k ] ) { + flags[ k ] = flags.hasVirtualBinding = true; + } + } + element = element.parentNode; + } + return flags; +} + +function getClosestElementWithVirtualBinding( element, eventType ) { + var b; + while ( element ) { + + b = $.data( element, dataPropertyName ); + + if ( b && ( !eventType || b[ eventType ] ) ) { + return element; + } + element = element.parentNode; + } + return null; +} + +function enableTouchBindings() { + blockTouchTriggers = false; +} + +function disableTouchBindings() { + blockTouchTriggers = true; +} + +function enableMouseBindings() { + lastTouchID = 0; + clickBlockList.length = 0; + blockMouseTriggers = false; + + // When mouse bindings are enabled, our + // touch bindings are disabled. + disableTouchBindings(); +} + +function disableMouseBindings() { + // When mouse bindings are disabled, our + // touch bindings are enabled. + enableTouchBindings(); +} + +function startResetTimer() { + clearResetTimer(); + resetTimerID = setTimeout( function() { + resetTimerID = 0; + enableMouseBindings(); + }, $.vmouse.resetTimerDuration ); +} + +function clearResetTimer() { + if ( resetTimerID ) { + clearTimeout( resetTimerID ); + resetTimerID = 0; + } +} + +function triggerVirtualEvent( eventType, event, flags ) { + var ve; + + if ( ( flags && flags[ eventType ] ) || + ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) { + + ve = createVirtualEvent( event, eventType ); + + $( event.target).trigger( ve ); + } + + return ve; +} + +function mouseEventCallback( event ) { + var touchID = $.data( event.target, touchTargetPropertyName ), + ve; + + if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) { + ve = triggerVirtualEvent( "v" + event.type, event ); + if ( ve ) { + if ( ve.isDefaultPrevented() ) { + event.preventDefault(); + } + if ( ve.isPropagationStopped() ) { + event.stopPropagation(); + } + if ( ve.isImmediatePropagationStopped() ) { + event.stopImmediatePropagation(); + } + } + } +} + +function handleTouchStart( event ) { + + var touches = getNativeEvent( event ).touches, + target, flags, t; + + if ( touches && touches.length === 1 ) { + + target = event.target; + flags = getVirtualBindingFlags( target ); + + if ( flags.hasVirtualBinding ) { + + lastTouchID = nextTouchID++; + $.data( target, touchTargetPropertyName, lastTouchID ); + + clearResetTimer(); + + disableMouseBindings(); + didScroll = false; + + t = getNativeEvent( event ).touches[ 0 ]; + startX = t.pageX; + startY = t.pageY; + + triggerVirtualEvent( "vmouseover", event, flags ); + triggerVirtualEvent( "vmousedown", event, flags ); + } + } +} + +function handleScroll( event ) { + if ( blockTouchTriggers ) { + return; + } + + if ( !didScroll ) { + triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) ); + } + + didScroll = true; + startResetTimer(); +} + +function handleTouchMove( event ) { + if ( blockTouchTriggers ) { + return; + } + + var t = getNativeEvent( event ).touches[ 0 ], + didCancel = didScroll, + moveThreshold = $.vmouse.moveDistanceThreshold, + flags = getVirtualBindingFlags( event.target ); + + didScroll = didScroll || + ( Math.abs( t.pageX - startX ) > moveThreshold || + Math.abs( t.pageY - startY ) > moveThreshold ); + + if ( didScroll && !didCancel ) { + triggerVirtualEvent( "vmousecancel", event, flags ); + } + + triggerVirtualEvent( "vmousemove", event, flags ); + startResetTimer(); +} + +function handleTouchEnd( event ) { + if ( blockTouchTriggers ) { + return; + } + + disableTouchBindings(); + + var flags = getVirtualBindingFlags( event.target ), + ve, t; + triggerVirtualEvent( "vmouseup", event, flags ); + + if ( !didScroll ) { + ve = triggerVirtualEvent( "vclick", event, flags ); + if ( ve && ve.isDefaultPrevented() ) { + // The target of the mouse events that follow the touchend + // event don't necessarily match the target used during the + // touch. This means we need to rely on coordinates for blocking + // any click that is generated. + t = getNativeEvent( event ).changedTouches[ 0 ]; + clickBlockList.push({ + touchID: lastTouchID, + x: t.clientX, + y: t.clientY + }); + + // Prevent any mouse events that follow from triggering + // virtual event notifications. + blockMouseTriggers = true; + } + } + triggerVirtualEvent( "vmouseout", event, flags); + didScroll = false; + + startResetTimer(); +} + +function hasVirtualBindings( ele ) { + var bindings = $.data( ele, dataPropertyName ), + k; + + if ( bindings ) { + for ( k in bindings ) { + if ( bindings[ k ] ) { + return true; + } + } + } + return false; +} + +function dummyMouseHandler() {} + +function getSpecialEventObject( eventType ) { + var realType = eventType.substr( 1 ); + + return { + setup: function(/* data, namespace */) { + // If this is the first virtual mouse binding for this element, + // add a bindings object to its data. + + if ( !hasVirtualBindings( this ) ) { + $.data( this, dataPropertyName, {} ); + } + + // If setup is called, we know it is the first binding for this + // eventType, so initialize the count for the eventType to zero. + var bindings = $.data( this, dataPropertyName ); + bindings[ eventType ] = true; + + // If this is the first virtual mouse event for this type, + // register a global handler on the document. + + activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1; + + if ( activeDocHandlers[ eventType ] === 1 ) { + $document.bind( realType, mouseEventCallback ); + } + + // Some browsers, like Opera Mini, won't dispatch mouse/click events + // for elements unless they actually have handlers registered on them. + // To get around this, we register dummy handlers on the elements. + + $( this ).bind( realType, dummyMouseHandler ); + + // For now, if event capture is not supported, we rely on mouse handlers. + if ( eventCaptureSupported ) { + // If this is the first virtual mouse binding for the document, + // register our touchstart handler on the document. + + activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1; + + if ( activeDocHandlers[ "touchstart" ] === 1 ) { + $document.bind( "touchstart", handleTouchStart ) + .bind( "touchend", handleTouchEnd ) + + // On touch platforms, touching the screen and then dragging your finger + // causes the window content to scroll after some distance threshold is + // exceeded. On these platforms, a scroll prevents a click event from being + // dispatched, and on some platforms, even the touchend is suppressed. To + // mimic the suppression of the click event, we need to watch for a scroll + // event. Unfortunately, some platforms like iOS don't dispatch scroll + // events until *AFTER* the user lifts their finger (touchend). This means + // we need to watch both scroll and touchmove events to figure out whether + // or not a scroll happenens before the touchend event is fired. + + .bind( "touchmove", handleTouchMove ) + .bind( "scroll", handleScroll ); + } + } + }, + + teardown: function(/* data, namespace */) { + // If this is the last virtual binding for this eventType, + // remove its global handler from the document. + + --activeDocHandlers[ eventType ]; + + if ( !activeDocHandlers[ eventType ] ) { + $document.unbind( realType, mouseEventCallback ); + } + + if ( eventCaptureSupported ) { + // If this is the last virtual mouse binding in existence, + // remove our document touchstart listener. + + --activeDocHandlers[ "touchstart" ]; + + if ( !activeDocHandlers[ "touchstart" ] ) { + $document.unbind( "touchstart", handleTouchStart ) + .unbind( "touchmove", handleTouchMove ) + .unbind( "touchend", handleTouchEnd ) + .unbind( "scroll", handleScroll ); + } + } + + var $this = $( this ), + bindings = $.data( this, dataPropertyName ); + + // teardown may be called when an element was + // removed from the DOM. If this is the case, + // jQuery core may have already stripped the element + // of any data bindings so we need to check it before + // using it. + if ( bindings ) { + bindings[ eventType ] = false; + } + + // Unregister the dummy event handler. + + $this.unbind( realType, dummyMouseHandler ); + + // If this is the last virtual mouse binding on the + // element, remove the binding data from the element. + + if ( !hasVirtualBindings( this ) ) { + $this.removeData( dataPropertyName ); + } + } + }; +} + +// Expose our custom events to the jQuery bind/unbind mechanism. + +for ( i = 0; i < virtualEventNames.length; i++ ) { + $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] ); +} + +// Add a capture click handler to block clicks. +// Note that we require event capture support for this so if the device +// doesn't support it, we punt for now and rely solely on mouse events. +if ( eventCaptureSupported ) { + document.addEventListener( "click", function( e ) { + var cnt = clickBlockList.length, + target = e.target, + x, y, ele, i, o, touchID; + + if ( cnt ) { + x = e.clientX; + y = e.clientY; + threshold = $.vmouse.clickDistanceThreshold; + + // The idea here is to run through the clickBlockList to see if + // the current click event is in the proximity of one of our + // vclick events that had preventDefault() called on it. If we find + // one, then we block the click. + // + // Why do we have to rely on proximity? + // + // Because the target of the touch event that triggered the vclick + // can be different from the target of the click event synthesized + // by the browser. The target of a mouse/click event that is synthesized + // from a touch event seems to be implementation specific. For example, + // some browsers will fire mouse/click events for a link that is near + // a touch event, even though the target of the touchstart/touchend event + // says the user touched outside the link. Also, it seems that with most + // browsers, the target of the mouse/click event is not calculated until the + // time it is dispatched, so if you replace an element that you touched + // with another element, the target of the mouse/click will be the new + // element underneath that point. + // + // Aside from proximity, we also check to see if the target and any + // of its ancestors were the ones that blocked a click. This is necessary + // because of the strange mouse/click target calculation done in the + // Android 2.1 browser, where if you click on an element, and there is a + // mouse/click handler on one of its ancestors, the target will be the + // innermost child of the touched element, even if that child is no where + // near the point of touch. + + ele = target; + + while ( ele ) { + for ( i = 0; i < cnt; i++ ) { + o = clickBlockList[ i ]; + touchID = 0; + + if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) || + $.data( ele, touchTargetPropertyName ) === o.touchID ) { + // XXX: We may want to consider removing matches from the block list + // instead of waiting for the reset timer to fire. + e.preventDefault(); + e.stopPropagation(); + return; + } + } + ele = ele.parentNode; + } + } + }, true); +} +})( jQuery, window, document ); + +(function( $ ) { + $.mobile = {}; +}( jQuery )); + + (function( $, undefined ) { + var support = { + touch: "ontouchend" in document + }; + + $.mobile.support = $.mobile.support || {}; + $.extend( $.support, support ); + $.extend( $.mobile.support, support ); + }( jQuery )); + + +(function( $, window, undefined ) { + var $document = $( document ), + supportTouch = $.mobile.support.touch, + scrollEvent = "touchmove scroll", + touchStartEvent = supportTouch ? "touchstart" : "mousedown", + touchStopEvent = supportTouch ? "touchend" : "mouseup", + touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; + + // setup new event shortcuts + $.each( ( "touchstart touchmove touchend " + + "tap taphold " + + "swipe swipeleft swiperight " + + "scrollstart scrollstop" ).split( " " ), function( i, name ) { + + $.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + // jQuery < 1.8 + if ( $.attrFn ) { + $.attrFn[ name ] = true; + } + }); + + function triggerCustomEvent( obj, eventType, event, bubble ) { + var originalType = event.type; + event.type = eventType; + if ( bubble ) { + $.event.trigger( event, undefined, obj ); + } else { + $.event.dispatch.call( obj, event ); + } + event.type = originalType; + } + + // also handles scrollstop + $.event.special.scrollstart = { + + enabled: true, + setup: function() { + + var thisObject = this, + $this = $( thisObject ), + scrolling, + timer; + + function trigger( event, state ) { + scrolling = state; + triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event ); + } + + // iPhone triggers scroll after a small delay; use touchmove instead + $this.bind( scrollEvent, function( event ) { + + if ( !$.event.special.scrollstart.enabled ) { + return; + } + + if ( !scrolling ) { + trigger( event, true ); + } + + clearTimeout( timer ); + timer = setTimeout( function() { + trigger( event, false ); + }, 50 ); + }); + }, + teardown: function() { + $( this ).unbind( scrollEvent ); + } + }; + + // also handles taphold + $.event.special.tap = { + tapholdThreshold: 750, + emitTapOnTaphold: true, + setup: function() { + var thisObject = this, + $this = $( thisObject ), + isTaphold = false; + + $this.bind( "vmousedown", function( event ) { + isTaphold = false; + if ( event.which && event.which !== 1 ) { + return false; + } + + var origTarget = event.target, + timer; + + function clearTapTimer() { + clearTimeout( timer ); + } + + function clearTapHandlers() { + clearTapTimer(); + + $this.unbind( "vclick", clickHandler ) + .unbind( "vmouseup", clearTapTimer ); + $document.unbind( "vmousecancel", clearTapHandlers ); + } + + function clickHandler( event ) { + clearTapHandlers(); + + // ONLY trigger a 'tap' event if the start target is + // the same as the stop target. + if ( !isTaphold && origTarget === event.target ) { + triggerCustomEvent( thisObject, "tap", event ); + } else if ( isTaphold ) { + event.preventDefault(); + } + } + + $this.bind( "vmouseup", clearTapTimer ) + .bind( "vclick", clickHandler ); + $document.bind( "vmousecancel", clearTapHandlers ); + + timer = setTimeout( function() { + if ( !$.event.special.tap.emitTapOnTaphold ) { + isTaphold = true; + } + triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) ); + }, $.event.special.tap.tapholdThreshold ); + }); + }, + teardown: function() { + $( this ).unbind( "vmousedown" ).unbind( "vclick" ).unbind( "vmouseup" ); + $document.unbind( "vmousecancel" ); + } + }; + + // Also handles swipeleft, swiperight + $.event.special.swipe = { + + // More than this horizontal displacement, and we will suppress scrolling. + scrollSupressionThreshold: 30, + + // More time than this, and it isn't a swipe. + durationThreshold: 1000, + + // Swipe horizontal displacement must be more than this. + horizontalDistanceThreshold: 30, + + // Swipe vertical displacement must be less than this. + verticalDistanceThreshold: 30, + + getLocation: function ( event ) { + var winPageX = window.pageXOffset, + winPageY = window.pageYOffset, + x = event.clientX, + y = event.clientY; + + if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) || + event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) { + + // iOS4 clientX/clientY have the value that should have been + // in pageX/pageY. While pageX/page/ have the value 0 + x = x - winPageX; + y = y - winPageY; + } else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) { + + // Some Android browsers have totally bogus values for clientX/Y + // when scrolling/zooming a page. Detectable since clientX/clientY + // should never be smaller than pageX/pageY minus page scroll + x = event.pageX - winPageX; + y = event.pageY - winPageY; + } + + return { + x: x, + y: y + }; + }, + + start: function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event, + location = $.event.special.swipe.getLocation( data ); + return { + time: ( new Date() ).getTime(), + coords: [ location.x, location.y ], + origin: $( event.target ) + }; + }, + + stop: function( event ) { + var data = event.originalEvent.touches ? + event.originalEvent.touches[ 0 ] : event, + location = $.event.special.swipe.getLocation( data ); + return { + time: ( new Date() ).getTime(), + coords: [ location.x, location.y ] + }; + }, + + handleSwipe: function( start, stop, thisObject, origTarget ) { + if ( stop.time - start.time < $.event.special.swipe.durationThreshold && + Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && + Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { + var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight"; + + triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true ); + triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true ); + return true; + } + return false; + + }, + + // This serves as a flag to ensure that at most one swipe event event is + // in work at any given time + eventInProgress: false, + + setup: function() { + var events, + thisObject = this, + $this = $( thisObject ), + context = {}; + + // Retrieve the events data for this element and add the swipe context + events = $.data( this, "mobile-events" ); + if ( !events ) { + events = { length: 0 }; + $.data( this, "mobile-events", events ); + } + events.length++; + events.swipe = context; + + context.start = function( event ) { + + // Bail if we're already working on a swipe event + if ( $.event.special.swipe.eventInProgress ) { + return; + } + $.event.special.swipe.eventInProgress = true; + + var stop, + start = $.event.special.swipe.start( event ), + origTarget = event.target, + emitted = false; + + context.move = function( event ) { + if ( !start || event.isDefaultPrevented() ) { + return; + } + + stop = $.event.special.swipe.stop( event ); + if ( !emitted ) { + emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget ); + if ( emitted ) { + + // Reset the context to make way for the next swipe event + $.event.special.swipe.eventInProgress = false; + } + } + // prevent scrolling + if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { + event.preventDefault(); + } + }; + + context.stop = function() { + emitted = true; + + // Reset the context to make way for the next swipe event + $.event.special.swipe.eventInProgress = false; + $document.off( touchMoveEvent, context.move ); + context.move = null; + }; + + $document.on( touchMoveEvent, context.move ) + .one( touchStopEvent, context.stop ); + }; + $this.on( touchStartEvent, context.start ); + }, + + teardown: function() { + var events, context; + + events = $.data( this, "mobile-events" ); + if ( events ) { + context = events.swipe; + delete events.swipe; + events.length--; + if ( events.length === 0 ) { + $.removeData( this, "mobile-events" ); + } + } + + if ( context ) { + if ( context.start ) { + $( this ).off( touchStartEvent, context.start ); + } + if ( context.move ) { + $document.off( touchMoveEvent, context.move ); + } + if ( context.stop ) { + $document.off( touchStopEvent, context.stop ); + } + } + } + }; + $.each({ + scrollstop: "scrollstart", + taphold: "tap", + swipeleft: "swipe.left", + swiperight: "swipe.right" + }, function( event, sourceEvent ) { + + $.event.special[ event ] = { + setup: function() { + $( this ).bind( sourceEvent, $.noop ); + }, + teardown: function() { + $( this ).unbind( sourceEvent ); + } + }; + }); + +})( jQuery, this ); + + +})); diff --git a/resources/mobile.js b/resources/mobile.js new file mode 100644 index 0000000..b90ca19 --- /dev/null +++ b/resources/mobile.js @@ -0,0 +1,53 @@ +/* Popout menus (header) */ + +$( function() { + var toggleTime = 200; + + // Open the various menus + $( '#user-tools h2' ).on( 'click', function( e ) { + if ( $( window ).width() < 851 ) { + $( '#p-personal-inner, #menus-cover' ).fadeToggle( toggleTime ); + } + } ); + $( '#site-navigation h2' ).on( 'click', function( e ) { + if ( $( window ).width() < 851 ) { + $( '#site-navigation .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime ); + } + } ); + $( '#site-tools h2' ).on( 'click', function( e ) { + if ( $( window ).width() < 851 ) { + $( '#site-tools .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime ); + } + } ); + $( '#ca-more' ).on( 'click', function( e ) { + $( '#page-tools .sidebar-inner' ).css( "top", $( '#ca-more' ).offset().top + 25 ); + if ( $( window ).width() < 851 ) { + $( '#page-tools .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime ); + } + } ); + $( '#ca-languages' ).on( 'click', function( e ) { + $( '#other-languages .sidebar-inner' ).css( "top", $( '#ca-languages' ).offset().top + 25 ); + if ( $( window ).width() < 851 ) { + $( '#other-languages .sidebar-inner, #menus-cover' ).fadeToggle( toggleTime ); + } + } ); + + // Close menus on click outside + $( document ).click( function( e ) { + if ( $( e.target ).closest( '#menus-cover' ).length > 0 ) { + $( '#p-personal-inner' ).fadeOut( toggleTime ); + $( '.sidebar-inner' ).fadeOut( toggleTime ); + $( '#menus-cover' ).fadeOut( toggleTime ); + } + } ); + + // Include alternative closing method for ios + $( window ).on( 'swiperight', function( e ) { + if ( $( window ).width() < 851 ) { + $( '#p-personal-inner' ).fadeOut( toggleTime ); + $( '.sidebar-inner' ).fadeOut( toggleTime ); + $( '#menus-cover' ).fadeOut( toggleTime ); + } + } ); +} ); + diff --git a/resources/screen-common.less b/resources/screen-common.less index 54def45..cc66e55 100644 --- a/resources/screen-common.less +++ b/resources/screen-common.less @@ -4,19 +4,27 @@ html, body { - font-family: @fonts; margin: 0; padding: 0; +} +body { + font-family: @fonts; color: @text; background: @background-dark; - font-size: .95em; line-height: 1.4; + font-size: @font-size; } + #mw-content { + .box; background: @background; + padding: 1em @content-padding 3em; + overflow: auto; + position: relative; } #mw-content-container { background: @background2; + word-wrap: break-word; } #mw-footer-container { border-top: solid 1px @background-dark2; @@ -33,17 +41,179 @@ body { #p-logo { text-align: center; + margin: auto; +} +.mw-wiki-logo { + display: block; + content: ''; width: 11em; + height: 11em; + background-repeat: no-repeat; + background-position: 50% 50%; + margin: auto; +} +#p-logo-text { + width: @column-left-size; + text-align: center; + line-height: 1; - a.mw-wiki-logo { + a { + padding: .2em 1em; + color: @text; + font-family: @fonts-secondary; + font-variant: small-caps; + font-size: 1.75em; display: block; - content: ''; - width: 11em; - height: 11em; - background-repeat: no-repeat; - background-position: 50% 50%; + + /* 13+ character names */ + &.long { + font-size: 1.45em; + padding: 0; + line-height: .8; + } + } +} + +/* Search */ + +#simpleSearch { + box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05); + background: @background; + position: relative; + border: solid 1px @grey; +} +#searchInput { + border: none; + margin: 0; + height: 2.1em; + padding: .4em 4.5em .2em 2em; + box-shadow: none; + background: transparent; + width: 100%; + min-height: 0; +} +#searchButton, +#mw-searchButton { + position: absolute; + top: 0; + right: 1.5em; + width: 2.5em; + height: 2.5em; + .icon; + .background-image-svg('images/search-ltr.svg', 'images/search-ltr.png'); + background-position: 50% 40%; + box-shadow: none; +} + +/* Dropdown stuff */ + +.pokey, +.pokey::after { + border-bottom: 10px solid @grey-bright; + border-left: 10px solid transparent; + border-right: 10px solid transparent; + content: ""; + height: 0px; + position: absolute; + transform: rotate(360deg); // to force some smoothing in annoying browsers + width: 0px; + z-index: 2; +} +.pokey { + top: 1.85em; + right: 1px; + display: none; +} +.pokey::after { + border-bottom-color: #FFFFFF; + right: -10px; + top: 2px; +} + +.dropdown-toggle { + .dropdown-header(); +} + +/* Footer */ + +#mw-footer { + padding-top: .5em; + padding-bottom: 1em; + + ul, + li { + margin: 1em 0; + list-style: none; + padding: 0; + } + #footer-icons { + float: right; + margin: 0 0 0 1em; + + li { + margin: 0 0 1em 0; + } + } +} +#footer-places li { + display: inline; + padding-right: 1em; +} + +/* Content */ + +#mw-content-container { + border-bottom: solid 4px @green; +} +#page-header-links { + div, + ul, + li { + list-style: none; + display: inline-block; + margin: 0; + padding: 0; + line-height: 1.5; + } + li { + margin: .25em 0 1.5em; + border-bottom: solid 3px @background; + + &:hover { + border-bottom-color: @grey; + } + &.selected { + border-bottom-color: @blue; + + a { + color: @text; + } + } + } +} +#p-namespaces { + float: left; + + li { + margin-right: 1em; } } +#p-pagetools { + float: right; + + li { + margin-left: 1em; + } +} + +#ca-watch a { + .ca-icon(); + .background-image-svg('images/star.svg', 'images/star.png'); +} +#ca-unwatch a { + .ca-icon(); + .background-image-svg('images/star-filled.svg', 'images/star-filled.png'); +} /* Misc */ @@ -51,6 +221,7 @@ a { text-decoration: none; color: @blue; + &:hover, &:visited { color: @blue-dark; } @@ -59,6 +230,7 @@ a.new, .new a { color: @red; + &:hover, &:visited { color: @red-dark; } @@ -66,6 +238,14 @@ a.new, .mw-editsection { font-family: @fonts; + + a { + .background-image-svg('images/pencil-grey.svg', 'images/pencil-grey.png'); + background-repeat: no-repeat; + background-position: 0 0; + display: inline-block; + padding: .5em 0 .25em 1.75em; + } } .mw-editsection-bracket { display: none; @@ -73,6 +253,10 @@ a.new, .visual-clear { clear: both; } +.mw-indicators { + float: right; + margin: .75em 0 0 1em; +} #mw-content { h1, h2, h3, h4, h5, h6, dt { font-weight: normal; @@ -218,12 +402,41 @@ table.wikitable > * > tr > th { border: none; } +/* Color bars */ + +.color-bar { + width: 100%; +} +@color-height: 4px; +.color-left { + height: @color-height; + background: @red-dark; + width: 50%; + float: left; +} +.color-right { + display: inline-block; + height: @color-height; + background: @green-dark; + width: 50%; + float: right; +} +.color-middle-container { + max-width: @content-width; + margin: 0 auto -@color-height; + position: relative; +} +.color-middle { + height: @color-height; + background: @blue-dark; + margin-left: @column-left-size + 1em; + margin-right: @column-right-size + 1em; +} /* Hidden stuff */ #p-namespaces h3, #p-pagetools h3, -#mw-site-navigation h2, #p-search h3 { .hidden; } diff --git a/resources/screen-desktop-full.less b/resources/screen-desktop-full.less new file mode 100644 index 0000000..a9c4e50 --- /dev/null +++ b/resources/screen-desktop-full.less @@ -0,0 +1,19 @@ +@import "variables.less"; + +#mw-site-navigation { + .column-left(); +} + +#mw-content { + margin-left: @column-left-size; + margin-right: @column-right-size; +} + +#mw-related-navigation { + .column-right(); +} + +// Redundant content category list +#catlinks { + display: none; +} diff --git a/resources/screen-desktop-mid.less b/resources/screen-desktop-mid.less new file mode 100644 index 0000000..9768e7f --- /dev/null +++ b/resources/screen-desktop-mid.less @@ -0,0 +1,19 @@ +@import "variables.less"; + +#mw-site-navigation, +#mw-related-navigation { + .column-left(); +} + +#mw-related-navigation { + margin-top: 2em; +} + +#mw-content { + margin-left: @column-left-size; +} + +// Redundant content category list +#catlinks { + display: none; +} diff --git a/resources/screen-desktop-small.less b/resources/screen-desktop-small.less new file mode 100644 index 0000000..5553043 --- /dev/null +++ b/resources/screen-desktop-small.less @@ -0,0 +1,127 @@ +@import "variables.less"; + +// Redundant sidebar category list and stuff + +#mw-content-block { + padding: 0; +} +#mw-content { + border-width: 0 0 1px; + clear: both; +} + +.categories-bottom(); + + +// Header navigation + +.sidebar-chunk { + display: inline-block; + position: relative; + + h2 { + display: inline-block; + } +} +.sidebar-inner, +#p-logo { + display: none !important; +} + +// Have these cover the fixed header color-bar using the mw-header-nav-hack (with its own color-bar at the bottom) +#mw-site-navigation, +#mw-related-navigation { + display: inline-block; + position: relative; + z-index: 99; + + h2 { + font-weight: normal; + font-family: @fonts-secondary; + font-size: 1.25em; + padding: .5em 0 .2em; + margin: 0; + + .dropdown-header(); + } +} +.sidebar-chunk:hover { + .pokey, + .sidebar-inner { + display: block !important; + } +} + +#mw-header-nav-hack { + border-top: solid 2px @grey-bright; + display: block; + position: absolute; + z-index: 98; + background: @background; + width: 100%; +} +#mw-header-nav-hack .color-bar { + margin-top: 2.5em; // Height of expected menu header contents +} + +#mw-site-navigation { + float: left; + margin-left: 2em; + + .sidebar-chunk { + margin: 0 1em 0 0; + } + .sidebar-inner { + .dropdown-menu( left ); + } +} +#mw-related-navigation { + float: right; + margin-right: 2em; + + .sidebar-chunk { + margin: 0 0 0 1em; + } + .sidebar-inner { + .dropdown-menu(); + top: 2.95em !important; // to override mobile positioning + } +} + +// Consistency + +#mw-header { + padding: 0 2em; +} +#p-logo-text a { + padding-left: 0; + text-align: left; +} + +#mw-footer { + padding: 0 @content-padding; +} + +// Personal menu + +#p-personal { + float: right; + + h2 span { + display: none; + } + h2:after { + margin-left: -.65em; + } + .dropdown { + right: -1em; + } + } + +#user-tools { + width: 6em; +} +#p-search { + margin-right: 6em; +} + diff --git a/resources/screen-desktop.less b/resources/screen-desktop.less index b5acdb0..8be7343 100644 --- a/resources/screen-desktop.less +++ b/resources/screen-desktop.less @@ -1,25 +1,21 @@ @import "variables.less"; +// Override menu display from mobile +// This also requires !importants where they hover and stuff +.dropdown, +.sidebar-inner { + display: block !important; +} + .ts-inner { - max-width: 100em; - padding: 0 3em; + max-width: @content-width; + padding: 0 1em; margin: auto; } -#p-logo-text, -#mw-site-navigation { - width: 12em; - float: left; -} -#p-search, -#mw-content, -#mw-header-container .color-middle { - margin-left: 12em; - margin-right: 15em; -} -#mw-related-navigation, -#user-tools { - width: 15em; - float: right; +#mw-content-container { + .background-image-svg('images/cat-grey.svg', 'images/cat-grey.png'); + background-repeat: no-repeat; + background-position: center 15em; } /* Header */ @@ -32,54 +28,63 @@ background: @background; color: @text; padding: 0.5em 0 0; + min-height: @fixed-header-height; + box-sizing: border-box; +} +#p-logo-text { + width: @column-left-size; + float: left; +} +#p-search { + margin-left: @column-left-size; + margin-right: @column-right-size; +} +#user-tools { + width: @column-right-size; + float: right; +} +#mw-header-nav-hack, +#mw-header-hack { + position: fixed; + z-index: 97; + top: @fixed-header-height; box-shadow: 0 3px 3px 2px rgba(0, 0, 0, 0.075), 0 0 2px rgba(0, 0, 0, 0.2); - min-height: 2.85em; +} +#mw-header-nav-hack { + display: none; } /* Add offset to make anchor links work with the fixed header */ :target:before { content: ""; display: block; - height: 2.85em; - margin: -2.85em 0 0; -} - -@color-height: 4px; -.color-left { - float:left; - height: @color-height; - background: @red-dark; - width: 50%; -} -.color-right { - display: inline-block; - height: @color-height; - background: @green-dark; - width: 50%; - float:right; -} -.color-middle { - height: @color-height; - background: @blue-dark; - margin-top: .4em; - margin-bottom: -@color-height; - position: relative; + height: @fixed-header-height; + margin: -@fixed-header-height 0 0; } .dropdown { - display: none; + display: none !important; } #p-personal { display: inline-block; position: relative; padding-left: 1.5em; - padding-right: 2em; line-height: 1; + + .dropdown { + .dropdown-menu(); + top: 2.95em; + right: -2em; + } + .pokey { + top: 2.125em; + } } -#p-personal h3 { - margin: 0 0 -1em; - padding: .7em 0 1.25em 25px; + +#p-personal h2 { + margin: 0; + padding: .7em 0 0 25px; font-family: @fonts-secondary; font-weight: normal; font-size: 1.1em; @@ -87,251 +92,24 @@ background-position: 0 7px; background-repeat: no-repeat; - &:after { - display: inline-block; - content: ''; - width: 22px; - height: 12px; - .background-image-svg('images/arrow-down-grey.svg', 'images/arrow-down-grey.png'); - background-position: center center; - background-repeat: no-repeat; - } - &:hover { - cursor: pointer; - } + .dropdown-header(); } - -#p-logo-text { - width: 11em; - text-align: center; - line-height: 1; - - a { - padding: .2em 0; - color: @text; - font-family: @fonts-secondary; - font-variant: small-caps; - font-size: 1.75em; - display: block; - - /* 13+ character names */ - &.long { - font-size: 1.45em; - padding: 0; - line-height: .8; - } +#p-personal:hover { + .pokey, + .dropdown { + display: block !important; } } -/* Search */ - -#simpleSearch { - box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.05); - background: @background; - position: relative; - border: solid 1px @grey; -} -#searchInput { - border: none; - margin: 0; - height: 2.1em; - padding: .4em 4.5em .2em 2em; - box-shadow: none; - background: transparent; - width: 100%; - min-height: 0; -} -#searchButton, -#mw-searchButton { - position: absolute; - top: 0; - right: 1.5em; - width: 2.5em; - height: 2.5em; - .icon; - .background-image-svg('images/search-ltr.svg', 'images/search-ltr.png'); - background-position: 50% 40%; - box-shadow: none; -} - -/* Dropdowns */ - -#p-personal:hover .dropdown { - display: block; -} - -.dropdown { - .box; - background: #fff; - box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.05); - position: absolute; - padding: 20px 2em 0; - min-width: 6em; - top: 2.95em; - right: 0; - overflow: visible; - line-height: 1.1; - z-index: 1; - - ul, - li { - list-style: none; - margin: 0; - padding: 0; - } - ul { - margin-bottom: 2em; - } - li { - margin: 0 0 .35em; - } -} -.pokey, -.pokey::after { - border-bottom: 10px solid #E6E6E6; - border-left: 10px solid transparent; - border-right: 10px solid transparent; - content: ""; - height: 0px; - position: absolute; - transform: rotate(360deg); // to force some smoothing - width: 0px; -} -.pokey { - right: 28px; - top: -10px; -} -.pokey::after { - border-bottom-color: #FFFFFF; - right: -10px; - top: 2px; +#page-header-links #ca-more, +#page-header-links #ca-languages, +.sidebar-chunk h2 { + display: none; } /* Content */ #mw-content-container { - margin-top: 3em; - padding-bottom: .5em; - border-bottom: solid 4px @green; -} -#mw-content { - .box; - background: @background; - padding: 1em 2em 3em; - overflow: hidden; -} -#page-header-links { - div, - ul, - li { - list-style: none; - display: inline-block; - margin: 0; - padding: 0; - } - li { - margin: .25em 0 1.5em; - - &:hover { - border-bottom: solid 3px @grey; - } - &.selected { - border-bottom: solid 3px @blue; - - a { - color: @text; - } - } - } - #p-namespaces { - float: left; - - li { - margin-right: 1em; - } - } - #p-pagetools { - float: right; - - li { - margin-left: 1em; - } - } -} -#ca-watch a, -#ca-unwatch a { - .icon; - display: inline-block; - width: 1.4em; - height: 1.4em; - box-sizing: border-box; -} -#ca-watch a { - .background-image-svg('images/star.svg', 'images/star.png'); -} -#ca-unwatch a { - .background-image-svg('images/star-filled.svg', 'images/star-filled.png'); -} - -/* Sidebars */ - -#mw-site-navigation .sidebar-chunk, -#mw-related-navigation .sidebar-chunk { - .box; - padding: 1.5em 1.5em 0; - margin: 1em; - line-height: 1.1; - - ul, - li { - list-style: none; - margin: 0; - padding: 0; - } - h3 { - font-weight: normal; - font-size: 1em; - margin: .25em 0 .75em 0; - padding-bottom: .15em; - border-bottom: solid 2px @grey; - } - ul { - margin-bottom: 2em; - } - li { - margin: 0 0 .35em; - } -} - -#mw-site-navigation .sidebar-chunk { - margin-left: 0; -} -#mw-related-navigation .sidebar-chunk { - margin-right: 0; -} - -/* Footer */ - -#mw-footer { - padding-top: .5em; + margin-top: @fixed-header-height; padding-bottom: 1em; - - ul, - li { - margin: 1em 0; - list-style: none; - padding: 0; - } -} -#footer-places li { - display: inline; - padding-right: 1em; -} -#footer-icons { - float: right; - margin: 0 0 0 1em; - - li { - margin: 0 0 1em 0; - } } diff --git a/resources/screen-misc.less b/resources/screen-misc.less new file mode 100644 index 0000000..05658de --- /dev/null +++ b/resources/screen-misc.less @@ -0,0 +1,24 @@ +@import "variables.less"; + +// Let's add some unnecessary white space. Or grey, as it were. In the dumbest possible way. + +@media screen and (min-width: 1425px) { + .color-middle-container, + .ts-inner { + padding: 0 3em; + } + .color-middle { + margin-left: @column-left-size; + margin-right: @column-right-size; + } +} + +@media screen and (min-width: 1250px) and (max-width: 1339px) { + .ts-inner { + padding: 0 3em; + } + .color-middle { + margin-left: @column-left-size + 3em; + margin-right: @column-right-size + 3em; + } +} diff --git a/resources/screen-mobile.less b/resources/screen-mobile.less index 61c088f..5a2a372 100644 --- a/resources/screen-mobile.less +++ b/resources/screen-mobile.less @@ -1 +1,244 @@ @import "variables.less"; + +/* Layout */ + +#mw-footer { + padding: 0 @content-padding; +} +.color-middle { + margin: auto; + width: 34%; +} + +.categories-bottom(); +#catlinks { + padding-bottom: 2em; + border-top: solid 3px @grey; +} + +#page-header-links #ca-view, +#mw-header-nav-hack, +#page-tools h2, +#p-logo { + display: none; +} + +#mw-header-container { + background: @background; + padding: 3.75em @content-padding .35em; +} + +#mw-header-hack { + position: relative; + z-index: 1; + box-shadow: 0 3px 3px 2px rgba(0, 0, 0, 0.075), 0 0 2px rgba(0, 0, 0, 0.2); +} + +/* Dropdowns */ + +.sidebar-inner, +.dropdown { + display: none; +} + +.sidebar-inner, +.dropdown { + .nav-block(); + background: @background; + box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.05); + position: absolute; + padding: 2em 2.5em 1em; + margin: 0; + min-width: 9.153em; + max-width: 80%; + top: 3.25em; + right: 0; + overflow: visible; + z-index: 100; + + h3 { + margin: .5em 0 1.5em; + } + ul { + margin: 1em 0 2em; + } + li { + margin: 0 0 .75em; + } +} + +#menus-cover { + display: none; + position: fixed; + top: 0; + left: 0; + z-index: 99; + width: 100%; + height: 100%; + background: @background2; + opacity: .8; + overflow: hidden; +} + +/* Dropdown toggles */ + +#user-tools h2, +.sidebar-chunk h2 { + .dropdown-header(); + margin: 0; + width: 30px; + height: 30px; + position: absolute; + top: 1em; + padding-bottom: 0; + font-size: 1em; + background-repeat: no-repeat; + background-position: 50% 50%; + + span { + display: inline-block; + .icon(); + } + &:after { + position: absolute; + top: .65em; + left: 23px; + } +} +#user-tools h2 { + right: @content-padding + .5em; + .background-image-svg('images/user-large-grey.svg', 'images/user-large-grey.png'); +} +#site-navigation { + h2 { + left: @content-padding; + .background-image-svg('images/menu-large-grey.svg', 'images/menu-large-grey.png'); + } + .sidebar-inner { + left: 0; + right: auto; + } +} +#site-tools h2 { + right: 7em; + .background-image-svg('images/gear-large-grey.svg', 'images/gear-large-grey.png'); +} + +/* Logo */ + +#p-logo-text { + position: absolute; + top: .75em; + left: 6em; + text-align: left; + + a { + padding-left: 0; + } +} + +/* Page actions */ + +#p-namespaces li { + margin-right: 1.5em; +} +#p-pagetools li { + margin-left: 1.5em; + + &#ca-languages { + margin-left: 1em; + } +} + +#page-header-links a { + .ca-icon(); +} +#ca-edit a { + .background-image-svg('images/pencil-grey.svg', 'images/pencil-grey.png'); +} +#ca-history a { + .background-image-svg('images/clock-grey.svg', 'images/clock-grey.png'); +} +#ca-talk a { + .background-image-svg('images/talk-grey.svg', 'images/talk-grey.png'); +} +#t-contributions a { + .background-image-svg('images/puzzle-grey.svg', 'images/puzzle-grey.png'); +} +#ca-addsection a { + .background-image-svg('images/plus-grey.svg', 'images/plus-grey.png'); +} +*[id^='ca-nstab-'] a { + .background-image-svg('images/page-grey.svg', 'images/page-grey.png'); +} + +#ca-more, +#ca-languages { + &:after { + margin-left: -.35em; + } + + span { + .ca-icon(); + } +} +#ca-more span { + .background-image-svg('images/gear-grey.svg', 'images/gear-grey.png'); +} +#ca-languages span { + .background-image-svg('images/languages-grey.svg', 'images/languages-grey.png'); +} + +/* Full-width thumbnails */ + +div.thumb { + float: none; +} +.thumb { + margin: 1em auto; +} +.tright { + margin-left: 0; + padding-left: 0; +} +.tleft { + margin-right: 0; + padding-right: 0; +} +.thumbinner { + padding: 1em 1.5em; + width: 100% !important; + box-sizing: border-box; +} +.thumbimage { + display: block; + margin: 0 auto .5em; +} + +#mw-content { + overflow: auto; + border: none; +} +#mw-content-block { + background: @background; +} + +/* Keep images from overflowing */ + +#mw-content a > img { + height: auto !important; + max-width: 100% !important; +} + +/* Table of contents */ + +#toc, .toc, .mw-warning { + width: 100%; + box-sizing: border-box; +} + +/* Misc */ + +.nomobile { + display: none; +} diff --git a/resources/variables.less b/resources/variables.less index 5ea87bc..6657dfa 100644 --- a/resources/variables.less +++ b/resources/variables.less @@ -1,5 +1,7 @@ @import "mediawiki.mixins"; +// Colours + @text: #013; @background: #fff; @background2: #f3f3f3; @@ -33,6 +35,7 @@ // Flair + // Fonts are chosen for consistent metrics, not necessarily overall prettiness. // This will NEED fixes for different languages. @fonts-secondary: 'Linux Libertine', 'Times New Roman', serif; @@ -40,8 +43,21 @@ @border: .2em; @radius: .2em; +@font-size: .95em; + +// Widths + +@column-left-size: 14em; +@column-right-size: 16em; + +@content-width: 100em; +@content-padding: 2em; + +@fixed-header-height: 3.125em; + + -/* Misc */ +// Miscellaneous functions // To hide objects, but keep them accessible for screen-readers .hidden() { @@ -53,11 +69,11 @@ .icon() { text-indent: -99999px; border: none; - background: transparent; + background-color: transparent; background-repeat: no-repeat; } -.box { +.box() { background: @background3; border: solid @grey-bright; border-width: 1px 1px @border; @@ -65,3 +81,115 @@ box-shadow: 0 1px 2px 1px rgba(0, 0, 0, 0.02); overflow: auto; } + +// Icons for the page actions menus +.ca-icon() { + .icon; + display: inline-block; + width: 20px; + height: 20px; + box-sizing: border-box; + margin-bottom: -.5em; +} + +// Navigation column blocks + +.nav-block() { + .box(); + padding: 1.5em 1.5em 0; + margin: 1em 0; + line-height: 1.1; + + ul, + li { + list-style: none; + margin: 0; + padding: 0; + } + h3 { + font-weight: normal; + font-size: 1em; + margin: .25em 0 .75em 0; + padding-bottom: .15em; + border-bottom: solid 2px @grey; + } + ul { + margin-bottom: 2em; + } + li { + margin: 0 0 .35em; + } +} + +.column-right() { + width: @column-right-size; + float: right; + clear: right; + padding-left: 1em; + box-sizing: border-box; + + .sidebar-chunk { + .nav-block(); + } +} +.column-left() { + width: @column-left-size; + float: left; + clear: left; + padding-right: 1em; + box-sizing: border-box; + + .sidebar-chunk { + .nav-block(); + } +} + +// Dropdowns +.dropdown-header() { + cursor: pointer; + margin-bottom: -1em; + padding-bottom: 1em; + + &:after { + display: inline-block; + content: ''; + width: 22px; + height: 12px; + .background-image-svg('images/arrow-down-grey.svg', 'images/arrow-down-grey.png'); + background-position: center center; + background-repeat: no-repeat; + } +} +.dropdown-menu( @direction: right ) { + .nav-block(); + background: @background; + box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.05); + position: absolute; + padding: 20px 2em 0; + margin: 0; + min-width: 9.153em; + top: 2.95em; + @{direction}: -1em; + overflow: visible; + z-index: 1; +} + +// Categories on bottom of page + +.categories-bottom() { + #catlinks-sidebar { + display: none; + } + + #catlinks { + margin: 0 @content-padding; + padding: 1.5em 0 .5em; + + li { + border-left: none; + } + div { + margin: 0 0 .35em; + } + } +} diff --git a/skin.json b/skin.json index 109d740..6b0e276 100644 --- a/skin.json +++ b/skin.json @@ -1,7 +1,7 @@ { "name": "Timeless", - "version": "0.3", - "author": "Isarra", + "version": "0.6", + "author": "Isarra Yos", "url": "https://www.mediawiki.org/wiki/Skin:Timeless", "descriptionmsg": "timeless-desc", "namemsg": "skinname-timeless", @@ -30,18 +30,40 @@ "media": "screen" }, "resources/screen-desktop.less": { - "media": "screen and (min-width: 751px)" + "media": "screen and (min-width: 851px)" + }, + "resources/screen-desktop-full.less": { + "media": "screen and (min-width: 1340px)" + }, + "resources/screen-desktop-mid.less": { + "media": "screen and (min-width: 1100px) and (max-width: 1339px)" + }, + "resources/screen-desktop-small.less": { + "media": "screen and (min-width: 851px) and (max-width: 1099px)" }, "resources/screen-mobile.less": { - "media": "screen and (max-width: 750px)" + "media": "screen and (max-width: 850px)" } } }, + "skins.timeless.misc": { + "position": "top", + "styles": [ + "resources/screen-misc.less" + ] + }, "skins.timeless.js": { "position": "bottom", "scripts": [ "resources/main.js" ] + }, + "skins.timeless.mobile": { + "position": "bottom", + "scripts": [ + "resources/libraries/jquery.mobile.custom.js", + "resources/mobile.js" + ] } }, "ResourceFileModulePaths": {