feat: add experimental support of Codex

It can be enabled by setting `$wgTabberNeueEnableLegacyMode` to `true`.
It is the first implementation and a WIP with numerous caveats:
- Nested Tabber does not work
- Tabber Transclue does not work
This commit is contained in:
alistair3149 2023-07-05 17:26:33 -04:00
parent 75ce280665
commit 89e90af034
No known key found for this signature in database
8 changed files with 174 additions and 20 deletions

View file

@ -27,11 +27,25 @@
"tabberneue-tabbertransclude-category"
],
"ResourceModules": {
"ext.tabberNeue": {
"ext.tabberNeue.codex": {
"packageFiles": [
"ext.tabberNeue.js",
"ext.tabberNeue.codex/ext.tabberNeue.codex.js",
"ext.tabberNeue.codex/App.vue"
],
"dependencies": [
"@wikimedia/codex",
"mediawiki.util"
],
"targets": [
"desktop",
"mobile"
]
},
"ext.tabberNeue.legacy": {
"packageFiles": [
"ext.tabberNeue.legacy/ext.tabberNeue.legacy.js",
{
"name": "config.json",
"name": "ext.tabberNeue.legacy/config.json",
"config": {
"enableAnimation": "TabberNeueEnableAnimation",
"updateLocationOnTabChange": "TabberNeueUpdateLocationOnTabChange"
@ -42,7 +56,7 @@
"error"
],
"styles": [
"ext.tabberNeue.less"
"ext.tabberNeue.legacy/ext.tabberNeue.legacy.less"
],
"dependencies": [
"mediawiki.Uri",
@ -117,6 +131,11 @@
},
"config_prefix": "wg",
"config": {
"TabberNeueEnableLegacyMode": {
"value": true,
"description": "Use legacy mode to render Tabber. It is required for nested Tabber.",
"public": true
},
"TabberNeueEnableAnimation": {
"value": false,
"description": "Enable or disable smooth scroll animation",

View file

@ -14,6 +14,7 @@ declare( strict_types=1 );
namespace MediaWiki\Extension\TabberNeue;
use MediaWiki\MediaWikiServices;
use Parser;
use PPFrame;
@ -31,17 +32,26 @@ class Tabber {
public static function parserHook( string $input, array $args, Parser $parser, PPFrame $frame ) {
$tabber = new Tabber();
$html = $tabber->render( $input, $parser, $frame );
if ( $input === null ) {
return;
}
// Critial rendering styles
// See ext.tabberNeue.inline.less
$style = sprintf(
'<style id="tabber-style">%s</style>',
'.client-js .tabber__header{height:2.6em;box-shadow:inset 0-1px 0 0;opacity:0.1}.client-js .tabber__header:after{position:absolute;width:16ch;height:0.5em;margin-top:1em;margin-left:0.75em;background:#000;border-radius:40px;content:""}.client-js .tabber__panel:not(:first-child){display:none}'
);
$parser->getOutput()->addHeadItem( $style, true );
$parser->getOutput()->addModules( [ 'ext.tabberNeue' ] );
$isLegacy = MediaWikiServices::getInstance()->getMainConfig()->get( 'TabberNeueEnableLegacyMode' );
if ( $isLegacy === true ) {
// Critial rendering styles
// See ext.tabberNeue.inline.less
$style = sprintf(
'<style id="tabber-style">%s</style>',
'.client-js .tabber__header{height:2.6em;box-shadow:inset 0-1px 0 0;opacity:0.1}.client-js .tabber__header:after{position:absolute;width:16ch;height:0.5em;margin-top:1em;margin-left:0.75em;background:#000;border-radius:40px;content:""}.client-js .tabber__panel:not(:first-child){display:none}'
);
$parser->getOutput()->addHeadItem( $style, true );
$parser->getOutput()->addModules( [ 'ext.tabberNeue.legacy' ] );
} else {
$parser->getOutput()->addModules( [ 'ext.tabberNeue.codex' ] );
}
$parser->addTrackingCategory( 'tabberneue-tabber-category' );
return $html;
}

View file

@ -34,17 +34,26 @@ class TabberTransclude {
public static function parserHook( string $input, array $args, Parser $parser, PPFrame $frame ) {
$tabberTransclude = new TabberTransclude();
$html = $tabberTransclude->render( $input, $parser, $frame );
if ( $input === null ) {
return;
}
// Critial rendering styles
// See ext.tabberNeue.inline.less
$style = sprintf(
'<style id="tabber-style">%s</style>',
'.tabber__header{height:2.6em;box-shadow:inset 0-1px 0 0;opacity:0.1}.tabber__header:after{position:absolute;width:16ch;height:0.5em;margin-top:1em;margin-left:0.75em;background:#000;border-radius:40px;content:""}.tabber__panel:not(:first-child){display:none}'
);
$parser->getOutput()->addHeadItem( $style, true );
$parser->getOutput()->addModules( [ 'ext.tabberNeue' ] );
$isLegacy = MediaWikiServices::getInstance()->getMainConfig()->get( 'TabberNeueEnableLegacyMode' );
if ( $isLegacy === true ) {
// Critial rendering styles
// See ext.tabberNeue.inline.less
$style = sprintf(
'<style id="tabber-style">%s</style>',
'.client-js .tabber__header{height:2.6em;box-shadow:inset 0-1px 0 0;opacity:0.1}.client-js .tabber__header:after{position:absolute;width:16ch;height:0.5em;margin-top:1em;margin-left:0.75em;background:#000;border-radius:40px;content:""}.client-js .tabber__panel:not(:first-child){display:none}'
);
$parser->getOutput()->addHeadItem( $style, true );
$parser->getOutput()->addModules( [ 'ext.tabberNeue.legacy' ] );
} else {
$parser->getOutput()->addModules( [ 'ext.tabberNeue.codex' ] );
}
$parser->addTrackingCategory( 'tabberneue-tabbertransclude-category' );
return $html;
}

View file

@ -0,0 +1,54 @@
<template>
<cdx-tabs v-model:active="currentTab" :framed="framed">
<cdx-tab
v-for="( tab, index ) in tabsData"
:key="index"
:name="tab.name"
:label="tab.label"
:disabled="tab.disabled"
v-html = "tab.content"
>
</cdx-tab>
</cdx-tabs>
</template>
<script>
const { defineComponent } = require('vue');
// Codex is available from ResourceLoader at runtime and is available without needing a build step.
const { CdxTabs, CdxTab } = require('@wikimedia/codex');
// @vue/component
module.exports = exports = defineComponent( {
name: 'App',
compatConfig: {
MODE: 3
},
compilerOptions: {
whitespace: 'condense'
},
components: {
CdxTabs,
CdxTab
},
props: {
tabberData: {
type: Object,
required: true
},
framed: {
type: Boolean,
default: false
}
},
data() {
return {
tabsData: this.tabberData.tabsData,
currentTab: this.tabberData.currentTab
};
},
mounted() {
console.log( this.$el );
this.$el.parentElement.classList.add( 'tabber--live' );
}
} );
</script>

View file

@ -0,0 +1,62 @@
const
Vue = require( 'vue' ),
App = require( './App.vue' );
/**
* @param {Element} tabber
* @return {void}
*/
function initApp( tabber ) {
const tabs = tabber.querySelectorAll( ':scope > .tabber__section > .tabber__panel' );
const tabberData = {
tabsData: [],
currentTab: ''
};
tabs.forEach( tab => {
const label = tab.getAttribute( 'data-title' );
if ( tab.querySelector( '.tabber' ) ) {
throw new Error( 'Nested Tabber is not supported in Codex mode, please use legacy mode instead.' );
}
tabberData.tabsData.push( {
name: mw.util.escapeIdForAttribute( label ),
label: label,
content: tab.innerHTML
} );
} );
tabberData.currentTab = tabberData.tabsData[0].name;
// @ts-ignore MediaWiki-specific function
Vue.createMwApp(
App, Object.assign( {
tabberData: tabberData
} )
)
.mount( tabber );
}
/**
* @param {Document} document
* @return {void}
*/
function main( document ) {
const tabbers = document.querySelectorAll( '.tabber:not( .tabber--live )' );
let sortedTabbers = [];
/* Nested Tabber children needed to be rendered before parents */
tabbers.forEach( tabber => {
if ( tabber.querySelector( '.tabber:not( .tabber--live )' ) ) {
sortedTabbers.push( tabber );
} else {
sortedTabbers.unshift( tabber );
}
} );
sortedTabbers.forEach( initApp );
}
main( document );