feat: initial refactor into TabberNeue

This commit is contained in:
alistair3149 2021-06-21 13:49:47 -04:00
parent 6c67baf4d1
commit eb9564509b
No known key found for this signature in database
GPG key ID: 94D081060FD3DD9C
47 changed files with 502 additions and 199 deletions

View file

@ -1,6 +0,0 @@
[gerrit]
host=gerrit.wikimedia.org
port=29418
project=mediawiki/extensions/Tabber
defaultbranch=master
defaultrebase=0

View file

@ -1,7 +1,9 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<ruleset name="MediaWiki"> <ruleset>
<rule ref="./vendor/hydrawiki/hydrawiki-codesniffer/HydraWiki" /> <rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki" />
<file>.</file> <file>.</file>
<arg name="encoding" value="utf8"/>
<arg name="extensions" value="php"/> <arg name="extensions" value="php"/>
<arg name="encoding" value="UTF-8"/>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/out/*</exclude-pattern>
</ruleset> </ruleset>

52
README.md Normal file
View file

@ -0,0 +1,52 @@
# TabberNeue
The TabberNeue extension allows wikis to create tabs within a page. It is a rewritten and forked version of [Extension:Tabber](https://www.mediawiki.org/wiki/Extension:Tabber). It includes multiple improvements such as responsive layout support, ARIA support, and conform to Wikimedia UI.
[Extension:TabberNeue on MediaWiki](https://www.mediawiki.org/wiki/Extension:TabberNeue).
## Requirements
* [MediaWiki](https://www.mediawiki.org) 1.35 or later
## Installation
You can get the extension via Git (specifying TabberNeue as the destination directory):
git clone https://github.com/StarCitizenTools/mediawiki-extensions-TabberNeue.git TabberNeue
Or [download it as zip archive](https://github.com/StarCitizenTools/mediawiki-extensions-TabberNeue/archive/main.zip).
In either case, the "TabberNeue" extension should end up in the "extensions" directory
of your MediaWiki installation. If you got the zip archive, you will need to put it
into a directory called TabberNeue.
## Usage
TabberNeue uses the exact same syntax as Tabber.
Tabs are created with `tabName=tabBody`, and separated by `|-|`.
```html
<tabber>
tab1=Some neat text here
|-|
tab2=
[http://www.google.com Google]<br/>
[http://www.cnn.com Cnn]<br/>
|-|
tab3={{Template:SomeTemplate}}
</tabber>
```
### Parser functions and conditionals
```html
<tabber>
Tab1 = {{{1|}}}
|-|
Tab2 = {{{2|}}}
</tabber>
```
Becomes:
```
{{#tag:tabber|
Tab1={{{1|}}}
{{!}}-{{!}}
Tab2={{{2|}}}
}}
```

View file

@ -1,54 +0,0 @@
ul.tabbernav {
margin: 0;
padding: 3px 0;
border-bottom: 1px solid #CCC;
font: bold 12px Verdana, sans-serif;
}
ul.tabbernav li {
list-style: none;
margin: 0;
display: inline-block;
padding-top: 1em;
}
ul.tabbernav li a {
padding: 3px .5em;
margin-left: 3px;
border: 1px solid #CCC;
border-bottom: none;
background: #F2F7FF;
text-decoration: none;
white-space: pre;
}
ul.tabbernav li a:link {
color: #448;
}
ul.tabbernav li a:visited {
color: #667;
}
ul.tabbernav li a:hover {
color: #000;
background: #FFF9F2;
border-color: #CCC;
}
ul.tabbernav li.tabberactive a {
background-color: #FFF;
border-bottom: 1px solid #FFF;
}
ul.tabbernav li.tabberactive a:hover {
color: #000;
background: #FFF;
border-bottom: 1px solid #FFF;
}
.tabber .tabbertab {
padding: 5px;
border: 1px solid #CCC;
border-top: 0;
}

View file

@ -1,32 +1,33 @@
{ {
"name": "Tabber", "name": "TabberNeue",
"version": "2.4.5", "version": "0.0.1",
"author": [ "author": [
"alistair3149",
"Eric Fortin", "Eric Fortin",
"Alexia E. Smith" "Alexia E. Smith"
], ],
"url": "https://www.mediawiki.org/wiki/Extension:Tabber", "url": "https://www.mediawiki.org/wiki/Extension:TabberNeue",
"descriptionmsg": "tabber-desc", "descriptionmsg": "tabberneue-desc",
"type": "parserhook", "type": "parserhook",
"license-name": "GPL-3.0-only", "license-name": "GPL-3.0-or-later",
"requires": { "requires": {
"MediaWiki": ">= 1.29.0" "MediaWiki": ">= 1.35.0"
}, },
"MessagesDirs": { "MessagesDirs": {
"Tabber": [ "TabberNeue": [
"/i18n" "/i18n"
] ]
}, },
"AutoloadClasses": { "AutoloadClasses": {
"Tabber\\TabberHooks": "TabberHooks.php" "TabberNeue\\TabberNeueHooks": "includes/TabberNeueHooks.php"
}, },
"ResourceModules": { "ResourceModules": {
"ext.Tabber": { "ext.tabberNeue": {
"styles": [ "styles": [
"css/tabber.css" "ext.tabberNeue.less"
], ],
"scripts": [ "scripts": [
"js/tabber.js" "ext.tabberNeue.js"
], ],
"dependencies": [ "dependencies": [
"mediawiki.Uri", "mediawiki.Uri",
@ -36,15 +37,33 @@
"desktop", "desktop",
"mobile" "mobile"
] ]
},
"ext.tabberNeue.icons": {
"class": "ResourceLoaderImageModule",
"selector": ".tabber__header__{name}:after",
"images": {
"next": {
"file": {
"ltr": "ext.tabberNeue.icons/next-ltr.svg",
"rtl": "ext.tabberNeue.icons/next-rtl.svg"
}
},
"prev": {
"file": {
"ltr": "ext.tabberNeue.icons/previous-ltr.svg",
"rtl": "ext.tabberNeue.icons/previous-rtl.svg"
}
}
}
} }
}, },
"ResourceFileModulePaths": { "ResourceFileModulePaths": {
"localBasePath": "", "localBasePath": "modules",
"remoteExtPath": "Tabber" "remoteExtPath": "TabberNeue/modules"
}, },
"Hooks": { "Hooks": {
"ParserFirstCallInit": [ "ParserFirstCallInit": [
"Tabber\\TabberHooks::onParserFirstCallInit" "TabberNeue\\TabberNeueHooks::onParserFirstCallInit"
] ]
}, },
"manifest_version": 1 "manifest_version": 1

View file

@ -4,5 +4,5 @@
"Xuacu" "Xuacu"
] ]
}, },
"tabber-desc": "Permite crear llingüetes dientro d'una páxina" "tabberneue-desc": "Permite crear llingüetes dientro d'una páxina"
} }

View file

@ -4,5 +4,5 @@
"Wizardist" "Wizardist"
] ]
}, },
"tabber-desc": "Дазваляе ствараць закладкі на старонцы" "tabberneue-desc": "Дазваляе ствараць закладкі на старонцы"
} }

View file

@ -4,5 +4,5 @@
"DCLXVI" "DCLXVI"
] ]
}, },
"tabber-desc": "Позволява създаването на табове в страниците" "tabberneue-desc": "Позволява създаването на табове в страниците"
} }

View file

@ -4,5 +4,5 @@
"Y-M D" "Y-M D"
] ]
}, },
"tabber-desc": "Aotreañ a ra krouiñ ivinelloù war ur bajenn" "tabberneue-desc": "Aotreañ a ra krouiñ ivinelloù war ur bajenn"
} }

View file

@ -4,5 +4,5 @@
"BroOk" "BroOk"
] ]
}, },
"tabber-desc": "Permet crear tabs dins una pàgina" "tabberneue-desc": "Permet crear tabs dins una pàgina"
} }

View file

@ -4,5 +4,5 @@
"Kghbln" "Kghbln"
] ]
}, },
"tabber-desc": "Ermöglicht das Erstellen von Reitern innerhalb einer Seite" "tabberneue-desc": "Ermöglicht das Erstellen von Reitern innerhalb einer Seite"
} }

View file

@ -4,5 +4,5 @@
"Michawiki" "Michawiki"
] ]
}, },
"tabber-desc": "Zmóžnja rejtarki w boku napóraś" "tabberneue-desc": "Zmóžnja rejtarki w boku napóraś"
} }

View file

@ -1,8 +1,9 @@
{ {
"@metadata": { "@metadata": {
"authors": [ "authors": [
"alistair3149",
"Eric Fortin" "Eric Fortin"
] ]
}, },
"tabber-desc": "Allows to create tabs within a page" "tabberneue-desc": "Allows to create tabs within a page. Forked from [https://www.mediawiki.org/wiki/Extension:Tabber Extension:Tabber]."
} }

View file

@ -4,5 +4,5 @@
"Armando-Martin" "Armando-Martin"
] ]
}, },
"tabber-desc": "Permite para fichas dentro de una página" "tabberneue-desc": "Permite para fichas dentro de una página"
} }

View file

@ -4,5 +4,5 @@
"Armin1392" "Armin1392"
] ]
}, },
"tabber-desc": "اجازه برای ایجاد تب‌ها درون یک صفحه" "tabberneue-desc": "اجازه برای ایجاد تب‌ها درون یک صفحه"
} }

View file

@ -5,5 +5,5 @@
"Nike" "Nike"
] ]
}, },
"tabber-desc": "Mahdollistaa välilehtien luonnin sivulla" "tabberneue-desc": "Mahdollistaa välilehtien luonnin sivulla"
} }

View file

@ -4,5 +4,5 @@
"Wyz" "Wyz"
] ]
}, },
"tabber-desc": "Permet de créer des onglets sur une page" "tabberneue-desc": "Permet de créer des onglets sur une page"
} }

View file

@ -4,5 +4,5 @@
"Toliño" "Toliño"
] ]
}, },
"tabber-desc": "Permite a creación de lapelas dentro dunha páxina" "tabberneue-desc": "Permite a creación de lapelas dentro dunha páxina"
} }

View file

@ -4,5 +4,5 @@
"Amire80" "Amire80"
] ]
}, },
"tabber-desc": "אפשרות ליצור לשוניות בדף" "tabberneue-desc": "אפשרות ליצור לשוניות בדף"
} }

View file

@ -4,5 +4,5 @@
"Michawiki" "Michawiki"
] ]
}, },
"tabber-desc": "Zmóžnja rajtarki znutřka strony wutworić" "tabberneue-desc": "Zmóžnja rajtarki znutřka strony wutworić"
} }

View file

@ -4,5 +4,5 @@
"Darth Kule" "Darth Kule"
] ]
}, },
"tabber-desc": "Permette di creare schede all'interno di una pagina" "tabberneue-desc": "Permette di creare schede all'interno di una pagina"
} }

View file

@ -4,5 +4,5 @@
"Shirayuki" "Shirayuki"
] ]
}, },
"tabber-desc": "ページ内にタブを作成できるようにする" "tabberneue-desc": "ページ内にタブを作成できるようにする"
} }

View file

@ -4,5 +4,5 @@
"아라" "아라"
] ]
}, },
"tabber-desc": "문서 내에서 탭 만들기 허용" "tabberneue-desc": "문서 내에서 탭 만들기 허용"
} }

View file

@ -4,5 +4,5 @@
"Purodha" "Purodha"
] ]
}, },
"tabber-desc": "Määd et müjjelesch, en Sigge Tabs erin ze maache." "tabberneue-desc": "Määd et müjjelesch, en Sigge Tabs erin ze maache."
} }

View file

@ -4,5 +4,5 @@
"Bjankuloski06" "Bjankuloski06"
] ]
}, },
"tabber-desc": "Овозможува создавање на јазичиња во рамките на една страница" "tabberneue-desc": "Овозможува создавање на јазичиња во рамките на една страница"
} }

View file

@ -4,5 +4,5 @@
"C.R." "C.R."
] ]
}, },
"tabber-desc": "Permette 'e crià schede dint'a na paggena" "tabberneue-desc": "Permette 'e crià schede dint'a na paggena"
} }

View file

@ -4,5 +4,5 @@
"SPQRobin" "SPQRobin"
] ]
}, },
"tabber-desc": "Maakt het mogelijk om tabbladen binnen een pagina te maken" "tabberneue-desc": "Maakt het mogelijk om tabbladen binnen een pagina te maken"
} }

View file

@ -4,5 +4,5 @@
"BeginaFelicysym" "BeginaFelicysym"
] ]
}, },
"tabber-desc": "Pozwala na tworzenie zakładek na stronie" "tabberneue-desc": "Pozwala na tworzenie zakładek na stronie"
} }

View file

@ -5,5 +5,5 @@
"Dragonòt" "Dragonòt"
] ]
}, },
"tabber-desc": "A përmët ëd creé dle schede drinta a na pàgina" "tabberneue-desc": "A përmët ëd creé dle schede drinta a na pàgina"
} }

View file

@ -4,5 +4,5 @@
"Cainamarques" "Cainamarques"
] ]
}, },
"tabber-desc": "Permite a criação de abas dentro de uma página" "tabberneue-desc": "Permite a criação de abas dentro de uma página"
} }

View file

@ -4,5 +4,5 @@
"Shirayuki" "Shirayuki"
] ]
}, },
"tabber-desc": "{{desc|name=Tabber|url=http://www.mediawiki.org/wiki/Extension:Tabber}}" "tabberneue-desc": "{{desc|name=TabberNeue|url=http://www.mediawiki.org/wiki/Extension:TabberNeue}}"
} }

View file

@ -4,5 +4,5 @@
"Joetaras" "Joetaras"
] ]
}, },
"tabber-desc": "Permette de ccrejà le schede jndr'à 'na pàgene" "tabberneue-desc": "Permette de ccrejà le schede jndr'à 'na pàgene"
} }

View file

@ -4,5 +4,5 @@
"Lockal" "Lockal"
] ]
}, },
"tabber-desc": "Позволяет создавать вкладки внутри страницы" "tabberneue-desc": "Позволяет создавать вкладки внутри страницы"
} }

View file

@ -4,5 +4,5 @@
"පසිඳු කාවින්ද" "පසිඳු කාවින්ද"
] ]
}, },
"tabber-desc": "පිටු අතර ටැබයන් තැනීමට ඉඩ දෙයි" "tabberneue-desc": "පිටු අතර ටැබයන් තැනීමට ඉඩ දෙයි"
} }

View file

@ -4,5 +4,5 @@
"WikiPhoenix" "WikiPhoenix"
] ]
}, },
"tabber-desc": "Gör det möjligt att skapa flikar på en sida" "tabberneue-desc": "Gör det möjligt att skapa flikar på en sida"
} }

View file

@ -4,5 +4,5 @@
"AnakngAraw" "AnakngAraw"
] ]
}, },
"tabber-desc": "Nagpapahintulot upang makalikha ng mga laylay sa loob ng isang pahina" "tabberneue-desc": "Nagpapahintulot upang makalikha ng mga laylay sa loob ng isang pahina"
} }

View file

@ -4,5 +4,5 @@
"Ата" "Ата"
] ]
}, },
"tabber-desc": "Дозволяє створювати вкладки всередині сторінки" "tabberneue-desc": "Дозволяє створювати вкладки всередині сторінки"
} }

View file

@ -5,5 +5,5 @@
"Yfdyh000" "Yfdyh000"
] ]
}, },
"tabber-desc": "允许在页面内创建标签页" "tabberneue-desc": "允许在页面内创建标签页"
} }

View file

@ -4,5 +4,5 @@
"Justincheng12345" "Justincheng12345"
] ]
}, },
"tabber-desc": "容許於頁面內創建分頁" "tabberneue-desc": "容許於頁面內創建分頁"
} }

View file

@ -9,12 +9,12 @@
* @link https://www.mediawiki.org/wiki/Extension:Tabber * @link https://www.mediawiki.org/wiki/Extension:Tabber
**/ **/
namespace Tabber; namespace TabberNeue;
use Parser; use Parser;
use PPFrame; use PPFrame;
class TabberHooks { class TabberNeueHooks {
/** /**
* Sets up this extension's parser functions. * Sets up this extension's parser functions.
* *
@ -23,8 +23,7 @@ class TabberHooks {
* @return boolean true * @return boolean true
*/ */
public static function onParserFirstCallInit(Parser &$parser) { public static function onParserFirstCallInit(Parser &$parser) {
$parser->setHook("tabber", "Tabber\\TabberHooks::renderTabber"); $parser->setHook('tabber', 'TabberNeue\\TabberNeueHooks::renderTabber');
return true; return true;
} }
@ -39,18 +38,19 @@ class TabberHooks {
* @return string HTML * @return string HTML
*/ */
public static function renderTabber($input, array $args, Parser $parser, PPFrame $frame) { public static function renderTabber($input, array $args, Parser $parser, PPFrame $frame) {
$parser->getOutput()->addModules('ext.Tabber'); $parser->getOutput()->addModules('ext.tabberNeue');
$key = md5($input); $key = substr(md5($input), 0, 6);
$arr = explode("|-|", $input); $arr = explode("|-|", $input);
$htmlTabs = ''; $htmlTabs = '';
foreach ($arr as $tab) { foreach ($arr as $tab) {
$htmlTabs .= self::buildTab($tab, $parser, $frame); $htmlTabs .= self::buildTab($tab, $parser, $frame);
} }
$HTML = '<div id="tabber-' . $key . '" class="tabber">' . $htmlTabs . "</div>"; $html = '<div id="tabber-' . $key . '" class="tabber">' .
'<section class="tabber__section">' . $htmlTabs . "</section></div>";
return $HTML; return $html;
} }
/** /**
@ -73,10 +73,9 @@ class TabberHooks {
$tabBody = $parser->recursiveTagParse($tabBody, $frame); $tabBody = $parser->recursiveTagParse($tabBody, $frame);
$tab = ' $tab = '<article class="tabber__panel" title="' . htmlspecialchars($tabName) .
<div class="tabbertab" title="' . htmlspecialchars($tabName) . '"> '"><p>' . $tabBody . '</p></article>';
<p>' . $tabBody . '</p>
</div>';
return $tab; return $tab;
} }

View file

@ -1,75 +0,0 @@
(function($) {
$.fn.tabber = function() {
return this.each(function() {
// create tabs
var $this = $(this),
tabContent = $this.children('.tabbertab'),
nav = $('<ul>').addClass('tabbernav'),
loc;
tabContent.each(function() {
$(this).attr('data-hash', mw.util.escapeIdForAttribute(this.title));
var anchor = $('<a>').text(this.title).attr('title',this.title).attr('data-hash', $(this).attr('data-hash')).attr('href', '#');
$('<li>').append(anchor).appendTo(nav);
// Append a manual word break point after each tab
nav.append($('<wbr>'));
});
$this.prepend(nav);
/**
* Internal helper function for showing content
* @param {string} title to show, matching only 1 tab
* @return {bool} true if matching tab could be shown
*/
function showContent(title) {
var content = tabContent.filter('[data-hash="' + title + '"]');
if (content.length !== 1) { return false; }
tabContent.hide();
content.show();
nav.find('.tabberactive').removeClass('tabberactive');
nav.find('a[data-hash="' + title + '"]').parent().addClass('tabberactive');
return true;
}
// setup initial state
var tab = new mw.Uri(location.href).fragment;
if (tab === '' || !showContent(tab)) {
showContent(tabContent.first().attr('data-hash'));
}
// Respond to clicks on the nav tabs
nav.on('click', 'a', function(e) {
var title = $(this).attr('data-hash');
e.preventDefault();
if (history.pushState) {
history.pushState(null, null, '#' + title);
switchTab();
} else {
location.hash = '#' + title;
}
});
$(window).on('hashchange', function(event) {
switchTab();
});
function switchTab() {
var tab = new mw.Uri(location.href).fragment;
if (!tab.length) {
showContent(tabContent.first().attr('data-hash'));
}
if (nav.find('a[data-hash="'+tab+'"]').length) {
showContent(tab);
}
}
$this.addClass('tabberlive');
});
};
}(jQuery));
$(document).ready(function() {
$('.tabber').tabber();
});

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>
next
</title>
<path d="M7 1L5.6 2.5 13 10l-7.4 7.5L7 19l9-9z"/>
</svg>

