Add edit icons to sticky header
- Add edit icons. - Update data passed to sticky header, button templates. - Show/hide edit icons client-side based on ids in fixed header. - Disable sticky header when in Visual Editor mode. - Use Visual Editor hooks to toggle IntersectionObserver. - Remove extraneous js for setting offsets for other sticky elements (simplify by moving known sticky element th to css - follow up to https://gerrit.wikimedia.org/r/c/mediawiki/skins/Vector/+/722475/comment/7b8ab2db_cd5c7e78/). Bug: T289723 Change-Id: Ifbab2f1c4d716f8fc261e3d7fa35fc71c6065ec5
This commit is contained in:
parent
338a9eeb8b
commit
67e7eab714
|
@ -1,7 +1,7 @@
|
|||
[
|
||||
{
|
||||
"resourceModule": "skins.vector.styles.legacy",
|
||||
"maxSize": "7.8 kB"
|
||||
"maxSize": "7.9 kB"
|
||||
},
|
||||
{
|
||||
"resourceModule": "skins.vector.styles",
|
||||
|
|
|
@ -68,6 +68,30 @@ class SkinVector extends SkinMustache {
|
|||
'tabindex' => '-1',
|
||||
'class' => 'sticky-header-icon'
|
||||
];
|
||||
private const EDIT_VE_ICON = [
|
||||
'href' => '#',
|
||||
'id' => 'ca-ve-edit-sticky-header',
|
||||
'event' => 've-edit-sticky-header',
|
||||
'icon' => 'wikimedia-edit',
|
||||
'is-quiet' => true,
|
||||
'class' => 'sticky-header-icon'
|
||||
];
|
||||
private const EDIT_WIKITEXT_ICON = [
|
||||
'href' => '#',
|
||||
'id' => 'ca-edit-sticky-header',
|
||||
'event' => 'wikitext-edit-sticky-header',
|
||||
'icon' => 'wikimedia-wikiText',
|
||||
'is-quiet' => true,
|
||||
'class' => 'sticky-header-icon'
|
||||
];
|
||||
private const EDIT_PROTECTED_ICON = [
|
||||
'href' => '#',
|
||||
'id' => 'ca-viewsource-sticky-header',
|
||||
'event' => 've-edit-protected-sticky-header',
|
||||
'icon' => 'wikimedia-editLock',
|
||||
'is-quiet' => true,
|
||||
'class' => 'sticky-header-icon'
|
||||
];
|
||||
private const SEARCH_EXPANDING_CLASS = 'vector-search-box-show-thumbnail';
|
||||
private const STICKY_HEADER_ENABLED_CLASS = 'vector-sticky-header-enabled';
|
||||
|
||||
|
@ -353,7 +377,7 @@ class SkinVector extends SkinMustache {
|
|||
* @param array $searchBoxData
|
||||
* @return array
|
||||
*/
|
||||
private function getStickyHeaderData( $searchBoxData ) {
|
||||
private function getStickyHeaderData( $searchBoxData ): array {
|
||||
return [
|
||||
'data-primary-action' => !$this->shouldHideLanguages() ? $this->getULSButtonData() : null,
|
||||
'data-button-start' => [
|
||||
|
@ -365,7 +389,11 @@ class SkinVector extends SkinMustache {
|
|||
],
|
||||
'data-search' => $searchBoxData,
|
||||
'data-buttons' => [
|
||||
self::TALK_ICON, self::HISTORY_ICON, self::NO_ICON, self::NO_ICON
|
||||
self::TALK_ICON,
|
||||
self::HISTORY_ICON,
|
||||
self::EDIT_VE_ICON,
|
||||
self::EDIT_PROTECTED_ICON,
|
||||
self::EDIT_WIKITEXT_ICON
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"Element": "https://developer.mozilla.org/docs/Web/API/Element",
|
||||
"Event": "https://developer.mozilla.org/docs/Web/API/Event",
|
||||
"HTMLElement": "https://developer.mozilla.org/docs/Web/API/HTMLElement",
|
||||
"Node": "https://developer.mozilla.org/docs/Web/API/Node",
|
||||
"NodeList": "https://developer.mozilla.org/docs/Web/API/NodeList",
|
||||
"HTMLInputElement": "https://developer.mozilla.org/docs/Web/API/HTMLInputElement",
|
||||
"\"removeEventListener\"": "https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener",
|
||||
|
|
|
@ -7,8 +7,7 @@ var
|
|||
FIRST_HEADING_ID = 'firstHeading',
|
||||
USER_MENU_ID = 'p-personal',
|
||||
VECTOR_USER_LINKS_SELECTOR = '.vector-user-links',
|
||||
SEARCH_TOGGLE_SELECTOR = '.vector-sticky-header-search-toggle',
|
||||
OTHER_STICKY_ELEMENT_SELECTORS = '.charts-stickyhead th';
|
||||
SEARCH_TOGGLE_SELECTOR = '.vector-sticky-header-search-toggle';
|
||||
|
||||
/**
|
||||
* Copies attribute from an element to another.
|
||||
|
@ -48,6 +47,23 @@ function makeNodeTrackable( node ) {
|
|||
suffixStickyAttribute( node, 'data-event-name' );
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {null|HTMLElement|Node} node
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
function toHTMLElement( node ) {
|
||||
// @ts-ignore
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} node
|
||||
*/
|
||||
function removeNode( node ) {
|
||||
toHTMLElement( node.parentNode ).removeChild( node );
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sticky header icons functional for modern Vector.
|
||||
*
|
||||
|
@ -77,6 +93,76 @@ function prepareIcons( header, history, talk ) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render sticky header edit or protected page icons for modern Vector.
|
||||
*
|
||||
* @param {HTMLElement} header
|
||||
* @param {HTMLElement|null} primaryEdit
|
||||
* @param {boolean} isProtected
|
||||
* @param {HTMLElement|null} secondaryEdit
|
||||
*/
|
||||
function prepareEditIcons(
|
||||
header,
|
||||
primaryEdit,
|
||||
isProtected,
|
||||
secondaryEdit
|
||||
) {
|
||||
var
|
||||
primaryEditSticky = toHTMLElement(
|
||||
header.querySelector(
|
||||
'#ca-ve-edit-sticky-header'
|
||||
)
|
||||
),
|
||||
protectedSticky = toHTMLElement(
|
||||
header.querySelector(
|
||||
'#ca-viewsource-sticky-header'
|
||||
)
|
||||
),
|
||||
wikitextSticky = toHTMLElement(
|
||||
header.querySelector(
|
||||
'#ca-edit-sticky-header'
|
||||
)
|
||||
);
|
||||
|
||||
if ( !primaryEdit ) {
|
||||
removeNode( protectedSticky );
|
||||
removeNode( wikitextSticky );
|
||||
removeNode( primaryEditSticky );
|
||||
return;
|
||||
} else if ( isProtected ) {
|
||||
removeNode( wikitextSticky );
|
||||
removeNode( primaryEditSticky );
|
||||
copyAttribute( primaryEdit, protectedSticky, 'href' );
|
||||
copyAttribute( primaryEdit, protectedSticky, 'title' );
|
||||
} else {
|
||||
removeNode( protectedSticky );
|
||||
copyAttribute( primaryEdit, primaryEditSticky, 'href' );
|
||||
copyAttribute( primaryEdit, primaryEditSticky, 'title' );
|
||||
if ( secondaryEdit ) {
|
||||
copyAttribute( secondaryEdit, wikitextSticky, 'href' );
|
||||
copyAttribute( secondaryEdit, wikitextSticky, 'title' );
|
||||
} else {
|
||||
removeNode( wikitextSticky );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if element is in viewport.
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isInViewport( element ) {
|
||||
var rect = element.getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= ( window.innerHeight || document.documentElement.clientHeight ) &&
|
||||
rect.right <= ( window.innerWidth || document.documentElement.clientWidth )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sticky header functional for modern Vector.
|
||||
*
|
||||
|
@ -136,13 +222,42 @@ function makeStickyHeaderFunctional(
|
|||
document.querySelector( '#ca-talk a' )
|
||||
);
|
||||
|
||||
// Apply offset for other sticky elements on page if applicable.
|
||||
var otherStickyElements = document.querySelectorAll( OTHER_STICKY_ELEMENT_SELECTORS );
|
||||
Array.prototype.forEach.call( otherStickyElements, function ( el ) {
|
||||
el.classList.add( 'mw-sticky-header-element' );
|
||||
} );
|
||||
var veEdit = document.querySelector( '#ca-ve-edit a' );
|
||||
var ceEdit = document.querySelector( '#ca-edit a' );
|
||||
var protectedEdit = document.querySelector( '#ca-viewsource a' );
|
||||
var isProtected = !!protectedEdit;
|
||||
var primaryEdit = protectedEdit || ( veEdit || ceEdit );
|
||||
var secondaryEdit = veEdit ? ceEdit : null;
|
||||
|
||||
prepareEditIcons(
|
||||
header,
|
||||
toHTMLElement( primaryEdit ),
|
||||
isProtected,
|
||||
toHTMLElement( secondaryEdit )
|
||||
);
|
||||
|
||||
stickyObserver.observe( stickyIntersection );
|
||||
|
||||
// When Visual Editor is activated, hide sticky header.
|
||||
mw.hook( 've.activationComplete' ).add( function () {
|
||||
// eslint-disable-next-line mediawiki/class-doc
|
||||
header.classList.remove( STICKY_HEADER_VISIBLE_CLASS );
|
||||
stickyObserver.unobserve( stickyIntersection );
|
||||
} );
|
||||
|
||||
// When Visual Editor is deactivated, by cliking "read" tab at top of page, show sticky header.
|
||||
mw.hook( 've.deactivationComplete' ).add( function () {
|
||||
stickyObserver.observe( stickyIntersection );
|
||||
} );
|
||||
|
||||
// After saving edits, re-apply the sticky header if the target is not in the viewport.
|
||||
mw.hook( 'postEdit.afterRemoval' ).add( function () {
|
||||
if ( !isInViewport( stickyIntersection ) ) {
|
||||
// eslint-disable-next-line mediawiki/class-doc
|
||||
header.classList.add( STICKY_HEADER_VISIBLE_CLASS );
|
||||
stickyObserver.observe( stickyIntersection );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,31 +56,26 @@
|
|||
//
|
||||
// Layout
|
||||
//
|
||||
&-start {
|
||||
&-start,
|
||||
&-end,
|
||||
&-icons,
|
||||
&-context-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&-end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&-start {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
//
|
||||
// Components
|
||||
//
|
||||
&-icons,
|
||||
&-context-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
margin: 0 15px;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
&-context-bar {
|
||||
border-left: 1px solid #c8c8c8;
|
||||
margin: 0 15px;
|
||||
padding-left: 30px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-context-bar-primary {
|
||||
|
@ -147,7 +142,8 @@
|
|||
|
||||
// T289817 Override other sticky element offsets to ensure that other
|
||||
// sticky elements (i.e. table headers) appear below the sticky header.
|
||||
.mw-sticky-header-element {
|
||||
.mw-sticky-header-element,
|
||||
.charts-stickyhead th {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
top: @height-sticky-header !important;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue