From f35bf487ef85bf61bc65c1c58bca23e7ca47e473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Dziewo=C5=84ski?= Date: Thu, 24 Jun 2021 21:10:40 +0200 Subject: [PATCH] Take over extra links to add a new topic added by gadgets/templates * Move getTitleFromUrl() from parser to utils. It's a generic method, the PHP equivalent is already in utils. Bug: T277371 Change-Id: Id960e5f60af02bdeb0a3a68f43b7a695eb035139 --- modules/Parser.js | 32 +---------------------- modules/ReplyLinksController.js | 46 ++++++++++++++++++++++++++++++--- modules/utils.js | 31 ++++++++++++++++++++++ 3 files changed, 75 insertions(+), 34 deletions(-) diff --git a/modules/Parser.js b/modules/Parser.js index 3bb0897ad..1f493d98b 100644 --- a/modules/Parser.js +++ b/modules/Parser.js @@ -495,36 +495,6 @@ Parser.prototype.findTimestamp = function ( node, timestampRegexps ) { return null; }; -/** - * Get a MediaWiki page title from a URL. - * - * @private - * @param {string} url - * @return {mw.Title|null} Page title, or null if this isn't a link to a page - */ -function getTitleFromUrl( url ) { - try { - url = new mw.Uri( url ); - } catch ( err ) { - // T106244: URL encoded values using fallback 8-bit encoding (invalid UTF-8) cause mediawiki.Uri to crash - return null; - } - if ( url.query.title ) { - return mw.Title.newFromText( url.query.title ); - } - - var articlePathRegexp = new RegExp( - mw.util.escapeRegExp( mw.config.get( 'wgArticlePath' ) ) - .replace( mw.util.escapeRegExp( '$1' ), '(.*)' ) - ); - var match; - if ( ( match = url.path.match( articlePathRegexp ) ) ) { - return mw.Title.newFromText( decodeURIComponent( match[ 1 ] ) ); - } - - return null; -} - /** * Given a link node (``), if it's a link to a user-related page, return their username. * @@ -532,7 +502,7 @@ function getTitleFromUrl( url ) { * @return {string|null} */ function getUsernameFromLink( link ) { - var title = getTitleFromUrl( link.href ); + var title = utils.getTitleFromUrl( link.href ); if ( !title ) { return null; } diff --git a/modules/ReplyLinksController.js b/modules/ReplyLinksController.js index b42791888..55c217a47 100644 --- a/modules/ReplyLinksController.js +++ b/modules/ReplyLinksController.js @@ -6,8 +6,10 @@ function ReplyLinksController( $pageContainer ) { OO.EventEmitter.call( this ); this.$pageContainer = $pageContainer; + this.$body = $( document.body ); this.onReplyLinkClickHandler = this.onReplyLinkClick.bind( this ); this.onAddSectionLinkClickHandler = this.onAddSectionLinkClick.bind( this ); + this.onAnyLinkClickHandler = this.onAnyLinkClick.bind( this ); // Reply links this.$replyLinks = $pageContainer.find( 'a.ext-discussiontools-init-replylink-reply[data-mw-comment]' ); @@ -24,6 +26,10 @@ function ReplyLinksController( $pageContainer ) { if ( $addSectionTab.length && pageExists ) { this.$addSectionLink = $addSectionTab.find( 'a' ); this.$addSectionLink.on( 'click keypress', this.onAddSectionLinkClickHandler ); + + // Handle events on all links that potentially open the new section interface, + // including links in the page content (from templates) or from gadgets. + this.$body.on( 'click keypress', 'a:not( [data-mw-comment] )', this.onAnyLinkClickHandler ); } } } @@ -52,14 +58,47 @@ ReplyLinksController.prototype.onAddSectionLinkClick = function ( e ) { if ( !this.isActivationEvent( e ) ) { return; } - e.preventDefault(); - // Disable VisualEditor's new section editor (in wikitext mode / NWE), to allow our own. // We do this on first click, because we don't control the order in which our code and NWE code // runs, so its event handlers may not be registered yet. $( e.target ).closest( '#ca-addsection' ).off( '.ve-target' ); - this.emit( 'link-click', utils.NEW_TOPIC_COMMENT_ID, $( e.target ) ); + // onAnyLinkClick() will also handle clicks on this element, so we don't emit() here to avoid + // doing it twice. +}; + +ReplyLinksController.prototype.onAnyLinkClick = function ( e ) { + // Check query parameters to see if this is really a new topic link + var href = $( e.currentTarget ).attr( 'href' ); + var uri; + try { + uri = new mw.Uri( href, { arrayParams: true } ); + } catch ( err ) { + // T106244: URL encoded values using fallback 8-bit encoding (invalid UTF-8) cause mediawiki.Uri to crash + return; + } + if ( ( uri.query.action !== 'edit' && uri.query.veaction !== 'editsource' ) || uri.query.section !== 'new' ) { + // Not a link to add a new topic + return; + } + + var title = utils.getTitleFromUrl( href ); + if ( !title || title.getPrefixedDb() !== mw.config.get( 'wgRelevantPageName' ) ) { + // Link to add a section on another page, not supported yet (T282205) + return; + } + + if ( uri.query.editintro || uri.query.preload || uri.query.preloadparams || uri.query.preloadtitle ) { + // Adding a new topic with preloaded text is not supported yet (T269310) + return; + } + + if ( !this.isActivationEvent( e ) ) { + return; + } + e.preventDefault(); + + this.emit( 'link-click', utils.NEW_TOPIC_COMMENT_ID, $( e.currentTarget ) ); }; ReplyLinksController.prototype.isActivationEvent = function ( e ) { @@ -126,6 +165,7 @@ ReplyLinksController.prototype.teardown = function () { this.$replyLinks.off( 'click keypress', this.onReplyLinkClickHandler ); this.$addSectionLink.off( 'click keypress', this.onAddSectionLinkClickHandler ); + this.$body.off( 'click keypress', 'a:not( [data-mw-comment] )', this.onAnyLinkClickHandler ); }; module.exports = ReplyLinksController; diff --git a/modules/utils.js b/modules/utils.js index 5c205ef70..932b31743 100644 --- a/modules/utils.js +++ b/modules/utils.js @@ -360,6 +360,36 @@ function getFullyCoveredSiblings( item ) { return null; } +/** + * Get a MediaWiki page title from a URL. + * + * @private + * @param {string} url + * @return {mw.Title|null} Page title, or null if this isn't a link to a page + */ +function getTitleFromUrl( url ) { + try { + url = new mw.Uri( url ); + } catch ( err ) { + // T106244: URL encoded values using fallback 8-bit encoding (invalid UTF-8) cause mediawiki.Uri to crash + return null; + } + if ( url.query.title ) { + return mw.Title.newFromText( url.query.title ); + } + + var articlePathRegexp = new RegExp( + mw.util.escapeRegExp( mw.config.get( 'wgArticlePath' ) ) + .replace( mw.util.escapeRegExp( '$1' ), '(.*)' ) + ); + var match; + if ( ( match = url.path.match( articlePathRegexp ) ) ) { + return mw.Title.newFromText( decodeURIComponent( match[ 1 ] ) ); + } + + return null; +} + /** * Traverse the document in depth-first order, calling the callback whenever entering and leaving * a node. The walk starts before the given node and ends when callback returns a truthy value, or @@ -439,6 +469,7 @@ module.exports = { getTranscludedFromElement: getTranscludedFromElement, getHeadlineNodeAndOffset: getHeadlineNodeAndOffset, htmlTrim: htmlTrim, + getTitleFromUrl: getTitleFromUrl, linearWalk: linearWalk, linearWalkBackwards: linearWalkBackwards };