After

Width:  |  Height:  |  Size: 207 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>
next
</title>
<path d="M4 10l9 9 1.4-1.5L7 10l7.4-7.5L13 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 206 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>
previous
</title>
<path d="M4 10l9 9 1.4-1.5L7 10l7.4-7.5L13 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 210 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>
previous
</title>
<path d="M7 1L5.6 2.5 13 10l-7.4 7.5L7 19l9-9z"/>
</svg>

After

Width:  |  Height:  |  Size: 211 B

175
modules/ext.tabberNeue.js Normal file
View file

@ -0,0 +1,175 @@
/**
* This needs a full-rewrite dropping jQuery and using ES6.
* But it will do for now.
*/
( function ( $ ) {
$.fn.tabber = function () {
// Load icons
mw.loader.load( 'ext.tabberNeue.icons' );
return this.each( function () {
// create tabs
var $this = $( this ),
key = $this.attr( 'id' ).substring( 7 ),
tabSection = $this.children( '.tabber__section' ),
tabPanel = tabSection.children( '.tabber__panel' ),
nav = $( '<nav>' ).addClass( 'tabber__nav' ),
header = $( '<header>' ).addClass( 'tabber__header' ),
arrowLeft = $( '<div>' ).addClass( 'tabber__header__prev' ),
arrowRight = $( '<div>' ).addClass( 'tabber__header__next' ),
hash;
nav.attr( 'role', 'tablist' );
tabPanel.each( function () {
hash = mw.util.escapeIdForAttribute( this.title ) + '-' + key;
$( this ).attr( 'id', hash );
$( this ).attr( 'role', 'tabpanel' );
$( this ).attr( 'aria-labelledby', 'tab-' + hash );
$( this ).attr( 'aria-hidden', 'true' );
var anchor = $( '<a>' ).text( this.title ).attr( 'title', this.title );
anchor.addClass( 'tabber__item' );
anchor.attr( 'role', 'tab' );
anchor.attr( 'href', '#' + hash );
anchor.attr( 'id', 'tab-' + hash );
anchor.attr( 'aria-controls', hash );
anchor.appendTo( nav );
} );
arrowLeft.appendTo( header );
nav.appendTo( header );
arrowRight.appendTo( header );
$this.prepend( header );
/**
* Internal helper function for showing panel
* @param {string} targetHash to show, matching only 1 tab
* @return {bool} true if matching tab could be shown
*/
function showPanel( targetHash ) {
const targetPanel = document.getElementById( targetHash ),
section = targetPanel.parentElement,
currentPanel = section.querySelector( '.tabber__panel--active' );
if ( currentPanel ) {
// jQuery
nav.find('.tabber__item--active').removeClass('tabber__item--active');
currentPanel.classList.remove( 'tabber__panel--active' );
currentPanel.setAttribute( 'aria-hidden', 'true' );
section.style.height = currentPanel.offsetHeight + 'px';
section.style.height = targetPanel.offsetHeight + 'px';
} else {
section.style.height = targetPanel.offsetHeight + 'px';
}
// Add active class to the tab item
nav.find( 'a[href="#' + targetHash + '"]' ).addClass( 'tabber__item--active' );
targetPanel.classList.add( 'tabber__panel--active' );
targetPanel.setAttribute( 'aria-hidden', 'false' );
// Scroll to tab
section.scrollLeft = targetPanel.offsetLeft;
}
function initButtons() {
const header = tabber.querySelector( '.tabber__header' ),
PREVCLASS = 'tabber__header--prev-visible',
NEXTCLASS = 'tabber__header--next-visible';
const scrollTabs = ( offset ) => {
const scrollLeft = tablist.scrollLeft + offset;
// Scroll to the start
if ( scrollLeft <= 0 ) {
tablist.scrollLeft = 0;
header.classList.remove( PREVCLASS );
header.classList.add( NEXTCLASS );
} else {
tablist.scrollLeft = scrollLeft;
// Scroll to the end
if ( scrollLeft + tablist.offsetWidth >= tablist.scrollWidth ) {
header.classList.remove( NEXTCLASS );
header.classList.add( PREVCLASS );
} else {
header.classList.add( NEXTCLASS );
header.classList.add( PREVCLASS );
}
}
}
const setupButtons = () => {
const isScrollable = ( tablist.scrollWidth > header.offsetWidth ) ? true : false;
if ( isScrollable ) {
const prevButton = header.querySelector( '.tabber__header__prev' ),
nextButton = header.querySelector( '.tabber__header__next' ),
scrollOffset = header.offsetWidth / 2;
// Just to add the right classes
scrollTabs( 0 );
prevButton.addEventListener( "click", () => {
scrollTabs( -scrollOffset );
}, false );
nextButton.addEventListener( "click", () => {
scrollTabs( scrollOffset );
}, false );
} else {
header.classList.remove( NEXTCLASS );
header.classList.remove( PREVCLASS );
}
}
setupButtons();
// Listen for window resize
window.addEventListener( 'resize', () => {
mw.util.debounce( 250, setupButtons() );
} );
}
function switchTab() {
var targetHash = new mw.Uri( location.href ).fragment;
if ( targetHash ) {
if ( nav.find( 'a[href="#' + targetHash + '"]' ).length ) {
showPanel( targetHash );
}
} else {
showPanel( tabPanel.first().attr( 'id' ) );
}
}
const tabber = document.getElementById( 'tabber-' + key ),
tablist = tabber.querySelector( '.tabber__nav' );
switchTab();
// Only run if client is not a touch device
if ( matchMedia( '(hover: hover)' ).matches ) {
initButtons( tabber );
}
$( window ).on( 'hashchange', function ( event ) {
switchTab();
} );
// Respond to clicks on the nav tabs
nav.on( 'click', 'a', function ( e ) {
var targetHash = $( this ).attr( 'href' ).substring( 1 );
// Prevent vertical scroll while maintaining the anchor behavior
e.preventDefault();
// Add hash to the end of the URL
history.pushState( null, null, '#' + targetHash );
showPanel( targetHash );
} );
$this.addClass( 'tabber--live' );
} );
};
}( jQuery ) );
$( document ).ready( function () {
$( '.tabber' ).tabber();
} );

