First-run experience popup for automatic topic subscriptions

Bug: T262103
Change-Id: I9f9336718ad060d553146c4e27604565ce5822c6
This commit is contained in:
Bartosz Dziewoński 2021-08-23 22:23:37 +02:00
parent 90283b3a7e
commit 9ded06a655
5 changed files with 261 additions and 6 deletions

View file

@ -94,6 +94,10 @@
"messages": [ "messages": [
"discussiontools-postedit-confirmation-published", "discussiontools-postedit-confirmation-published",
"discussiontools-postedit-confirmation-topicadded", "discussiontools-postedit-confirmation-topicadded",
"discussiontools-autotopicsubpopup-title",
"discussiontools-autotopicsubpopup-body",
"discussiontools-autotopicsubpopup-dismiss",
"discussiontools-autotopicsubpopup-preferences",
"discussiontools-error-comment-conflict", "discussiontools-error-comment-conflict",
"discussiontools-error-comment-disappeared", "discussiontools-error-comment-disappeared",
"discussiontools-error-comment-not-saved", "discussiontools-error-comment-not-saved",

View file

@ -111,6 +111,9 @@ class PreferenceHooks implements
$preferences['discussiontools-newtopictool-opened'] = [ $preferences['discussiontools-newtopictool-opened'] = [
'type' => 'api', 'type' => 'api',
]; ];
$preferences['discussiontools-seenautotopicsubpopup'] = [
'type' => 'api',
];
$dtConfig = $this->configFactory->makeConfig( 'discussiontools' ); $dtConfig = $this->configFactory->makeConfig( 'discussiontools' );
if ( if (

View file

@ -0,0 +1,50 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="200" fill="white"/>
<g filter="url(#filter0_d)">
<mask id="mask0" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="25" y="25" width="150" height="150">
<circle cx="100" cy="100" r="75" fill="#EAF3FF"/>
</mask>
<g mask="url(#mask0)">
<g filter="url(#filter1_d)">
<circle cx="100" cy="100" r="75" fill="#EAF3FF"/>
</g>
<g filter="url(#filter2_d)">
<rect x="23" y="65" width="114" height="126" rx="2" fill="white"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.35 86.1H60.65C56.7288 86.1 53.55 88.8758 53.55 92.3V135.7C53.55 139.124 56.7288 141.9 60.65 141.9H110.35C114.271 141.9 117.45 139.124 117.45 135.7V92.3C117.45 88.8758 114.271 86.1 110.35 86.1ZM110.35 123.3H96.15L92.6 129.5H78.4L74.85 123.3H60.65V92.3H110.35V123.3Z" fill="#3366CC"/>
<path d="M113.776 46C115.503 46 116.904 47.4003 116.904 49.1277V67.894C116.904 69.6214 115.503 71.0217 113.776 71.0217C112.048 71.0217 110.648 69.6214 110.648 67.894V49.1277C110.648 47.4003 112.048 46 113.776 46ZM141.437 57.4357C142.237 57.4357 143.025 57.7536 143.636 58.3642C144.857 59.5858 144.857 61.5901 143.636 62.8115L132.591 73.8562C131.37 75.0777 129.365 75.0774 128.144 73.8562C126.922 72.6347 126.923 70.6303 128.144 69.409L139.189 58.3642C139.799 57.7535 140.636 57.4357 141.437 57.4357ZM86.1151 57.4357C86.9155 57.4357 87.7523 57.7535 88.3632 58.3642L99.4079 69.409C100.629 70.6303 100.629 72.6347 99.4079 73.8562C98.1864 75.0774 96.1821 75.0777 94.9608 73.8562L83.9159 62.8115C82.6946 61.5901 82.6948 59.5858 83.9159 58.3642C84.5268 57.7536 85.3147 57.4357 86.1151 57.4357ZM152.872 85.0964C154.6 85.0964 156 86.4967 156 88.2241C156 89.9516 154.6 91.3518 152.872 91.3518H134.106C132.379 91.3518 130.978 89.9516 130.978 88.2241C130.978 86.4967 132.379 85.0964 134.106 85.0964H152.872ZM130.343 101.663C131.143 101.664 131.98 101.981 132.591 102.592L143.636 113.637C144.857 114.858 144.857 116.862 143.636 118.084C142.414 119.305 140.41 119.305 139.189 118.084L128.144 107.039C126.923 105.818 126.922 103.814 128.144 102.592C128.755 101.981 129.542 101.663 130.343 101.663Z" fill="#FFCC33"/>
</g>
</g>
<defs>
<filter id="filter0_d" x="21" y="21" width="158" height="158" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter1_d" x="21" y="25" width="158" height="158" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<filter id="filter2_d" x="18" y="60" width="124" height="136" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="2.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -2,8 +2,9 @@
/* global moment */ /* global moment */
var var
$pageContainer, linksController, $pageContainer, linksController, lastHighlightComment,
featuresEnabled = mw.config.get( 'wgDiscussionToolsFeaturesEnabled' ) || {}, featuresEnabled = mw.config.get( 'wgDiscussionToolsFeaturesEnabled' ) || {},
seenAutoTopicSubPopup = !!+mw.user.options.get( 'discussiontools-seenautotopicsubpopup' ),
storage = mw.storage.session, storage = mw.storage.session,
Parser = require( './Parser.js' ), Parser = require( './Parser.js' ),
ThreadItem = require( './ThreadItem.js' ), ThreadItem = require( './ThreadItem.js' ),
@ -15,7 +16,7 @@ var
utils = require( './utils.js' ), utils = require( './utils.js' ),
STATE_UNSUBSCRIBED = 0, STATE_UNSUBSCRIBED = 0,
STATE_SUBSCRIBED = 1, STATE_SUBSCRIBED = 1,
// STATE_AUTOSUBSCRIBED = 2, STATE_AUTOSUBSCRIBED = 2,
pageDataCache = {}; pageDataCache = {};
mw.messages.set( require( './controller/contLangMessages.json' ) ); mw.messages.set( require( './controller/contLangMessages.json' ) );
@ -315,6 +316,100 @@ function initTopicSubscriptions( $container ) {
} ); } );
} }
function maybeShowFirstTimeAutoTopicSubPopup() {
if ( seenAutoTopicSubPopup ) {
return;
}
seenAutoTopicSubPopup = true;
mw.user.options.set( 'discussiontools-seenautotopicsubpopup', '1' );
getApi().saveOption( 'discussiontools-seenautotopicsubpopup', '1' );
var $popupContent, popup;
if ( !lastHighlightComment ) {
return;
}
function close() {
popup.$element.removeClass( 'ext-discussiontools-autotopicsubpopup-fadein' );
setTimeout( function () {
popup.$element.detach();
}, 1000 );
}
$popupContent = $( '<div>' )
.append(
$( '<strong>' )
.addClass( 'ext-discussiontools-autotopicsubpopup-title' )
.text( mw.msg( 'discussiontools-autotopicsubpopup-title' ) ),
$( '<div>' )
.addClass( 'ext-discussiontools-autotopicsubpopup-image' ),
$( '<div>' )
.addClass( 'ext-discussiontools-autotopicsubpopup-body' )
.text( mw.msg( 'discussiontools-autotopicsubpopup-body' ) ),
$( '<div>' )
.addClass( 'ext-discussiontools-autotopicsubpopup-actions' )
.append( new OO.ui.ButtonWidget( {
label: mw.msg( 'discussiontools-autotopicsubpopup-dismiss' ),
flags: [ 'primary', 'progressive' ]
} ).on( 'click', close ).$element )
.append( new OO.ui.ButtonWidget( {
label: mw.msg( 'discussiontools-autotopicsubpopup-preferences' ),
href: mw.util.getUrl( 'Special:Preferences#mw-prefsection-editing-discussion' ),
framed: false
} ).$element )
);
popup = new OO.ui.PopupWidget( {
// Styles and dimensions
width: '',
height: '',
anchor: false,
autoClose: false,
head: false,
padded: false,
classes: [ 'ext-discussiontools-autotopicsubpopup' ],
hideWhenOutOfView: false,
// Content
$content: $popupContent.contents()
} );
// Like in highlight()
lastHighlightComment.getNativeRange().insertNode( popup.$element[ 0 ] );
// Pull it outside of headings to avoid silly fonts
if ( popup.$element.closest( 'h1, h2, h3, h4, h5, h6' ).length ) {
popup.$element.closest( 'h1, h2, h3, h4, h5, h6' ).after( popup.$element );
}
// Disable positioning, the popup is positioned in CSS, above the highlight
popup.toggle( true ).toggleClipping( false ).togglePositioning( false );
// If the page is very short, there might not be enough space above the highlight,
// causing the popup to overlap the skin navigation or even be off-screen.
// Position it on top of the highlight in that case...
// eslint-disable-next-line no-jquery/no-global-selector
if ( popup.$popup[ 0 ].getBoundingClientRect().top < $( '.mw-body' )[ 0 ].getBoundingClientRect().top ) {
popup.$popup.addClass( 'ext-discussiontools-autotopicsubpopup-overlap' );
}
// Scroll into view, leave some space above to avoid overlapping .postedit-container
OO.ui.Element.static.scrollIntoView(
popup.$popup[ 0 ],
{
padding: {
// Add padding to avoid overlapping the post-edit notification (above on desktop, below on mobile)
top: OO.ui.isMobile() ? 10 : 60,
bottom: OO.ui.isMobile() ? 85 : 10
},
// Specify scrollContainer for compatibility with MobileFrontend.
// Apparently it makes `<dd>` elements scrollable and OOUI tried to scroll them instead of body.
scrollContainer: OO.ui.Element.static.getRootScrollableElement( popup.$popup[ 0 ] )
}
);
popup.$element.addClass( 'ext-discussiontools-autotopicsubpopup-fadein' );
}
function updateSubscriptionStates( $container, headingsToUpdate ) { function updateSubscriptionStates( $container, headingsToUpdate ) {
// This method is called when we recently edited this page, and auto-subscriptions might have been // This method is called when we recently edited this page, and auto-subscriptions might have been
// added for some topics. It updates the [subscribe] buttons to reflect the new subscriptions. // added for some topics. It updates the [subscribe] buttons to reflect the new subscriptions.
@ -326,6 +421,7 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
} ); } );
// If the topic is already marked as auto-subscribed, there's nothing to do. // If the topic is already marked as auto-subscribed, there's nothing to do.
// (Except maybe show the first-time popup.)
// If the topic is marked as having never been subscribed, check if they are auto-subscribed now. // If the topic is marked as having never been subscribed, check if they are auto-subscribed now.
var topicsToCheck = []; var topicsToCheck = [];
var pending = []; var pending = [];
@ -334,7 +430,9 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
var subscribedState = el.hasAttribute( 'data-mw-subscribed' ) ? var subscribedState = el.hasAttribute( 'data-mw-subscribed' ) ?
Number( el.getAttribute( 'data-mw-subscribed' ) ) : null; Number( el.getAttribute( 'data-mw-subscribed' ) ) : null;
if ( subscribedState === null ) { if ( subscribedState === STATE_AUTOSUBSCRIBED ) {
maybeShowFirstTimeAutoTopicSubPopup();
} else if ( subscribedState === null ) {
topicsToCheck.push( headingName ); topicsToCheck.push( headingName );
pending.push( el ); pending.push( el );
} }
@ -370,6 +468,9 @@ function updateSubscriptionStates( $container, headingsToUpdate ) {
for ( var subItemName in response.subscriptions ) { for ( var subItemName in response.subscriptions ) {
var state = response.subscriptions[ subItemName ]; var state = response.subscriptions[ subItemName ];
updateSubscribeButton( linksByName[ subItemName ], state ); updateSubscribeButton( linksByName[ subItemName ], state );
if ( state === STATE_AUTOSUBSCRIBED ) {
maybeShowFirstTimeAutoTopicSubPopup();
}
} }
$( pending ).removeClass( 'ext-discussiontools-init-section-subscribe-link-pending' ); $( pending ).removeClass( 'ext-discussiontools-init-section-subscribe-link-pending' );
}, function () { }, function () {
@ -616,6 +717,7 @@ function init( $container, state ) {
// Highlight the last comment on the page // Highlight the last comment on the page
var lastComment = threadItems[ threadItems.length - 1 ]; var lastComment = threadItems[ threadItems.length - 1 ];
$highlight = highlight( lastComment ); $highlight = highlight( lastComment );
lastHighlightComment = lastComment;
// If it's the only comment under its heading, highlight the heading too. // If it's the only comment under its heading, highlight the heading too.
// (It might not be if the new discussion topic was posted without a title: T272666.) // (It might not be if the new discussion topic was posted without a title: T272666.)
@ -625,6 +727,7 @@ function init( $container, state ) {
lastComment.parent.replies.length === 1 lastComment.parent.replies.length === 1
) { ) {
$highlight = $highlight.add( highlight( lastComment.parent ) ); $highlight = $highlight.add( highlight( lastComment.parent ) );
lastHighlightComment = lastComment.parent;
} }
mw.hook( 'postEdit' ).fire( { mw.hook( 'postEdit' ).fire( {
@ -635,6 +738,7 @@ function init( $container, state ) {
// Find the comment we replied to, then highlight the last reply // Find the comment we replied to, then highlight the last reply
var repliedToComment = threadItemsById[ state.repliedTo ]; var repliedToComment = threadItemsById[ state.repliedTo ];
$highlight = highlight( repliedToComment.replies[ repliedToComment.replies.length - 1 ] ); $highlight = highlight( repliedToComment.replies[ repliedToComment.replies.length - 1 ] );
lastHighlightComment = repliedToComment.replies[ repliedToComment.replies.length - 1 ];
if ( OO.ui.isMobile() ) { if ( OO.ui.isMobile() ) {
mw.notify( mw.msg( 'discussiontools-postedit-confirmation-published', mw.user ) ); mw.notify( mw.msg( 'discussiontools-postedit-confirmation-published', mw.user ) );
@ -655,9 +759,9 @@ function init( $container, state ) {
$highlight[ 0 ], $highlight[ 0 ],
{ {
padding: { padding: {
top: 10, // Add padding to avoid overlapping the post-edit notification (above on desktop, below on mobile)
// Add padding on mobile to avoid overlapping the notification top: OO.ui.isMobile() ? 10 : 60,
bottom: 10 + ( OO.ui.isMobile() ? 75 : 0 ) bottom: OO.ui.isMobile() ? 85 : 10
}, },
// Specify scrollContainer for compatibility with MobileFrontend. // Specify scrollContainer for compatibility with MobileFrontend.
// Apparently it makes `<dd>` elements scrollable and OOUI tried to scroll them instead of body. // Apparently it makes `<dd>` elements scrollable and OOUI tried to scroll them instead of body.

View file

@ -211,3 +211,97 @@ span[ data-mw-comment-start ] {
} }
} }
/* stylelint-enable selector-class-pattern */ /* stylelint-enable selector-class-pattern */
// Styles inspired by the Popups extension
// (and occasionally copypasted from there)
.ext-discussiontools-autotopicsubpopup {
position: absolute;
left: 0;
right: 0;
// Increase specificity to override .oo-ui-popupWidget
.oo-ui-popupWidget& {
// Animations
opacity: 0;
transform: translate( 0, -20px );
transition: opacity 0.2s, transform 0.2s;
&-fadein {
opacity: 1;
transform: translate( 0, 0 );
}
}
.oo-ui-popupWidget-popup {
padding: 1.5em;
box-sizing: border-box;
// Center horizontally
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
// Position above the highlight
bottom: 3em;
&.ext-discussiontools-autotopicsubpopup-overlap {
// If there isn't enough space above, position on top of the highlight
top: -1em;
bottom: auto;
}
}
&-title {
font-weight: bold;
font-size: 1.2em;
display: block;
}
&-image {
/* @embed */
background: url( autotopicsubpopup-image.svg ) center center no-repeat;
width: 200px;
height: 200px;
}
// Desktop
@media ( min-width: 720px ) {
.oo-ui-popupWidget-popup {
width: 450px;
min-height: 200px;
padding-right: 0;
}
&-image {
position: absolute;
top: 0;
right: 0;
height: 100%;
}
&-body {
margin-bottom: 1em;
margin-top: 1em;
}
&-title,
&-body,
&-actions {
margin-right: 200px;
}
}
// Mobile
@media ( max-width: 719px ) {
.oo-ui-popupWidget-popup {
width: 320px;
}
&-image {
width: 100%;
}
&-body {
margin-bottom: 1em;
}
}
}