feat(Echo): upgrade Echo buttons for consistency

This is similiar to how it is handled in Vector.
We are forcing the Echo button to render in the same way as it was initially.
This took me so many hours sigh.
This commit is contained in:
alistair3149 2024-07-05 20:37:52 -04:00
parent 251109c712
commit fdc6eaee20
No known key found for this signature in database
8 changed files with 127 additions and 90 deletions

View file

@ -356,8 +356,9 @@ class SkinHooks implements
$linkClass = $item['link-class'] ?? [];
$newLinkClass = [
// Allows Echo to react to clicks
'mw-echo-notification-badge-nojs',
'citizen-header__button'
'citizen-echo-notification-badge',
'citizen-header__button',
'mw-echo-notification-badge-nojs'
];
if ( in_array( 'mw-echo-unseen-notifications', $linkClass ) ) {
$newLinkClass[] = 'mw-echo-unseen-notifications';

View file

@ -0,0 +1,107 @@
/**
* Originally based on Vector
*
* Upgrades Echo for icon consistency.
* Undos work inside Echo to replace our button.
*
* TODO: Switch to mw.hook( 'ext.echo.NotificationBadgeWidget.onInitialize' ) when we drop 1.39 support
*/
function init() {
if ( document.querySelectorAll( '#pt-notifications-alert a, #pt-notifications-notice a' ).length !== 2 ) {
return;
}
const notifications = document.getElementById( 'p-notifications' );
// Clone the icons so we can insert it back afterwards
const alertIcon = notifications.querySelector( '#pt-notifications-alert > a > .citizen-ui-icon' ).cloneNode();
const noticeIcon = notifications.querySelector( '#pt-notifications-notice > a > .citizen-ui-icon' ).cloneNode();
// When the Echo button is clicked, all of its children are reset back to the initial state.
// This will re-upgrade the children of the Echo button
const callChildSupportServices = ( anchor ) => {
const badge = anchor.parentElement;
// Wrap label in a span
const label = document.createElement( 'span' );
label.textContent = anchor.textContent;
anchor.replaceChildren( label );
// Add icon span back
if ( badge.id === 'pt-notifications-alert' ) {
anchor.prepend( alertIcon );
anchor.classList.remove( 'oo-ui-icon-bell' );
} else if ( badge.id === 'pt-notifications-notice' ) {
anchor.prepend( noticeIcon );
anchor.classList.remove( 'oo-ui-icon-tray' );
}
};
// Upgrade the Echo badge
// This only needs to be run once at the Echo button init
const setupFosterHome = ( badge, anchor ) => {
badge.classList.add( 'mw-list-item' );
anchor.classList.remove( 'mw-echo-notifications-badge' );
anchor.classList.add( 'citizen-header__button', 'citizen-echo-notification-badge' );
callChildSupportServices( anchor );
};
// Whenever Echo kicks its children out from the button, undo what Echo did.
const abuseObserver = new MutationObserver( ( mutations ) => {
for ( const mutation of mutations ) {
if ( mutation.type === 'childList' ) {
const removedNodes = mutation.removedNodes;
if ( removedNodes.length === 0 ) {
return;
}
for ( const removedNode of removedNodes ) {
if ( !( removedNode instanceof HTMLSpanElement ) || !removedNode.classList.contains( 'citizen-ui-icon' ) ) {
return;
}
const anchor = mutation.target;
callChildSupportServices( anchor );
}
}
}
} );
// Observe for Echo button init, it will only happen once per icon (so twice)
let initObserved = 0;
const initObserver = new MutationObserver( ( mutations ) => {
for ( const mutation of mutations ) {
// All Echo buttons are observed by abuseObserver, disconnect observer.
if ( initObserved >= 2 ) {
initObserver.disconnect();
}
if ( mutation.type === 'childList' ) {
const addedNodes = mutation.addedNodes;
if ( addedNodes.length === 0 ) {
return;
}
for ( const addedNode of addedNodes ) {
if ( !addedNode.classList.contains( 'mw-echo-ui-notificationBadgeButtonPopupWidget' ) ) {
return;
}
const anchor = addedNode.firstElementChild;
// Upgrade the badge immediately before Echo kicks its children out
setupFosterHome( addedNode, anchor );
// Observe Echo button
abuseObserver.observe(
anchor,
{
childList: true,
subtree: true
}
);
initObserved++;
}
}
}
} );
initObserver.observe(
notifications,
{
childList: true,
subtree: true
}
);
}
module.exports = init;

View file

@ -60,6 +60,7 @@ function initBodyContent( bodyContent ) {
function main( window ) {
const
config = require( './config.json' ),
echo = require( './echo.js' ),
search = require( './search.js' ),
dropdown = require( './dropdown.js' ),
stickyHeader = require( './stickyHeader.js' ),
@ -67,6 +68,7 @@ function main( window ) {
share = require( './share.js' );
enableCssAnimations( window.document );
echo();
search.init( window );
dropdown.init();
stickyHeader.init();

View file

@ -132,7 +132,7 @@
}
// Echo badge styles do not load before init in 1.39
.mw-echo-notification-badge-nojs {
.citizen-echo-notification-badge {
.citizen-ui-icon + span {
.sr-only;
}

View file

@ -102,10 +102,6 @@
&-empty {
display: none !important;
}
a::after {
filter: var( --filter-invert );
}
}
// HACK: Restore view button on revision page (#845)

View file

@ -77,7 +77,7 @@
// Language counter badge
.citizen-page-languages {
> .citizen-dropdown-summary {
.citizen-dropdown-summary {
position: relative; // So that the badge doesn't take a stroll to the far left
&::after {

View file

@ -158,6 +158,7 @@
"callback": "MediaWiki\\Skins\\Citizen\\Hooks\\ResourceLoaderHooks::getCitizenResourceLoaderConfig"
},
"resources/skins.citizen.scripts/dropdown.js",
"resources/skins.citizen.scripts/echo.js",
"resources/skins.citizen.scripts/lastModified.js",
"resources/skins.citizen.scripts/overflowElements.js",
"resources/skins.citizen.scripts/scrollObserver.js",

View file

@ -8,87 +8,17 @@
* Date: 2024-05-06
*/
.mw-echo-notifications-badge {
#pt-notifications-alert &,
#pt-notifications-notice & {
top: 0;
width: var( --header-button-size );
height: var( --header-button-size );
margin: 0;
background-color: transparent; // reset mw-list-item style from Citizen
filter: var( --filter-invert );
background-repeat: no-repeat;
background-position: center;
background-size: var( --header-icon-size );
border-radius: var( --border-radius-base );
opacity: var( --opacity-icon-base );
&:hover {
opacity: var( --opacity-icon-base--hover );
}
&:active {
opacity: var( --opacity-icon-base--selected );
}
&:focus {
box-shadow: 0 0 0 1px var( --color-inverted-fixed ), 0 0 0 3px var( --color-progressive );
opacity: var( --opacity-icon-base--selected );
&::after {
border-color: var( --color-progressive );
}
}
&::after {
top: 0;
font-size: 0.65rem;
background-color: var( --color-destructive );
// Re-invert indicator color
filter: var( --filter-invert );
border-color: var( --color-destructive );
border-radius: var( --border-radius-pill );
}
&-dimmed {
opacity: var( --opacity-icon-base--disabled );
}
&.mw-echo-notifications-badge-all-read {
opacity: var( --opacity-icon-base );
&:hover {
opacity: var( --opacity-icon-base--hover );
}
&:active {
opacity: var( --opacity-icon-base--selected );
}
}
}
&.oo-ui-flaggedElement-unseen,
&.mw-echo-unseen-notifications {
#pt-notifications-alert &::after {
background-color: var( --color-destructive );
}
#pt-notifications-notice &::after {
background-color: var( --color-destructive );
}
}
}
// Add quiet background hover states
#pt-notifications-alert,
#pt-notifications-notice {
border-radius: var( --border-radius-base );
&:hover {
background-color: var( --background-color-button-quiet--hover );
}
&:active {
background-color: var( --background-color-button-quiet--active );
.citizen-echo-notification-badge {
&:not( [ data-counter-text='1' ] )::after {
position: absolute;
top: var( --space-xxs );
right: var( --space-xxs );
box-sizing: content-box;
width: 0.5rem;
height: 0.5rem;
content: '';
background: var( --color-progressive );
border: 4px solid var( --color-surface-0 );
border-radius: 100%;
}
}