Render talk page as a tab instead of an overlay

Following up on Jon's POC, this will get rid of the talk board component in
favor of linking to the server rendered talk page.

Additional Changes:

* Cleaned up talk selenium tests. Removed talk_steps.rb which doesn't appear
to be used anymore.

* Changed talk add button classes to a single class

* Moved "Add discussion" button to postheadinghtml per design mock

* Added  "...talk-explained", "...talk-explained-empty" messages to
postheadinghtml per design mock

* Due to undesirable jumps in window scroll caused by the section anchor
& Toggler.js code when opening the TalkSectionOverlay (read fixme in
code), a Promise is always returned from OverlayManager route to reset
the scroll position to the top when the section overlay is opened.

* Moved
"mobile-frontend-talk-fullpage",
"mobile-frontend-talk-reply-success",
"mobile-frontend-talk-topic-feedback",
"mobile-frontend-talk-explained"
"mobile-frontend-talk-explained-empty"
messages to minerva as minerva is
the one who initiates those messages now.

* Limited $talk selector to only `.talk` elements since amc talk tab
does not need to be targeted

* After saving a reply from TalkSectionOverlay, the DOM that is not
part of the overlay becomes out of sync since a new reply was created.
To get around this, an `onSaveComplete` callback was passed (similar to
the TalkSectionAddOverlay) to execute a full page refresh. Although this
is clunky, it is the easiest way to resync.

Bug: T230695
Depends-On: I80201394fd7015db6700446142b0b4b20829f12e
Change-Id: I243f1193bce0da9fa710fc3b5379f90b2d079680
This commit is contained in:
Nicholas Ray 2019-10-17 16:46:07 -06:00
parent c8df4cad7b
commit 2d579183c9
13 changed files with 469 additions and 271 deletions

View file

@ -55,6 +55,11 @@
"minerva-page-actions-wikibase": "{{WBREPONAME}} item",
"minerva-page-actions-language-switcher": "Languages",
"minerva-page-actions-history": "History",
"minerva-talk-explained": "Active discussions",
"minerva-talk-explained-empty": "There are no discussions on this page.",
"minerva-talk-full-page": "Read as wiki page",
"minerva-talk-topic-feedback": "New topic added to talk page!",
"minerva-talk-reply-success": "Your reply was saved to the talk page.",
"skinname-minerva": "MinervaNeue",
"minerva-skin-desc": "A responsive mobile first skin",
"skin-minerva-issue-learn-more": "Learn more",

View file

@ -15,7 +15,6 @@
},
"minerva-last-modified-date": "Text that displays the date the page was last modified. Parameters:\n* $1 - date\n* $2 - time\n{{Related|Mobile-frontend-last-modified}}",
"minerva-watchlist-cta": "Appears when you click watchlist icon when not logged in. First link is to login form, 2nd to sign up form. Links are created in JavaScript.",
"minerva-talk-add-topic": "Label for button which shows at bottom of talk pages in mobile view prompting addition of topic",
"mobile-frontend-cookies-required": "Error message shown when user attempts to switch site modes and cookies are not enabled.",
"mobile-frontend-editor-edit": "Caption for the link showing edit form. (In the imperative mood)\n{{Identical|Edit}}",
"minerva-download": "Caption for the download button (in the imperative mood).\n{{Identical|Download}}",
@ -66,6 +65,12 @@
"minerva-page-actions-wikibase": "In the secondary page menu, the wikibase item button label",
"minerva-page-actions-language-switcher": "In the secondary page menu, the language switcher item button label\n{{Identical|Languages}}",
"minerva-page-actions-history": "In the secondary page menu, a short label for the history link",
"minerva-talk-add-topic": "Label for button which shows at top of talk pages in mobile view prompting addition of topic",
"minerva-talk-explained": "Explains that the user is seeing talk page headings",
"minerva-talk-explained-empty": "Explains why the list is empty.",
"minerva-talk-full-page": "Used as label for link to show the full talk page.",
"minerva-talk-topic-feedback": "Feedback when a topic has been added to talk page.",
"minerva-talk-reply-success": "Toast message when you have saved a reply successfully.",
"skinname-minerva": "{{name}}",
"minerva-skin-desc": "{{desc|name=Minerva Neue|url=https://www.mediawiki.org/wiki/Skin:Minerva_Neue|what=skin}}",
"skin-minerva-issue-learn-more": "Label for link that allows expanding of ambox issue templates.",

View file

@ -483,6 +483,16 @@ class SkinMinerva extends SkinTemplate {
return Html::element( 'div', $attrs, $tagline );
}
/**
* @return bool Whether or not current title is a Talk page with the default
* action ('view')
*/
private function isTalkPageWithViewAction() {
$title = $this->getTitle();
return $title->isTalkPage() && Action::getActionName( $this->getContext() ) === "view";
}
/**
* Returns the HTML representing the heading.
* @return string HTML for header
@ -497,6 +507,30 @@ class SkinMinerva extends SkinTemplate {
return Html::rawElement( 'h1', [ 'id' => 'section_0' ], $heading );
}
private function getTalkPagePostHeadingHtml() {
$sectionCount = 0;
$title = $this->getTitle();
if ( $this->canUseWikiPage() ) {
$wikiPage = $this->getWikiPage();
$parserOptions = $wikiPage->makeParserOptions( $this->getContext() );
$parserOutput = $wikiPage->getParserOutput( $parserOptions );
$sectionCount = $parserOutput ? count( $parserOutput->getSections() ) : 0;
}
$message = $sectionCount > 0 ? wfMessage( 'minerva-talk-explained' )
: wfMessage( 'minerva-talk-explained-empty' );
$html = Html::element( 'div', [ 'class' => 'minerva-talk-content-explained' ], $message->text() );
$addTopicButton = $this->getTalkButton( $title, wfMessage(
'minerva-talk-add-topic' )->text(), true );
if ( $this->getPermissions()->isTalkAllowed() ) {
$html = Html::element( 'a', $addTopicButton['attributes'], $addTopicButton['label'] ) . $html;
}
return $html;
}
/**
* Create and prepare header and footer content
* @param BaseTemplate $tpl
@ -506,6 +540,7 @@ class SkinMinerva extends SkinTemplate {
$user = $this->getUser();
$out = $this->getOutput();
$tpl->set( 'taglinehtml', $this->getTaglineHtml() );
if ( $this->getUserPageHelper()->isUserPage() &&
// when overflow menu is visible, we don't need to build secondary options
// as most of options are visible on Toolbar/Overflow menu
@ -536,6 +571,10 @@ class SkinMinerva extends SkinTemplate {
$pageTitle = '';
}
$out->setPageTitle( $pageTitle );
} elseif ( $this->isTalkPageWithViewAction() ) {
// We only want the simplified talk page to show for the view action of the
// talk (e.g. not history action)
$tpl->set( 'postheadinghtml', $this->getTalkPagePostHeadingHtml() );
}
if ( $this->canUseWikiPage() && $this->getWikiPage()->exists() ) {
@ -593,15 +632,15 @@ class SkinMinerva extends SkinTemplate {
/**
* Returns an array with details for a talk button.
* @param Title $talkTitle Title object of the talk page
* @param array $talkButton Array with data of desktop talk button
* @param string $label Button label
* @param bool $addSection (optional) when added the talk button will render
* as an add topic button. Defaults to false.
* @return array
*/
protected function getTalkButton( $talkTitle, $talkButton, $addSection = false ) {
protected function getTalkButton( $talkTitle, $label, $addSection = false ) {
if ( $addSection ) {
$params = [ 'action' => 'edit', 'section' => 'new' ];
$className = 'talk continue add';
$className = 'minerva-talk-add-button ' . MinervaUI::buttonClass( 'progressive', 'button' );
} else {
$params = [];
$className = 'talk';
@ -613,7 +652,7 @@ class SkinMinerva extends SkinTemplate {
'data-title' => $talkTitle->getFullText(),
'class' => $className,
],
'label' => $talkButton['text'],
'label' => $label,
];
}
@ -645,16 +684,16 @@ class SkinMinerva extends SkinTemplate {
$languagesHelper = $services->getService( 'Minerva.LanguagesHelper' );
$buttons = [];
// always add a button to link to the talk page
// in beta it will be the entry point for the talk overlay feature,
// in stable it will link to the wikitext talk page
// it will link to the wikitext talk page
$title = $this->getTitle();
$subjectPage = Title::newFromLinkTarget( $namespaceInfo->getSubjectPage( $title ) );
$talkAtBottom = !$this->skinOptions->get( SkinOptions::TALK_AT_TOP ) ||
$subjectPage->isMainPage() || $title->isTalkPage();
$namespaces = $tpl->data['content_navigation']['namespaces'];
if ( !$this->getUserPageHelper()->isUserPage()
&& $this->getPermissions()->isTalkAllowed() && $talkAtBottom
$subjectPage->isMainPage();
if ( !$this->getUserPageHelper()->isUserPage() &&
$this->getPermissions()->isTalkAllowed() && $talkAtBottom &&
!$this->isTalkPageWithViewAction()
) {
$namespaces = $tpl->data['content_navigation']['namespaces'];
// FIXME [core]: This seems unnecessary..
$subjectId = $title->getNamespaceKey( '' );
$talkId = $subjectId === 'main' ? 'talk' : "{$subjectId}_talk";
@ -663,12 +702,7 @@ class SkinMinerva extends SkinTemplate {
$talkButton = $namespaces[$talkId];
$talkTitle = Title::newFromLinkTarget( $namespaceInfo->getTalkPage( $title ) );
if ( $title->isTalkPage() ) {
$talkButton['text'] = wfMessage( 'minerva-talk-add-topic' );
$buttons['talk'] = $this->getTalkButton( $title, $talkButton, true );
} else {
$buttons['talk'] = $this->getTalkButton( $talkTitle, $talkButton );
}
$buttons['talk'] = $this->getTalkButton( $talkTitle, $talkButton['text'] );
}
}
@ -794,6 +828,13 @@ class SkinMinerva extends SkinTemplate {
$classes .= ' minerva--history-page-action-enabled';
}
if (
// Class is used when page actions is modified to contain more elements
$this->isTalkPageWithViewAction()
) {
$classes .= ' skin-minerva--talk-simplified';
}
$bodyAttrs[ 'class' ] .= ' ' . $classes;
}
@ -823,7 +864,10 @@ class SkinMinerva extends SkinTemplate {
} elseif ( $this->getUserPageHelper()->isUserPage() ) {
$styles[] = 'skins.minerva.userpage.styles';
$styles[] = 'skins.minerva.userpage.icons';
} elseif ( $this->isTalkPageWithViewAction() ) {
$styles[] = 'skins.minerva.talk.styles';
}
if ( $this->getUser()->isLoggedIn() ) {
$styles[] = 'skins.minerva.loggedin.styles';
$styles[] = 'skins.minerva.icons.loggedin';

View file

@ -3,170 +3,284 @@
*/
module.exports = function ( mobile ) {
var
SKIN_MINERVA_TALK_SIMPLIFIED_CLASS = 'skin-minerva--talk-simplified',
toast = mobile.toast,
currentPage = mobile.currentPage(),
loader = mobile.rlModuleLoader,
loadingOverlay = mobile.loadingOverlay,
eventBus = mobile.eventBusSingleton,
PageGateway = mobile.PageGateway,
api = new mw.Api(),
gateway = new PageGateway( api ),
// eslint-disable-next-line no-jquery/no-global-selector
$talk = $( '.talk, [rel="discussion"]' ),
// use the plain return value here - T128273
title = $talk.attr( 'data-title' ),
overlayManager = mobile.OverlayManager.getSingleton(),
// FIXME: This dependency shouldn't exist
skin = mobile.Skin.getSingleton(),
inTalkNamespace = false,
pageTitle, talkTitle, talkNs, pageNs;
// T127190
if ( title ) {
title = decodeURIComponent( title );
}
// sanity check: the talk namespace needs to have the next higher integer
// of the page namespace (the api should add topics only to the talk page of the current
// page)
// (https://www.mediawiki.org/wiki/Manual:Using_custom_namespaces#Creating_a_custom_namespace)
// The method to get associated namespaces will change later (maybe), see T487
pageTitle = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) );
talkTitle = title ? mw.Title.newFromText( title ) : pageTitle.getTalkPage();
// Check that there is a valid page and talk title
if ( !pageTitle || !talkTitle ||
// the talk link points to something other than the current page
// so we chose to leave this as a normal link
pageTitle.getMainText() !== talkTitle.getMainText() ) {
return;
}
talkNs = talkTitle.getNamespaceId();
pageNs = pageTitle.getNamespaceId();
inTalkNamespace = talkNs === pageNs;
if ( pageNs + 1 !== talkNs && !inTalkNamespace ) {
return;
}
talkTitle = currentPage.titleObj.getTalkPage().getPrefixedText();
/**
* Render a talk overlay for a given section
* @param {string} id (a number e.g. '1' or the string 'new')
* Render a type of talk overlay
* @param {string} className name of talk overlay to create
* @param {Object} talkOptions
* @return {Overlay}
* @return {Overlay|JQuery.Promise}
*/
function talkSectionOverlay( id, talkOptions ) {
function createOverlay( className, talkOptions ) {
// eslint-disable-next-line no-restricted-properties
var M = mw.mobileFrontend;
if ( id === 'new' ) {
return new ( M.require( 'mobile.talk.overlays/TalkSectionAddOverlay' ) )( talkOptions );
}
return new ( M.require( 'mobile.talk.overlays/TalkSectionOverlay' ) )( talkOptions );
return mw.loader.getState( 'mobile.talk.overlays' ) === 'ready' ?
new ( M.require( 'mobile.talk.overlays/' + className ) )( talkOptions ) :
// otherwise pull it from ResourceLoader async
loader.loadModule( 'mobile.talk.overlays' ).then( function () {
return new ( M.require( 'mobile.talk.overlays/' + className ) )( talkOptions );
} );
}
overlayManager.add( /^\/talk\/?(.*)$/, function ( id ) {
var title = talkTitle.toText(),
talkOptions = {
api: api,
title: title,
onSaveComplete: function () {
gateway.invalidatePage( title );
// navigate back. the overlay is done with so close it
overlayManager.router.back();
try {
overlayManager.replaceCurrent(
mobile.talk.overlay( title, gateway )
);
overlayManager.router.navigateTo( null, {
// This should be defined in Minerva.
path: '#/talk',
useReplaceState: true
} );
} catch ( e ) {
// the user came directly - there is no overlay to replace
// so no overlay to refresh
}
mw.notify( mw.msg( 'mobile-frontend-talk-topic-feedback' ) );
},
// T184273 using `currentPage` because 'wgPageName'
// contains underscores instead of spaces.
currentPageTitle: currentPage.title,
licenseMsg: skin.getLicenseMsg(),
eventBus: eventBus,
id: id
};
// talk case
if ( id ) {
// If the module is already loaded return it instantly and synchronously.
// this avoids a flash of
// content when transitioning from mobile.talk.overlay to this overlay (T221978)
return mw.loader.getState( 'mobile.talk.overlays' ) === 'ready' ?
talkSectionOverlay( id, talkOptions ) :
// otherwise pull it from ResourceLoader async
loader.loadModule( 'mobile.talk.overlays' ).then( function () {
return talkSectionOverlay( id, talkOptions );
} );
} else {
return mobile.talk.overlay( title, gateway );
}
} );
function changeHash() {
// eslint-disable-next-line no-jquery/no-class-state
if ( $talk.hasClass( 'add' ) ) {
window.location.hash = '#/talk/new';
} else {
window.location.hash = '#/talk';
}
function removeTalkSectionListeners() {
// eslint-disable-next-line no-jquery/no-global-selector
$( '.section-heading' ).each( function () {
var $heading = $( this );
$heading.off( 'click.talkSectionOverlay' );
} );
}
/**
* @param {JQuery.Element} $heading
* @param {JQuery.Element} $headline
* @param {string} sectionId
* @return {JQuery.Promise} A promise that rejects if simplified mode is off.
* A promise that resolves to the TalkSectionOverlay otherwise (unless a
* network error occurs).
*/
function createTalkSectionOverlay( $heading, $headline, sectionId ) {
if ( !isSimplifiedViewEnabled() ) {
// If the simplified view is not enabled, we don't want to show the
// talk section overlay (e.g. when user clicks on a link in TOC)
return mobile.util.Deferred().reject();
}
// FIXME: Yes, this is hacky. Async code is needed to deal with the
// overlay opening in a scroll position that is not at the top. What
// causes this scroll to occur is code from Toggler.js which also
// listens to hash changes and causes the window to scroll to the top of
// the clicked on section after the overlay is open. We should probably
// stop the Toggler code from executing at all when the simplified view
// is enabled to prevent this code from happening, but MobileFrontend
// currently initializes the toggler code in mobile.init.js.
//
// https://github.com/wikimedia/mediawiki-extensions-MobileFrontend/blob/5c646a9881a787215b7d77c1d8b6b9126f302697/src/mobile.startup/Toggler.js#L216
return mobile.util.Deferred().resolve().then(
function () {
return createOverlay( 'TalkSectionOverlay', {
id: sectionId,
section: new mobile.Section( {
line: $headline.text(),
text: $heading.next().html()
} ),
// FIXME: Replace this api param with onSaveComplete
api: api,
title: talkTitle,
// T184273 using `currentPage` because 'wgPageName'
// contains underscores instead of spaces.
licenseMsg: skin.getLicenseMsg(),
onSaveComplete: function () {
toast.showOnPageReload( mw.message( 'minerva-talk-reply-success' ).text() );
window.location.reload();
}
} );
} );
}
/**
* Initializes code needed to display the TalkSectionOverlay
*/
function initTalkSection() {
// register route for each of the sub-headings
// eslint-disable-next-line no-jquery/no-global-selector
$( '.section-heading' ).each( function () {
var
sectionId,
$heading = $( this ),
$headline = $heading.find( '.mw-headline' ),
sectionIdMatch = $heading.next().attr( 'class' ).match( /mf-section-(\d+)/ ),
headlineId = $headline.attr( 'id' );
if ( sectionIdMatch === null || sectionIdMatch.length !== 2 ) {
// If section id couldn't be parsed, there is no point in continuing
return;
}
sectionId = sectionIdMatch[ 1 ];
$heading.on( 'click.talkSectionOverlay', function ( ev ) {
ev.preventDefault();
window.location.hash = '#' + headlineId;
} );
overlayManager.add( headlineId, function () {
return createTalkSectionOverlay( $heading, $headline, sectionId );
} );
} );
}
function talkSectionAddSaveHandler() {
toast.showOnPageReload( mw.message( 'minerva-talk-topic-feedback' ).text() );
window.location = currentPage.titleObj.getTalkPage().getUrl();
}
/**
* Initializes code needed to display the TalkSectionAddOverlay
*/
function initTalkSectionAdd() {
overlayManager.add( /^\/talk\/new$/, function () {
return createOverlay( 'TalkSectionAddOverlay', {
api: api,
title: talkTitle,
// T184273 using `currentPage` because 'wgPageName'
// contains underscores instead of spaces.
licenseMsg: skin.getLicenseMsg(),
eventBus: eventBus,
currentPageTitle: currentPage.title,
onSaveComplete: function () {
talkSectionAddSaveHandler();
}
} );
} );
// This will be removed after TalkSectionaddOverlay.js is refactored to
// exclusively use the passed in `onSaveComplete` callback
eventBus.on( 'talk-added-wo-overlay', function () {
talkSectionAddSaveHandler();
} );
}
/**
* T230695: In the simplified view, we need to display a "Read as wikipage"
* button which enables the user to switch from simplified mode to the regular
* version of the mobile talk page (with TOC and sections that you can
* expand/collapse).
*/
function renderReadAsWikiPageButton() {
$( '<button>' )
.addClass( 'minerva-talk-full-page-button' )
.text( mw.message( 'minerva-talk-full-page' ).text() )
.on( 'click', function () {
// We need to cleanup the listeners previously added in initTalkSection
// so that events like clicking on a section) won't cause
// scroll changes/things only needed for the simplified view
removeTalkSectionListeners();
// eslint-disable-next-line no-jquery/no-global-selector
$( 'body' ).removeClass( 'skin-minerva--talk-simplified' );
$( this ).remove();
// send user back up to top of page so they don't land awkwardly in
// middle of page when it expands
window.scrollTo( 0, 0 );
} )
.appendTo( '#content' );
}
/**
* @method
* @param {JQuery.Event} ev
* @param {Function} onDismiss
* @param {Function} onFollowRoute
* @param {string} returnToQuery
* @return {undefined}
*/
function amcTalkClickHandler( ev ) {
function showDrawerIfEligible( ev, onDismiss, onFollowRoute, returnToQuery ) {
var
amcOutreach = mobile.amcOutreach,
amcCampaign = amcOutreach.loadCampaign(),
onDismiss = function () {
changeHash();
toast.show( mw.message( 'mobile-frontend-amc-outreach-dismissed-message' ).text() );
};
amcCampaign = amcOutreach.loadCampaign();
// avoiding navigating to original URL
// DO NOT USE stopPropagation or you'll break click tracking in WikimediaEvents
ev.preventDefault();
if ( amcCampaign.showIfEligible( amcOutreach.ACTIONS.onTalkLink, onDismiss, currentPage.title, '#/talk' ) ) {
if (
amcCampaign.showIfEligible(
amcOutreach.ACTIONS.onTalkLink,
onDismiss,
talkTitle,
returnToQuery
)
) {
return;
}
changeHash();
onFollowRoute();
}
/**
* Create route '#/talk'
* @ignore
* Called when user clicks on the add dicussion button located on a talk
* page
*
* @param {JQuery.Event} ev
*/
function amcAddTalkClickHandler( ev ) {
var
onFollowRoute = function () {
window.location.hash = '#/talk/new';
},
onDismiss = function () {
onFollowRoute();
toast.show( mw.message( 'mobile-frontend-amc-outreach-dismissed-message' ).text() );
};
showDrawerIfEligible( ev, onDismiss, onFollowRoute, '#/talk/new' );
}
/**
* Called when user clicks on a talk button that links to the talk page
*
* @param {JQuery.Event} ev
*/
function amcTalkClickHandler( ev ) {
var
$button = $( ev.target ),
onFollowRoute = function () {
window.location = $button.attr( 'href' );
},
onDismiss = function () {
onFollowRoute();
toast.showOnPageReload( mw.message( 'mobile-frontend-amc-outreach-dismissed-message' ).text() );
};
showDrawerIfEligible( ev, onDismiss, onFollowRoute, '' );
}
/**
* @return {boolean}
*/
function isSimplifiedViewEnabled() {
// eslint-disable-next-line no-jquery/no-class-state, no-jquery/no-global-selector
return $( 'body' ).hasClass( SKIN_MINERVA_TALK_SIMPLIFIED_CLASS );
}
/**
* Sets up necessary event handlers related to the talk page and talk buttons.
* Also renders the "Read as wikipage" button for the simplified mode
* (T230695).
*/
function init() {
// eslint-disable-next-line no-jquery/no-global-selector
var $talk = $( '.talk' ),
// eslint-disable-next-line no-jquery/no-global-selector
$addTalk = $( '.minerva-talk-add-button' );
$talk.on( 'click', amcTalkClickHandler );
$addTalk.on( 'click', amcAddTalkClickHandler );
// We only want the talk section add overlay to show when the user is on the
// view action (default action) of the talk page and not when the user is on
// other actions of the talk page (e.g. like the history action)
if ( currentPage.titleObj.isTalkPage() && mw.config.get( 'wgAction' ) === 'view' ) {
initTalkSectionAdd();
}
// SkinMinerva sets a class on the body which effectively controls when this
// mode is on
if ( isSimplifiedViewEnabled() ) {
initTalkSection();
renderReadAsWikiPageButton();
}
}
init();
if ( inTalkNamespace ) {
// reload the page after the new discussion was added
eventBus.on( 'talk-added-wo-overlay', function () {
var overlay = loadingOverlay();
window.location.hash = '';
// setTimeout to make sure, that loadingOverlay's overlayenabled class on html doesnt
// get removed by OverlayManager (who closes TalkSectionAddOverlay).
window.setTimeout( function () {
overlay.show();
window.location.reload();
}, 10 );
} );
}
};

View file

@ -0,0 +1,79 @@
@import '../../minerva.less/minerva.variables.less';
@divider-line-color: @colorGray14;
.minerva-talk-add-button {
display: block;
width: 100%;
max-width: none;
margin-top: 15px;
margin-bottom: 20px;
}
.minerva-talk-content-explained {
// When .skin-minerva--talk-simplified class is not applied to body, we don't
// want this to show (e.g. on pages without any topics, redundant no content
// messages could show)
display: none;
margin-top: 15px; // setting margin-top in case .minerva-talk-add-button is not present to provide a margin (i.e. when anon user)
padding-bottom: 15px;
border-bottom: 1px solid @divider-line-color;
font-size: @font-size-minerva-small;
font-weight: bold;
color: @colorGray5;
}
.minerva-talk-full-page-button {
display: block;
position: sticky;
position: -webkit-sticky;
bottom: 0;
left: 0;
width: 100%;
padding: 1em;
color: @colorProgressive;
background: @colorGray15;
border-top: 1px solid @divider-line-color;
text-align: center;
}
// Only run on talk pages
.client-js .skin-minerva--talk-simplified {
#toc,
#mf-section-0,
.section-heading + div {
display: none;
}
.minerva-talk-content-explained {
display: block;
}
.section-heading {
position: relative;
border-bottom: 1px solid @divider-line-color;
font-size: 1em;
font-family: @fontFamilyBase;
line-height: 1;
cursor: pointer;
display: block;
margin: 0;
padding: 0.8em 0;
.mw-ui-icon {
display: none;
}
}
.return-link {
margin-bottom: 20px;
}
}
@media screen and ( min-width: @width-breakpoint-tablet ) {
.minerva-talk-full-page-button {
margin-left: auto;
margin-right: auto;
max-width: @contentMaxWidthTablet;
}
}

View file

@ -438,6 +438,15 @@
"resources/skins.minerva.userpage.styles/userpage.less"
]
},
"skins.minerva.talk.styles": {
"targets": [
"mobile",
"desktop"
],
"styles": [
"resources/skins.minerva.talk.styles/talkpage.less"
]
},
"skins.minerva.personalMenu.icons": {
"class": "ResourceLoaderImageModule",
"selector": ".mw-ui-icon-minerva-{name}:before",
@ -574,7 +583,10 @@
"mobile-frontend-editor-redlink-explain",
"minerva-download",
"minerva-watchlist-cta",
"mobile-frontend-redirected-from"
"mobile-frontend-redirected-from",
"minerva-talk-full-page",
"minerva-talk-topic-feedback",
"minerva-talk-reply-success"
],
"styles": [
"resources/skins.minerva.scripts/styles.less",

View file

@ -1,54 +0,0 @@
When(/^I click the talk button$/) do
on(ArticlePage) do |page|
page.wait_until_rl_module_ready('skins.minerva.scripts')
page.talk_element.when_present.click
end
end
When(/^no topic is present$/) do
expect(on(ArticlePage).talk_overlay_content_header_element.when_present.text).to match 'There are no conversations about this page.'
end
When(/^I add a topic called "(.+)"$/) do |topic|
step 'I click the add discussion button'
on(ArticlePage) do |page|
page.talk_overlay_summary = topic
page.talk_overlay_body = 'Topic body is a really long text.'
page.wait_until { page.talk_overlay_save_button_element.enabled? }
page.talk_overlay_save_button
end
end
When(/^I see the talk overlay$/) do
on(ArticlePage).overlay_element.when_visible
end
When(/^I click the add discussion button$/) do
on(ArticlePage).talkadd_element.when_present.click
end
Then(/^I should see the topic called "(.+)" in the list of topics$/) do |topic|
# Timeout is high as the previous action hits the API which may take some time
expect(on(ArticlePage).talk_overlay_first_topic_title_element.when_present(20).text).to match topic
end
Then(/^I should see the talk overlay$/) do
on(ArticlePage) do |page|
page.wait_until_rl_module_ready('mobile.talk.overlays')
expect(on(ArticlePage).overlay_heading_element.when_present.text).to match 'Talk'
end
end
Then(/^there should be no talk button$/) do
expect(on(ArticlePage).talk_element).not_to be_visible
end
Then(/^there should be an add discussion button$/) do
# give overlay time to fully load
expect(on(ArticlePage).talkadd_element.when_present(10)).to be_visible
end
Then(/^there should be a save discussion button$/) do
# give overlay time to fully load
expect(on(ArticlePage).talktopic_save_element.when_present(10)).to be_visible
end

View file

@ -107,17 +107,17 @@ const iAmViewingAnUnwatchedPage = () => {
iAmOnPage( title );
};
const iAmOnAPageWithNoTalkTopics = () => {
const iAmOnATalkPageWithNoTalkTopics = () => {
const title = `Selenium talk test ${new Date()}`;
createPage( title, 'Selenium' );
iAmOnPage( title );
iAmOnPage( `Talk:${title}` );
};
module.exports = {
waitForPropagation,
iAmOnAPageThatHasTheFollowingEdits,
iAmOnAPageWithNoTalkTopics,
iAmOnATalkPageWithNoTalkTopics,
iAmViewingAWatchedPage,
iAmViewingAnUnwatchedPage,
iAmInAWikiThatHasCategories,

View file

@ -4,7 +4,7 @@ const { defineSupportCode } = require( 'cucumber' ),
} = require( './category_steps' ),
{ iAmInAWikiThatHasCategories,
iAmOnAPageThatHasTheFollowingEdits,
iAmOnAPageWithNoTalkTopics,
iAmOnATalkPageWithNoTalkTopics,
iAmViewingAWatchedPage, iAmViewingAnUnwatchedPage,
iGoToAPageThatHasLanguages } = require( './create_page_api_steps' ),
{
@ -38,12 +38,13 @@ const { defineSupportCode } = require( 'cucumber' ),
iSeeTheSearchOverlay
} = require( './search_steps' ),
{
iClickTheTalkButton,
iClickTheAddTalkButton,
iAddATopic,
iSeeTheTalkOverlay,
thereShouldBeASaveDiscussionButton,
noTopicIsPresent,
thereShouldBeAnAddDiscussionButton,
thereShouldBeATalkButton,
thereShouldBeNoTalkButton,
iShouldSeeTheTopicInTheListOfTopics
} = require( './talk_steps' ),
@ -89,7 +90,7 @@ defineSupportCode( function ( { Then, When, Given } ) {
When( /I click the browser back button/, iClickTheBrowserBackButton );
// Page steps
Given( /^I am on a page with no talk topics$/, iAmOnAPageWithNoTalkTopics );
Given( /^I am on a talk page with no talk topics$/, iAmOnATalkPageWithNoTalkTopics );
Given( /^I am in a wiki that has categories$/, () => {
iAmInAWikiThatHasCategories( 'Selenium categories test page' );
} );
@ -117,13 +118,14 @@ defineSupportCode( function ( { Then, When, Given } ) {
Then( /I should see the notifications overlay/, iShouldSeeTheNotificationsOverlay );
// talk
When( /^I click the talk button$/, iClickTheTalkButton );
When( /^I click the add talk button$/, iClickTheAddTalkButton );
When( /^I add a topic called "(.+)"$/, iAddATopic );
Then( /^I see the talk overlay$/, iSeeTheTalkOverlay );
Then( /^I should see the talk overlay$/, iSeeTheTalkOverlay );
Then( /^there should be a save discussion button$/, thereShouldBeASaveDiscussionButton );
Then( /^no topic is present$/, noTopicIsPresent );
Then( /^there should be an add discussion button$/, thereShouldBeAnAddDiscussionButton );
Then( /^there should be a talk button/, thereShouldBeATalkButton );
Then( /^there should be no talk button$/, thereShouldBeNoTalkButton );
Then( /^I should see the topic called "(.+)" in the list of topics$/, iShouldSeeTheTopicInTheListOfTopics );

View file

@ -3,16 +3,13 @@ const { iSeeAnOverlay, waitForPropagation } = require( './common_steps' );
const ArticlePageWithEditorOverlay = require( '../support/pages/article_page_with_editor_overlay' );
const { ArticlePage } = require( '../support/world.js' );
const iClickTheTalkButton = () => {
const iClickTheAddTalkButton = () => {
ArticlePage.waitUntilResourceLoaderModuleReady( 'skins.minerva.scripts' );
ArticlePage.talk_element.waitForVisible();
ArticlePage.talk_element.click();
ArticlePage.talk_add_element.waitForVisible();
ArticlePage.talk_add_element.click();
};
const iAddATopic = ( subject ) => {
ArticlePageWithEditorOverlay.continue_element.waitForVisible();
ArticlePageWithEditorOverlay.continue_element.click();
ArticlePageWithEditorOverlay.editor_overlay_element.waitForExist();
const overlay = ArticlePageWithEditorOverlay.editor_overlay_element;
overlay.element( '.overlay input' ).waitForExist();
overlay.element( '.overlay input' ).setValue( subject );
@ -35,17 +32,15 @@ const thereShouldBeASaveDiscussionButton = () => {
};
const noTopicIsPresent = () => {
ArticlePageWithEditorOverlay.editor_overlay_element.waitForExist();
const overlay = ArticlePageWithEditorOverlay.editor_overlay_element;
overlay.element( '.content-header' ).waitForExist();
assert.strictEqual(
overlay.element( '.content-header' ).getText(),
'There are no conversations about this page.'
);
assert.strictEqual( ArticlePage.first_section_element.isExisting(), false );
};
const thereShouldBeAnAddDiscussionButton = () => {
ArticlePageWithEditorOverlay.continue_element.waitForVisible();
assert.strictEqual( ArticlePage.talk_add_element.isVisible(), true );
};
const thereShouldBeATalkButton = () => {
assert.strictEqual( ArticlePage.talk_element.isVisible(), true );
};
const thereShouldBeNoTalkButton = () => {
@ -53,11 +48,8 @@ const thereShouldBeNoTalkButton = () => {
};
const iShouldSeeTheTopicInTheListOfTopics = ( subject ) => {
ArticlePageWithEditorOverlay.editor_overlay_element.waitForExist();
ArticlePageWithEditorOverlay.editor_overlay_element.element( '.topic-title-list li' ).waitForExist();
const firstItem = ArticlePageWithEditorOverlay.editor_overlay_element.element( '.topic-title-list li' );
assert.strictEqual(
firstItem.getText().indexOf( subject ) > -1,
ArticlePage.first_section_element.getText().indexOf( subject ) > -1,
true
);
};
@ -68,7 +60,8 @@ module.exports = {
thereShouldBeASaveDiscussionButton,
noTopicIsPresent,
thereShouldBeAnAddDiscussionButton,
thereShouldBeATalkButton,
thereShouldBeNoTalkButton,
iShouldSeeTheTopicInTheListOfTopics,
iClickTheTalkButton
iClickTheAddTalkButton
};

View file

@ -12,6 +12,10 @@ class ArticlePage extends MinervaPage {
get watch_element() { return $( '#ca-watch' ); }
get talk_element() { return $( '.talk ' ); }
get talk_add_element() { return $( '.minerva-talk-add-button' ); }
get first_section_element() {
return $( '.section-heading' );
}
get watched_element() { return $( '.mw-ui-icon-wikimedia-unStar-progressive, .mw-ui-icon-mf-watched' ); }
get menu_button_element() { return $( '#mw-mf-main-menu-button' ); }
get search_icon_element() { return $( '#searchIcon' ); }

View file

@ -4,47 +4,44 @@ Feature: Talk
Background:
Given I am using the mobile site
Scenario: Talk button not visible as logged out user
Given the page "Selenium talk test" exists
And I am on the "Selenium talk test" page
Then there should be no talk button
@login
Scenario: Talk on a page that does exist
Given the page "Talk:Selenium talk test" exists
Scenario: Talk button visible as logged in user
Given the page "Selenium talk test" exists
And I am logged into the mobile website
And the page "Selenium talk test" exists
When I click the talk button
Then I should see the talk overlay
And I am on the "Selenium talk test" page
Then there should be a talk button
@login
Scenario: Talk on a page that doesn't exist (bug 64268)
Given I am logged into the mobile website
And I am on a page that does not exist
When I click the talk button
Then I should see the talk overlay
Then there should be a talk button
@smoke @login
Scenario: Add discussion button shows on talk pages for logged in users
Given the page "Talk:Selenium talk test" exists
And I am logged into the mobile website
And I am on the "Talk:Selenium talk test" page
Then there should be an add discussion button
@smoke @login
Scenario: Add discussion for talk page possible as logged in user
Given the page "Talk:Selenium talk test" exists
And I am logged into the mobile website
And the page "Selenium talk test" exists
When I click the talk button
Then there should be an add discussion button
@smoke @login
Scenario: Add topic button shows on talk pages for logged in users
Given the page "Talk:Selenium talk test" exists
And I am logged into the mobile website
And I am on the "Talk:Selenium talk test" page
When I click the talk button
When I click the add talk button
Then there should be a save discussion button
Scenario: A newly created topic appears in the list of topics immediately
Scenario: A newly created topic appears in the list of topics
Given I am logged into the mobile website
And I am on a page with no talk topics
And I am on a talk page with no talk topics
And no topic is present
When I click the talk button
And I see the talk overlay
And no topic is present
And I add a topic called "New topic"
And I see the talk overlay
And I add a topic called "New topic"
Then I should see the topic called "New topic" in the list of topics
Scenario: Add discussion on talk page not possible as logged out user
Given the page "Talk:Selenium talk test" exists
And the page "Selenium talk test" exists
Then there should be no talk button

View file

@ -1,4 +1,4 @@
const { iAmOnAPageWithNoTalkTopics } = require( '../features/step_definitions/create_page_api_steps' ),
const { iAmOnATalkPageWithNoTalkTopics } = require( '../features/step_definitions/create_page_api_steps' ),
{
pageExists, iAmOnAPageThatDoesNotExist,
iAmUsingTheMobileSite,
@ -6,12 +6,13 @@ const { iAmOnAPageWithNoTalkTopics } = require( '../features/step_definitions/cr
iAmOnPage
} = require( '../features/step_definitions/common_steps' ),
{
iClickTheTalkButton,
iClickTheAddTalkButton,
iAddATopic,
iSeeTheTalkOverlay,
thereShouldBeASaveDiscussionButton,
noTopicIsPresent,
thereShouldBeAnAddDiscussionButton,
thereShouldBeATalkButton,
thereShouldBeNoTalkButton,
iShouldSeeTheTopicInTheListOfTopics
} = require( '../features/step_definitions/talk_steps' );
@ -28,50 +29,46 @@ describe( 'Talk', () => {
iAmUsingTheMobileSite();
} );
it( 'Add discussion on talk page not possible as logged out user', () => {
it( 'Talk button not visible as logged out user', () => {
iAmOnPage( 'Selenium talk test' );
thereShouldBeNoTalkButton();
} );
// @login
it( 'Talk on a page that does exist', () => {
it( 'Talk button visible as logged in user', () => {
iAmLoggedIntoTheMobileWebsite();
iAmOnPage( 'Selenium talk test' );
iClickTheTalkButton();
iSeeTheTalkOverlay();
thereShouldBeATalkButton();
} );
// @login
it( 'Talk on a page that doesn\'t exist (bug 64268)', () => {
iAmLoggedIntoTheMobileWebsite();
iAmOnAPageThatDoesNotExist();
iClickTheTalkButton();
iSeeTheTalkOverlay();
thereShouldBeATalkButton();
} );
// @smoke @login
it( 'Add discussion button shows on talk pages for logged in users', () => {
iAmLoggedIntoTheMobileWebsite();
iAmOnPage( 'Talk:Selenium talk test' );
thereShouldBeAnAddDiscussionButton();
} );
// @smoke @login
it( 'Add discussion for talk page possible as logged in user', () => {
iAmLoggedIntoTheMobileWebsite();
iAmOnPage( 'Selenium talk test' );
iClickTheTalkButton();
thereShouldBeAnAddDiscussionButton();
} );
// @smoke @login
it( 'Add topic button shows on talk pages for logged in users', () => {
iAmLoggedIntoTheMobileWebsite();
iAmOnAPageThatDoesNotExist();
iAmOnPage( 'Talk:Selenium talk test' );
iClickTheTalkButton();
iClickTheAddTalkButton();
thereShouldBeASaveDiscussionButton();
} );
it( 'A newly created topic appears in the list of topics immediately', () => {
it( 'A newly created topic appears in the list of topics', () => {
iAmLoggedIntoTheMobileWebsite();
iAmOnAPageWithNoTalkTopics();
iClickTheTalkButton();
iSeeTheTalkOverlay();
iAmOnATalkPageWithNoTalkTopics();
noTopicIsPresent();
iClickTheAddTalkButton();
iSeeTheTalkOverlay();
iAddATopic( 'New topic' );
iShouldSeeTheTopicInTheListOfTopics( 'New topic' );
} );