162
modules/ext.tabberNeue.less Normal file
View file

@ -0,0 +1,162 @@
.tabber {
display: flex;
flex-direction: column;
/* establish primary containing box */
overflow: hidden;
position: relative;
&__header {
/* defend against <section> needing 100% */
flex-shrink: 0;
/* fixes cross browser quarks */
min-block-size: fit-content;
display: flex;
position: relative;
&__prev {
left: 0;
}
&__next {
right: 0;
}
&__prev,
&__next {
top: 0;
bottom: 0;
position: absolute;
width: 20px;
cursor: pointer;
display: none;
z-index: 1;
&:after {
content: "";
top: 0;
bottom: 0;
position: absolute;
width: inherit;
background-size: 14px;
background-position: center;
background-repeat: no-repeat;
}
}
&--prev-visible .tabber__nav {
mask-image: linear-gradient( 90deg, transparent, #000 20% );
-webkit-mask-image: linear-gradient( 90deg, transparent, #000 20% );
}
&--next-visible .tabber__nav {
mask-image: linear-gradient( 90deg, #000 80%, transparent );
-webkit-mask-image: linear-gradient( 90deg, #000 80%, transparent );
}
&--prev-visible.tabber__header--next-visible .tabber__nav {
mask-image: linear-gradient( 90deg, transparent, #000 20%, #000 80%, transparent );
-webkit-mask-image: linear-gradient( 90deg, transparent, #000 20%, #000 80%, transparent );
}
&--prev-visible .tabber__header__prev,
&--next-visible .tabber__header__next {
display: block;
}
}
&__header,
&__section {
/* prevent scroll chaining on x scroll */
overscroll-behavior-x: contain;
/* scrolling should snap children on x */
scroll-snap-type: x mandatory;
}
&__header,
&__section,
&__nav {
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
&__nav {
display: flex;
overflow: auto hidden;
box-shadow: inset 0 -1px 0 0 #a2a9b1;
}
&__item {
scroll-snap-align: start;
display: inline-flex;
align-items: center;
white-space: nowrap;
padding: 5px 12px;
color: #54595d;
text-decoration: none;
font-weight: bold;
&:visited {
color: #54595d;
}
&:hover,
&:active,
&:focus {
text-decoration: none;
}
&--active,
&--active:visited {
color: #36c;
box-shadow: inset 0 -2px 0 0 #36c;
}
}
&__section {
overflow: hidden;
block-size: 100%;
display: grid;
grid-auto-flow: column;
grid-auto-columns: 100%;
}
&__panel {
/* be pushy about consuming all space */
block-size: 100%;
scroll-snap-align: start;
overflow-y: auto;
overscroll-behavior-y: contain;
height: max-content;
}
}
@media (hover: hover) {
.tabber {
&__item {
&:hover {
color: #447ff5;
box-shadow: inset 0 -2px 0 0 #447ff5;
}
&:active {
color: #2a4b8d;
box-shadow: inset 0 -2px 0 0 #2a4b8d;
}
}
}
}
@media (prefers-reduced-motion: no-preference) {
.tabber {
&__header,
&__section,
&__nav {
scroll-behavior: smooth;
}
}
}