Merge "Add autosave support to visual mode"

This commit is contained in:
jenkins-bot 2020-05-21 17:06:39 +00:00 committed by Gerrit Code Review
commit d0dd4cf036
5 changed files with 73 additions and 63 deletions

View file

@ -18,6 +18,8 @@ if ( defaultVisual ) {
}
function CommentController( $pageContainer, comment, thread ) {
var mode;
this.$pageContainer = $pageContainer;
this.comment = comment;
this.thread = thread;
@ -40,8 +42,9 @@ function CommentController( $pageContainer, comment, thread ) {
this.$replyLinkButtons.append( this.$replyLink );
modifier.addReplyLink( comment, this.$replyLinkButtons[ 0 ] );
if ( storage.get( 'reply/' + comment.id + '/body' ) ) {
this.setup();
if ( storage.get( 'reply/' + comment.id + '/saveable' ) ) {
mode = storage.get( 'reply/' + comment.id + '/mode' );
this.setup( mode );
}
}
@ -114,10 +117,19 @@ CommentController.prototype.onReplyLinkClick = function ( e ) {
this.setup();
};
CommentController.prototype.setup = function () {
/**
* Create and setup the reply widget
*
* @param {string} [mode] Optionally force a mode, 'visual' or 'source'
*/
CommentController.prototype.setup = function ( mode ) {
var parsoidPromise,
commentController = this;
if ( mode === undefined ) {
mode = defaultVisual ? 'visual' : 'source';
}
// TODO: Allow users to use multiple reply widgets simultaneously.
// Currently submitting a reply from one widget would also destroy the other ones.
// eslint-disable-next-line no-jquery/no-class-state
@ -142,7 +154,7 @@ CommentController.prototype.setup = function () {
mechanism: 'click',
// TODO: Use 'wikitext-2017' when config.enable2017Wikitext is set
// eslint-disable-next-line camelcase
editor_interface: defaultVisual ? 'visual' : 'wikitext'
editor_interface: mode === 'visual' ? 'visual' : 'wikitext'
} );
this.$replyLinkButtons.addClass( 'dt-init-replylink-active' );
@ -151,7 +163,7 @@ CommentController.prototype.setup = function () {
parsoidPromise = getParsoidTranscludedCommentData( this.comment.id );
this.replyWidgetPromise = parsoidPromise.then( function ( parsoidData ) {
return commentController.createReplyWidget( parsoidData );
return commentController.createReplyWidget( parsoidData, mode === 'visual' );
}, function ( code, data ) {
commentController.teardown();
@ -182,7 +194,7 @@ CommentController.prototype.setup = function () {
}
$( commentController.newListItem ).empty().append( replyWidget.$element );
commentController.setupReplyWidget( replyWidget, true );
commentController.setupReplyWidget( replyWidget, null, true );
logger( { action: 'ready' } );
logger( { action: 'loaded' } );
@ -215,10 +227,10 @@ CommentController.prototype.createReplyWidget = function ( parsoidData, visual )
} );
};
CommentController.prototype.setupReplyWidget = function ( replyWidget, scrollIntoView ) {
CommentController.prototype.setupReplyWidget = function ( replyWidget, initialValue, scrollIntoView ) {
replyWidget.connect( this, { teardown: 'teardown' } );
replyWidget.setup();
replyWidget.setup( initialValue );
if ( scrollIntoView ) {
replyWidget.scrollElementIntoView( { padding: scrollPadding } );
}
@ -406,8 +418,7 @@ CommentController.prototype.switchToWikitext = function () {
oldWidget.disconnect( commentController );
oldWidget.teardown();
replyWidget.setValue( controller.sanitizeWikitextLinebreaks( wikitext ) );
commentController.setupReplyWidget( replyWidget );
commentController.setupReplyWidget( replyWidget, controller.sanitizeWikitextLinebreaks( wikitext ) );
} );
};
@ -454,8 +465,7 @@ CommentController.prototype.switchToVisual = function () {
modifier.unwrapList( doc.body.children[ 0 ] );
}
replyWidget.setValue( doc );
commentController.setupReplyWidget( replyWidget );
commentController.setupReplyWidget( replyWidget, doc );
} );
};

View file

