/* global moment */ const STATE_UNSUBSCRIBED = 0, STATE_SUBSCRIBED = 1, STATE_AUTOSUBSCRIBED = 2, utils = require( './utils.js' ), CommentItem = require( './CommentItem.js' ), HeadingItem = require( './HeadingItem.js' ); let api, seenAutoTopicSubPopup = !!+mw.user.options.get( 'discussiontools-seenautotopicsubpopup' ), linksByName = {}, buttonsByName = {}; /** * Update a subscribe link * * @param {HTMLElement} element Subscribe link * @param {number|null} state State constant (STATE_UNSUBSCRIBED, STATE_SUBSCRIBED or STATE_AUTOSUBSCRIBED) * @param {HTMLElement|null} labelElement Subscribe link, if different to element * @param {boolean} isNewTopics Is a subscribe link for new topics subscriptions */ function updateSubscribeLink( element, state, labelElement, isNewTopics ) { labelElement = labelElement || element; if ( state !== null ) { element.setAttribute( 'data-mw-subscribed', String( state ) ); } if ( state ) { labelElement.textContent = mw.msg( isNewTopics ? 'discussiontools-newtopicssubscription-button-unsubscribe-label' : 'discussiontools-topicsubscription-button-unsubscribe' ); element.setAttribute( 'title', mw.msg( isNewTopics ? 'discussiontools-newtopicssubscription-button-unsubscribe-tooltip' : 'discussiontools-topicsubscription-button-unsubscribe-tooltip' ) ); } else { labelElement.textContent = mw.msg( isNewTopics ? 'discussiontools-newtopicssubscription-button-subscribe-label' : 'discussiontools-topicsubscription-button-subscribe' ); element.setAttribute( 'title', mw.msg( isNewTopics ? 'discussiontools-newtopicssubscription-button-subscribe-tooltip' : 'discussiontools-topicsubscription-button-subscribe-tooltip' ) ); } } /** * Update a subscribe button * * @param {OO.ui.ButtonWidget} button Subscribe button * @param {number|null} state State constant (STATE_UNSUBSCRIBED, STATE_SUBSCRIBED or STATE_AUTOSUBSCRIBED) */ function updateSubscribeButton( button, state ) { if ( state !== null ) { button.$element[ 0 ].setAttribute( 'data-mw-subscribed', String( state ) ); } if ( state ) { button.setIcon( 'bell' ); button.setLabel( mw.msg( 'discussiontools-topicsubscription-button-unsubscribe-label' ) ); button.setTitle( mw.msg( 'discussiontools-topicsubscription-button-unsubscribe-tooltip' ) ); } else { button.setIcon( 'bellOutline' ); button.setLabel( mw.msg( 'discussiontools-topicsubscription-button-subscribe-label' ) ); button.setTitle( mw.msg( 'discussiontools-topicsubscription-button-subscribe-tooltip' ) ); } } /** * Change the subscription state of a topic subscription * * @param {string} title Page title * @param {string} commentName Comment name * @param {boolean} subscribe Subscription state * @param {boolean} isNewTopics Subscription is for new topics * @return {jQuery.Promise} Promise which resolves after change of state */ function changeSubscription( title, commentName, subscribe, isNewTopics ) { const promise = api.postWithToken( 'csrf', { action: 'discussiontoolssubscribe', page: title, commentname: commentName, subscribe: subscribe } ).then( ( response ) => OO.getProp( response, 'discussiontoolssubscribe' ) || {} ); promise.then( ( result ) => { mw.notify( mw.msg( result.subscribe ? ( isNewTopics ? 'discussiontools-newtopicssubscription-notify-subscribed-body' : 'discussiontools-topicsubscription-notify-subscribed-body' ) : ( isNewTopics ? 'discussiontools-newtopicssubscription-notify-unsubscribed-body' : 'discussiontools-topicsubscription-notify-unsubscribed-body' ) ), { title: mw.msg( result.subscribe ? ( isNewTopics ? 'discussiontools-newtopicssubscription-notify-subscribed-title' : 'discussiontools-topicsubscription-notify-subscribed-title' ) : ( isNewTopics ? 'discussiontools-newtopicssubscription-notify-unsubscribed-title' : 'discussiontools-topicsubscription-notify-unsubscribed-title' ) ) } ); }, ( code, data ) => { mw.notify( api.getErrorMessage( data ), { type: 'error' } ); } ); return promise; } function getSubscribedStateFromElement( element ) { return element.hasAttribute( 'data-mw-subscribed' ) ? Number( element.getAttribute( 'data-mw-subscribed' ) ) : null; } /** * Lazy load API to avoid circular dependency */ function initApi() { if ( !api ) { api = require( './controller.js' ).getApi(); } } /** * Initialize topic subscriptions feature * * @param {jQuery} $container Page container * @param {ThreadItemSet} threadItemSet */ function initTopicSubscriptions( $container, threadItemSet ) { linksByName = {}; buttonsByName = {}; initApi(); // Subscription buttons (visual enhancements) $container.find( '.ext-discussiontools-init-section-subscribeButton' ).each( ( i, element ) => { // These attributes will be lost when infusing // TODO: Could also be fixed by subclassing ButtonWidget in PHP const subscribedStateTemp = getSubscribedStateFromElement( element ); const id = $( element ).closest( '.ext-discussiontools-init-section' ) .find( '[data-mw-comment-start]' ).attr( 'id' ); const headingItem = threadItemSet.findCommentById( id ); if ( !( headingItem instanceof HeadingItem ) ) { // This should never happen return; } const name = headingItem.name; const button = OO.ui.infuse( element ); buttonsByName[ name ] = button; // Restore data attribute if ( subscribedStateTemp !== null ) { button.$element[ 0 ].setAttribute( 'data-mw-subscribed', String( subscribedStateTemp ) ); } const title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle(); button.on( 'click', () => { // Get latest subscribedState const subscribedState = getSubscribedStateFromElement( button.$element[ 0 ] ); button.setDisabled( true ); changeSubscription( title, name, !subscribedState ) .then( ( result ) => { updateSubscribeButton( button, result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED ); } ) .always( () => { button.setDisabled( false ); } ); } ); } ); // Subscription links (no visual enhancements) $container.find( '.ext-discussiontools-init-section-subscribe-link' ).each( ( i, link ) => { const $link = $( link ); const id = $link.closest( '.ext-discussiontools-init-section' ) .find( '[data-mw-comment-start]' ).attr( 'id' ); const headingItem = threadItemSet.findCommentById( id ); if ( !( headingItem instanceof HeadingItem ) ) { // This should never happen return; } const itemName = headingItem.name; const title = mw.config.get( 'wgRelevantPageName' ) + '#' + headingItem.getLinkableTitle(); linksByName[ itemName ] = link; $link.on( 'click keypress', ( e ) => { if ( e.type === 'keypress' && e.which !== OO.ui.Keys.ENTER && e.which !== OO.ui.Keys.SPACE ) { // Only handle keypresses on the "Enter" or "Space" keys return; } if ( e.type === 'click' && !utils.isUnmodifiedLeftClick( e ) ) { // Only handle unmodified left clicks return; } e.preventDefault(); // Get latest subscribedState const subscribedState = getSubscribedStateFromElement( $link[ 0 ] ); $link.addClass( 'ext-discussiontools-init-section-subscribe-link-pending' ); changeSubscription( title, itemName, !subscribedState ) .then( ( result ) => { updateSubscribeLink( $link[ 0 ], result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED ); } ) .always( () => { $link.removeClass( 'ext-discussiontools-init-section-subscribe-link-pending' ); } ); } ); } ); initNewTopicsSubscription(); } /** * Bind new topics subscription button * * Note: because this function can get called from `wikipage.content`, * and we're interacting with elements outside of $container, make * sure to account for this possibly being run multiple times on a * pageload. Calls from DT's own previews are filtered out, but other * page actions like live-preview can still reach this point. */ function initNewTopicsSubscription() { let $button, $label, $icon; initApi(); if ( mw.config.get( 'skin' ) === 'minerva' ) { // eslint-disable-next-line no-jquery/no-global-selector $button = $( '.menu__item--page-actions-overflow-t-page-subscribe' ); $label = $button.find( '.toggle-list-item__label' ); $icon = $button.find( '.minerva-icon' ); // HACK: We can't set data-mw-subscribed intially in Minerva, so work it out from the icon // eslint-disable-next-line no-jquery/no-class-state const initialState = $icon.hasClass( 'minerva-icon--bell' ) ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED; $button.attr( 'data-mw-subscribed', String( initialState ) ); } else { // eslint-disable-next-line no-jquery/no-global-selector $button = $( '#ca-dt-page-subscribe > a' ); $label = $button.find( 'span' ); $icon = $( [] ); } const titleObj = mw.Title.newFromText( mw.config.get( 'wgRelevantPageName' ) ); const name = utils.getNewTopicsSubscriptionId( titleObj ); $button.off( '.mw-dt-topicsubscriptions' ).on( 'click.mw-dt-topicsubscriptions', ( e ) => { e.preventDefault(); // Get latest subscribedState const subscribedState = getSubscribedStateFromElement( $button[ 0 ] ); changeSubscription( titleObj.getPrefixedText(), name, !subscribedState, true ) .then( ( result ) => { updateSubscribeLink( $button[ 0 ], result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED, $label[ 0 ], true ); $icon.toggleClass( 'minerva-icon--bell', !!result.subscribe ); $icon.toggleClass( 'minerva-icon--bellOutline', !result.subscribe ); } ); } ); } function initSpecialTopicSubscriptions() { api = require( './controller.js' ).getApi(); // Unsubscribe links on special page // eslint-disable-next-line no-jquery/no-global-selector $( '.ext-discussiontools-special-unsubscribe-button' ).each( ( i, element ) => { const button = OO.ui.infuse( element ); const data = button.getData(); let subscribedState = STATE_SUBSCRIBED; button.on( 'click', () => { button.setDisabled( true ); changeSubscription( data.title, data.item, !subscribedState ) .then( ( result ) => { button.setLabel( mw.msg( result.subscribe ? 'discussiontools-topicsubscription-button-unsubscribe-label' : 'discussiontools-topicsubscription-button-subscribe-label' ) ); button.clearFlags(); button.setFlags( [ result.subscribe ? 'destructive' : 'progressive' ] ); subscribedState = result.subscribe ? STATE_SUBSCRIBED : STATE_UNSUBSCRIBED; } ).always( () => { button.setDisabled( false ); } ); } ); } ); } /** * Show the first time popup for auto topic subscriptions, if required */ function maybeShowFirstTimeAutoTopicSubPopup() { const lastHighlightComment = require( './highlighter.js' ).getLastHighlightedPublishedComment(); if ( !lastHighlightComment || seenAutoTopicSubPopup ) { return; } seenAutoTopicSubPopup = true; mw.user.options.set( 'discussiontools-seenautotopicsubpopup', '1' ); api.saveOption( 'discussiontools-seenautotopicsubpopup', '1' ); let popup = null; function close() { popup.$element.removeClass( 'ext-discussiontools-autotopicsubpopup-fadein' ); setTimeout( () => { popup.$element.detach(); }, 1000 ); } const $popupContent = $( '