@ -250,12 +250,13 @@ function init( $container, state ) {
pageThreads = parser.groupThreads( pageComments );
pageCommentsById = commentsById( pageComments );
$pageContainer.removeClass( 'dt-init-replylink-open' );
pageThreads.forEach( function ( thread ) {
traverseNode( thread, thread );
} );
$pageContainer.addClass( 'dt-init-done' );
$pageContainer.removeClass( 'dt-init-replylink-open' );
// For debugging
mw.dt.pageThreads = pageThreads;

View file

@ -32,6 +32,7 @@ function ReplyWidget( commentController, parsoidData, config ) {
this.context = contextNode ? contextNode.nodeName.toLowerCase() : 'dl';
// TODO: Should storagePrefix include pageName?
this.storagePrefix = 'reply/' + comment.id;
this.storage = mw.storage.session;
inputConfig = $.extend(
{ placeholder: mw.msg( 'discussiontools-replywidget-placeholder-reply', comment.author ) },
@ -146,8 +147,6 @@ function ReplyWidget( commentController, parsoidData, config ) {
widget.$actions.prepend( widget.$checkboxes );
}
} );
this.initAutoSave();
}
/* Inheritance */
@ -169,22 +168,10 @@ ReplyWidget.prototype.focus = null;
ReplyWidget.prototype.getValue = null;
/**
* Set the reply widget's value
*
* @method
* @chainable
* @param {Mixed} value Value
* @return {ReplyWidget}
*/
ReplyWidget.prototype.setValue = null;
ReplyWidget.prototype.isEmpty = null;
ReplyWidget.prototype.getMode = null;
ReplyWidget.prototype.initAutoSave = null;
ReplyWidget.prototype.clear = function () {
if ( this.errorMessage ) {
this.errorMessage.$element.remove();
@ -199,6 +186,7 @@ ReplyWidget.prototype.setPending = function ( pending ) {
} else {
this.replyButton.setDisabled( false );
this.cancelButton.setDisabled( false );
this.updateButtons();
}
};
@ -238,6 +226,7 @@ ReplyWidget.prototype.onModeTabSelectChoose = function ( option ) {
/**
* Setup the widget
*
* @param {Mixed} initialValue Initial value
* @chainable
* @return {ReplyWidget}
*/
@ -247,9 +236,15 @@ ReplyWidget.prototype.setup = function () {
this.modeTabSelect.selectItemByData( this.getMode() );
this.saveEditMode( this.getMode() );
}
return this;
};
ReplyWidget.prototype.afterSetup = function () {
// Init preview and button state
this.onInputChange();
return this;
// Autosave
this.storage.set( this.storagePrefix + '/mode', this.getMode() );
};
/**
@ -298,6 +293,8 @@ ReplyWidget.prototype.tryTeardown = function () {
ReplyWidget.prototype.teardown = function () {
this.unbindBeforeUnloadHandler();
this.clear();
this.storage.remove( this.storagePrefix + '/mode' );
this.storage.remove( this.storagePrefix + '/saveable' );
this.$preview.empty();
this.emit( 'teardown' );
return this;
@ -319,7 +316,8 @@ ReplyWidget.prototype.onInputChange = function () {
ol: '#'
}[ this.context ];
this.replyButton.setDisabled( this.isEmpty() );
this.updateButtons();
this.storage.set( this.storagePrefix + '/saveable', this.isEmpty() ? '' : '1' );
if ( this.getMode() !== 'source' ) {
return;
@ -361,6 +359,10 @@ ReplyWidget.prototype.onInputChange = function () {
} );
};
ReplyWidget.prototype.updateButtons = function () {
this.replyButton.setDisabled( this.isEmpty() );
};
ReplyWidget.prototype.onFirstTransaction = function () {
logger( { action: 'firstChange' } );
};

View file

@ -45,7 +45,7 @@ ReplyWidgetPlain.prototype.clear = function () {
// Parent method
ReplyWidgetPlain.super.prototype.clear.apply( this, arguments );
this.setValue( '' );
this.replyBodyWidget.setValue( '' );
};
ReplyWidgetPlain.prototype.isEmpty = function () {
@ -56,15 +56,6 @@ ReplyWidgetPlain.prototype.getMode = function () {
return 'source';
};
ReplyWidgetPlain.prototype.initAutoSave = function () {
var body;
this.storage = mw.storage.session;
this.storage.set( this.storagePrefix + '/class', 'ReplyWidgetPlain' );
if ( ( body = this.storage.get( this.storagePrefix + '/body' ) ) ) {
this.replyBodyWidget.setValue( body );
}
};
ReplyWidgetPlain.prototype.onInputChange = function () {
var wikitext;
@ -75,7 +66,9 @@ ReplyWidgetPlain.prototype.onInputChange = function () {
this.storage.set( this.storagePrefix + '/body', wikitext );
};
ReplyWidgetPlain.prototype.setup = function () {
ReplyWidgetPlain.prototype.setup = function ( initialValue ) {
var autosaveValue = this.storage.get( this.storagePrefix + '/body' );
// Parent method
ReplyWidgetPlain.super.prototype.setup.call( this );
@ -83,6 +76,10 @@ ReplyWidgetPlain.prototype.setup = function () {
this.replyBodyWidget.connect( this, { change: this.onInputChangeThrottled } );
this.replyBodyWidget.once( 'change', this.onFirstTransaction.bind( this ) );
this.replyBodyWidget.setValue( initialValue || autosaveValue );
this.afterSetup();
return this;
};
@ -90,7 +87,6 @@ ReplyWidgetPlain.prototype.teardown = function () {
this.replyBodyWidget.disconnect( this );
this.replyBodyWidget.off( 'change' );
this.storage.remove( this.storagePrefix + '/class' );
this.storage.remove( this.storagePrefix + '/body' );
// Parent method
@ -124,9 +120,4 @@ ReplyWidgetPlain.prototype.getValue = function () {
return this.replyBodyWidget.getValue();
};
ReplyWidgetPlain.prototype.setValue = function ( value ) {
this.replyBodyWidget.setValue( value );
return this;
};
module.exports = ReplyWidgetPlain;

View file

@ -44,17 +44,6 @@ ReplyWidgetVisual.prototype.getValue = function () {
}
};
ReplyWidgetVisual.prototype.setValue = function ( value ) {
var target = this.replyBodyWidget.target;
if ( target && target.getSurface() ) {
target.setDocument( value );
} else {
// #setup hasn't been called yet, just save the value for when it is
this.initialValue = value;
}
return this;
};
ReplyWidgetVisual.prototype.clear = function () {
// Parent method
ReplyWidgetVisual.super.prototype.clear.apply( this, arguments );
@ -73,13 +62,28 @@ ReplyWidgetVisual.prototype.getMode = function () {
this.defaultMode;
};
ReplyWidgetVisual.prototype.initAutoSave = function () {
// TODO: Implement
};
ReplyWidgetVisual.prototype.setup = function ( initialValue ) {
var htmlOrDoc,
widget = this,
target = this.replyBodyWidget.target;
ReplyWidgetVisual.prototype.setup = function () {
this.replyBodyWidget.setDocument( this.initialValue || '<p></p>' );
this.initialValue = null;
if ( this.storage.get( this.storagePrefix + '/saveable' ) ) {
htmlOrDoc = this.storage.get( this.storagePrefix + '/ve-dochtml' );
target.recovered = true;
} else {
htmlOrDoc = initialValue || '<p></p>';
}
target.originalHtml = htmlOrDoc instanceof HTMLDocument ? ve.properInnerHtml( htmlOrDoc.body ) : htmlOrDoc;
target.fromEditedState = !!initialValue;
this.replyBodyWidget.setDocument( htmlOrDoc );
target.once( 'surfaceReady', function () {
target.getSurface().getModel().setAutosaveDocId( widget.storagePrefix );
target.initAutosave();
widget.afterSetup();
} );
// Parent method
ReplyWidgetVisual.super.prototype.setup.call( this );
@ -97,6 +101,8 @@ ReplyWidgetVisual.prototype.setup = function () {
ReplyWidgetVisual.prototype.teardown = function () {
this.replyBodyWidget.disconnect( this );
this.replyBodyWidget.off( 'change' );
// TODO: Just teardown the whole target?
this.replyBodyWidget.target.clearDocState();
// Parent method
return ReplyWidgetVisual.super.prototype.teardown.call( this );