jenkins-bot has submitted this change and it was merged.
Change subject: Organize flow-initialize and add an Initializer object
......................................................................
Organize flow-initialize and add an Initializer object
Refactor flow-initialize to be more human-readable and
more organized by moving the code into mw.flow.Initializer
which maintains state, does its existence tests on its
own and organizes the code into logical pieces.
Change-Id: I315d7c423eaeaa0686f748644c460967fb2c7b9e
---
M Resources.php
M modules/flow-initialize.js
A modules/mw.flow.Initializer.js
3 files changed, 952 insertions(+), 679 deletions(-)
Approvals:
Mattflaschen: Looks good to me, approved
jenkins-bot: Verified
diff --git a/Resources.php b/Resources.php
index bee4a05..f467713 100644
--- a/Resources.php
+++ b/Resources.php
@@ -473,6 +473,7 @@
// this must be last (of everything loaded. otherwise
a components
// can be initialized before all the mixins are loaded.
Can we mixin
// after initialization?)
+ 'mw.flow.Initializer.js',
'flow-initialize.js',
),
'dependencies' => array(
diff --git a/modules/flow-initialize.js b/modules/flow-initialize.js
index 9772ec5..73a49e2 100644
--- a/modules/flow-initialize.js
+++ b/modules/flow-initialize.js
@@ -1,7 +1,6 @@
/*!
* Runs Flow code, using methods in FlowUI.
*/
-
( function ( $ ) {
// Pretend we got some data and run with it
/*
@@ -9,697 +8,68 @@
* @todo not like this
*/
$( document ).ready( function () {
- var dataBlob, navWidget, flowBoard, dmBoard, newTopicWidget,
sidebarExpandWidget, descriptionWidget,
- siderailCollapsed = mw.user.options.get(
'flow-side-rail-state' ) === 'collapsed',
- pageTitle = mw.Title.newFromText( mw.config.get(
'wgPageName' ) ),
+ var flowBoard,
$component = $( '.flow-component' ),
$board = $( '.flow-board' ),
- preloadTopic, preloadContent, preloadFormat,
- finishLoading = function () {
- $component.addClass( 'flow-component-ready' );
- $( '.flow-ui-load-overlay' ).addClass(
'oo-ui-element-hidden' );
- };
+ pageTitle = mw.Title.newFromText( mw.config.get(
'wgPageName' ) ),
+ initializer = new mw.flow.Initializer( {
+ pageTitle: pageTitle,
+ $component: $component,
+ $board: $board
+ } );
- // HACK: If there is no component, we are not on a flow
- // board at all, and there's no need to load anything.
- // This is especially true for tests, though we should
- // fix this by telling ResourceLoader to not load
- // flow-initialize at all on tests.
- if ( $component.length === 0 ) {
- finishLoading();
+ // Set component
+ if ( !initializer.setComponentDom( $component ) ) {
+ initializer.finishLoading();
return;
}
- mw.flow.initComponent( $component );
+ // Initialize old system
+ initializer.initOldComponent();
+ // Initialize board
+ if ( initializer.setBoardDom( $board ) ) {
+ // Set up flowBoard
+ flowBoard = mw.flow.getPrototypeMethod( 'component',
'getInstanceByElement' )( $board );
+ initializer.setBoardObject( flowBoard );
- // HACK: Similarly to the above hack, if there is no board
- // we shouldn't proceed. This is true mainly to history pages
- // that have the component but not the board DOM element.
- if ( $board.length === 0 ) {
+ // Initialize DM system and board
+ initializer.initDataModel( {
+ pageTitle: pageTitle,
+ tocPostsLimit: 50,
+ renderedTopics: $( '.flow-topic' ).length,
+ boardId: $component.data( 'flow-id' ),
+ defaultSort: $board.data( 'flow-sortby' )
+ } );
+
+ // For reference and debugging
+ mw.flow.system = initializer.getDataModelSystem();
+
+ if ( initializer.isDiffPage() ) {
+ // Setup undo pages
+ initializer.setupUndoPage();
+ } else {
+ // Replace the no-js editor if we are editing
in a
+ // new page
+ initializer.replaceNoJSEditor( $(
'.flow-edit-post-form' ) );
+
+ // Create and replace UI widgets
+ initializer.initializeWidgets();
+
+ // Fall back to mw.flow.data, which was used
until September 2015
+ // NOTICE: This block must be after the
initialization of the ui widgets so
+ // they can populate themselves according to
the events.
+ initializer.populateDataModel( mw.config.get(
'wgFlowData' ) || ( mw.flow && mw.flow.data ) );
+ }
+ } else {
// Editing summary in a separate window. That has no
- // flow-board, but we should still replace it
- startEditTopicSummary(
+ // flow-board, but we should still replace the widget
+ initializer.startEditTopicSummary(
false,
$component.data( 'flow-id' )
);
-
- // Mark as finished loading
- finishLoading();
- return;
- }
-
- // Set up window overlay
- $( 'body' ).append( mw.flow.ui.windowOverlay.$element );
- mw.flow.ui.windowOverlay.$element.append(
mw.flow.ui.windowManager.$element );
-
- flowBoard = mw.flow.getPrototypeMethod( 'component',
'getInstanceByElement' )( $board );
-
- if (
- $component.hasClass( 'topic-page' ) &&
- $( 'body' ).hasClass( 'action-view' )
- ) {
- $board
- .toggleClass( 'flow-board-expanded',
siderailCollapsed );
- // We are in single-topic view. Initialize the sidebar
expand widget
- sidebarExpandWidget = new
mw.flow.ui.SidebarExpandWidget( {
- collapsed: siderailCollapsed,
- expandedButtonTitle: mw.msg(
'flow-topic-collapse-siderail' ),
- collapsedButtonTitle: mw.msg(
'flow-topic-expand-siderail' )
- } );
- sidebarExpandWidget.$element.insertAfter( $board );
- sidebarExpandWidget.on( 'toggle', function ( collapsed
) {
- $board.toggleClass( 'flow-board-expanded',
collapsed );
- } );
- }
-
- // Load data model
- mw.flow.system = new mw.flow.dm.System( {
- pageTitle: pageTitle,
- tocPostsLimit: 50,
- renderedTopics: $( '.flow-topic' ).length,
- boardId: $component.data( 'flow-id' ),
- defaultSort: $board.data( 'flow-sortby' )
- } );
-
- dmBoard = mw.flow.system.getBoard();
- dmBoard.on( 'add', function ( newItems ) {
- var i, len, item, itemId;
-
- for ( i = 0, len = newItems.length; i < len; i++ ) {
- item = newItems[ i ];
- itemId = item.getId();
-
- if ( $.inArray( itemId,
flowBoard.orderedTopicIds ) === -1 ) {
- flowBoard.orderedTopicIds.push( itemId
);
- }
-
- flowBoard.topicTitlesById[ itemId ] =
item.getContent();
- flowBoard.updateTimestampsByTopicId[ itemId ] =
item.getLastUpdate();
- }
- flowBoard.sortTopicIds( flowBoard );
- } );
-
- // E.g. on topic re-order, before re-population.
- dmBoard.on( 'clear', function () {
- flowBoard.orderedTopicIds = [];
- flowBoard.topicTitlesById = {};
- } );
- // We shouldn't have to worry about 'remove', since by the time
we have filtering,
- // orderedTopicIds should be gone.
-
- // Initialize the old system to accept the default
- // 'newest' order for the topic order widget
- // Get the current default sort
- flowBoard.topicIdSort =
mw.flow.system.getBoard().getSortOrder();
-
- /* UI Widgets */
- navWidget = new mw.flow.ui.NavigationWidget( mw.flow.system, {
- defaultSort: flowBoard.topicIdSort
- } );
- $( '.flow-board-navigation' ).append( navWidget.$element );
-
- // HACK: These event handlers should be in the prospective
widgets
- // they will move once we have Board UI and Topic UI widgets
- mw.flow.system.on( 'resetBoardStart', function () {
- $component.addClass( 'flow-api-inprogress' );
- // Before we reinitialize the board we have to detach
- // the navigation widget. This should not be necessary
when
- // the board and topics are OOUI widgets
- navWidget.$element.detach();
- } );
-
- mw.flow.system.on( 'resetBoardEnd', function ( data ) {
- var $rendered;
-
- // HACK: Trim the data for only the rendered topics
- // We can't have the API request use a limit because
when we
- // rebuild the board (when we reorder) we want the full
50
- // topics for the ToC widget. When the topic- and
board-widget
- // are operational, we should render properly without
using this
- // hack.
- data.topiclist.roots = data.topiclist.roots.splice( 0,
mw.flow.system.getRenderedTopics() );
-
- $rendered = $(
-
mw.flow.TemplateEngine.processTemplateGetFragment(
- 'flow_block_loop',
- { blocks: data }
- )
- ).children();
- // Run this on a short timeout so that the other board
handler in FlowBoardComponentLoadMoreFeatureMixin can run
- // TODO: Using a timeout doesn't seem like the right
way to do this.
- setTimeout( function () {
- // Reinitialize the whole board with these nodes
- // flowBoard.reinitializeContainer( $rendered );
- $board.empty().append( $rendered[ 1 ] );
-
- // Since we've replaced the entire board, we
need to reinitialize
- // it. This also takes away the original
navWidget, so we need to
- // make sure it's reinitialized too
- flowBoard.reinitializeContainer( $rendered );
- $( '.flow-board-navigation' ).append(
navWidget.$element );
-
- $component.removeClass( 'flow-api-inprogress' );
- }, 50 );
-
- } );
-
- // HACK: On load more, populate the board dm
- flowBoard.on( 'loadmore', function ( topiclist ) {
- // Add to the DM board
- mw.flow.system.populateBoardTopicsFromJson( topiclist );
-
- replaceReplyForms( $board );
- deactivateReplyLinks( $board );
- } );
-
- // HACK: Update the DM when topic is refreshed
- flowBoard.on( 'refreshTopic', function ( workflowId, topicData
) {
- var revisionId, revision,
- topic = dmBoard.getItemById( workflowId ),
- data = topicData.flow[ 'view-topic'
].result.topic;
-
- if ( !topic ) {
- // New topic
- mw.flow.system.populateBoardTopicsFromJson(
data, 0 );
- } else {
- // Topic already exists. Repopulate
- revisionId = data.posts[ workflowId ];
- revision = data.revisions[ revisionId ];
- topic.populate( revision );
- }
-
- replaceReplyForms( topicData.$topic );
- deactivateReplyLinks( topicData.$topic );
- } );
-
- // Load a topic from the ToC that isn't rendered on
- // the page yet. This will be gone once board, topic
- // and post are widgetized.
- navWidget.on( 'loadTopic', function ( topicId ) {
- flowBoard.jumpToTopic( topicId );
- } );
- navWidget.on( 'reorderTopics', function ( newOrder ) {
- flowBoard.topicIdSort = newOrder;
- } );
-
- // Replace reply inputs with the editor widget
- function replaceReplyForms( $element ) {
- $element.find( '.flow-post.flow-reply-form' ).each(
function () {
- var $topic = $( this ).parent(),
- placeholder = mw.msg(
'flow-reply-topic-title-placeholder', $topic.find( '.flow-topic-title'
).text().trim() ),
- replyTo = $( this ).find(
'input[name="topic_replyTo"]' ).val(),
- replyWidget = new
mw.flow.ui.ReplyWidget( $topic.data( 'flowId' ), replyTo, {
- placeholder: placeholder
- } );
-
- replyWidget.on( 'saveContent', function (
workflow ) {
- replyWidget.destroy();
- replyWidget.$element.remove();
-
- // HACK get the old system to rerender
the topic
- return
flowBoard.flowBoardComponentRefreshTopic(
- $topic,
- workflow
- );
- } );
- replyWidget.$element.data( 'self', replyWidget
);
-
- // Replace the reply form with the new editor
widget
- $( this ).replaceWith( replyWidget.$element );
- } );
- }
- replaceReplyForms( $board );
-
- // No-JS view with JavaScript on (replace old editor)
- function replaceNoJSEditor( $element ) {
- var editPostWidget,
- $post = $element.parent(),
- $topic = $post.closest( '.flow-topic' );
-
- function saveOrCancelHandler( workflow ) {
- editPostWidget.destroy();
- editPostWidget.$element.remove();
-
- // HACK get the old system to rerender the topic
- return flowBoard.flowBoardComponentRefreshTopic(
- $topic,
- workflow
- );
- }
-
- if ( !$element.length ) {
- return;
- }
-
- editPostWidget = new mw.flow.ui.EditPostWidget(
$topic.data( 'flowId' ), $post.data( 'flowId' ) );
-
- editPostWidget
- .on( 'saveContent', saveOrCancelHandler )
- // HACK: In this case, we are in an edge case
where the topic already
- // loaded with the editor open. We can't trust
the content of the editor
- // for displaying the post in case of a
'cancel' event and we don't have
- // the actual content stored in the DOM
anywhere else.
- // We must reload the topic -- just like we do
on save -- for a cancel
- // event too.
- .on( 'cancel', saveOrCancelHandler.bind( null,
$topic.data( 'flowId' ) ) );
-
- $element.replaceWith( editPostWidget.$element );
- }
- // Editing in a separate window
- replaceNoJSEditor( $( '.flow-edit-post-form' ) );
-
- // Undo actions
- if ( $( 'form[data-module="topic"]' ).length ) {
- replaceEditorInUndoEditPost( $(
'form[data-module="topic"]' ) );
- finishLoading();
- return;
- } else if ( $( 'form[data-module="header"]' ).length ) {
- replaceEditorInUndoHeaderPost( $(
'form[data-module="header"]' ) );
- finishLoading();
- return;
- }
-
- function createEditorWidget( $domToReplace, content, saveMsgKey
) {
- var $wrapper,
- anonWarning = new
mw.flow.ui.AnonWarningWidget(),
- error = new OO.ui.LabelWidget( {
- classes: [
'flow-ui-boardDescriptionWidget-error flow-errors errorbox' ]
- } ),
- editor = new mw.flow.ui.EditorWidget( {
- saveMsgKey: saveMsgKey
- } );
-
- error.toggle( false );
- editor.toggle( true );
- anonWarning.toggle( mw.user.isAnon() );
-
- // HACK: We still need a reference to the error widget,
for
- // the api responses in the intialized widgets that use
this
- // function, so make a forced connection
- editor.error = error;
-
- // Prepare the editor
- editor.pushPending();
- editor.activate();
-
- editor.setContent( content, 'wikitext' )
- .then( function () {
- editor.popPending();
- } );
-
- editor
- .on( 'saveContent', function ( content,
contentFormat ) {
- var $captchaField, captcha;
-
- editor.pushPending();
-
- $captchaField = error.$label.find(
'[name="wpCaptchaWord"]' );
- if ( $captchaField.length > 0 ) {
- captcha = {
- id:
this.error.$label.find( '[name="wpCaptchaId"]' ).val(),
- answer:
$captchaField.val()
- };
- }
- error.setLabel( '' );
- error.toggle( false );
-
- // HACK: This is a cheat so that we can
have a single function
- // that creates the editor, but
multiple uses, especially for the
- // APIhandler in different cases
- editor.emit( 'afterSaveContent',
content, contentFormat, captcha );
- } )
- .on( 'cancel', function () {
- editor.pushPending();
- editor.emit( 'afterCancel' );
- // returnToBoard();
- } );
-
- $wrapper = $( '<div>' )
- .append(
- error.$element,
- anonWarning.$element,
- editor.$element
- );
- $domToReplace.replaceWith( $wrapper );
-
- return editor;
- }
-
- function replaceEditorInUndoEditPost( $form ) {
- var apiHandler, content, postId, editor, prevRevId,
- pageName = mw.config.get( 'wgPageName' ),
- title = mw.Title.newFromText( pageName ),
- topicId = title.getNameText(),
- returnToTitle = function () {
- // HACK: redirect to topic view
- window.location.href = title.getUrl();
- };
-
- if ( !$form.length ) {
- return;
- }
-
- postId = $form.find( 'input[name="topic_postId"]'
).val();
- prevRevId = $form.find(
'input[name="topic_prev_revision"]' ).val();
- content = $form.find( 'textarea' ).val();
-
- apiHandler = new mw.flow.dm.APIHandler(
- 'Topic:' + topicId,
- {
- currentRevision: prevRevId
- }
- );
-
- // Create the editor
- editor = createEditorWidget(
- $form,
- content,
- mw.user.isAnon() ?
'flow-post-action-edit-post-submit-anonymously' :
'flow-post-action-edit-post-submit'
- );
-
- // Events
- editor
- .on( 'afterSaveContent', function ( content,
contentFormat, captcha ) {
- apiHandler.savePost( topicId, postId,
content, contentFormat, captcha )
- .then(
- // Success
- returnToTitle,
- // Failure
- function ( errorCode,
errorObj ) {
- if (
/spamfilter$/.test( errorCode ) && errorObj.error.spamfilter ===
'flow-spam-confirmedit-form' ) {
-
editor.error.setLabel(
-
// CAPTCHA form
-
new OO.ui.HtmlSnippet( errorObj.error.info )
- );
- } else {
-
editor.error.setLabel( errorObj.error && errorObj.error.info ||
errorObj.exception );
- }
-
-
editor.error.toggle( true );
-
editor.popPending();
- }
- );
- } )
- .on( 'afterCancel', returnToTitle );
- }
-
- function replaceEditorInUndoHeaderPost( $form ) {
- var prevRevId, editor, content,
- error, apiHandler,
- pageName = mw.config.get( 'wgPageName' ),
- title = mw.Title.newFromText( pageName ),
- returnToBoard = function () {
- window.location.href = title.getUrl();
- };
-
- if ( !$form.length ) {
- return;
- }
-
- prevRevId = $form.find(
'input[name="header_prev_revision"]' ).val();
- content = $form.find( 'textarea[name="header_content"]'
).val();
-
- apiHandler = new mw.flow.dm.APIHandler(
- title.getPrefixedDb(),
- {
- currentRevision: prevRevId
- }
- );
-
- // Create the editor
- editor = createEditorWidget(
- $form,
- content,
- mw.user.isAnon() ?
'flow-edit-header-submit-anonymously' : 'flow-edit-header-submit'
- );
-
- // Events
- editor
- .on( 'afterSaveContent', function ( content,
contentFormat, captcha ) {
- apiHandler.saveDescription( content,
contentFormat, captcha )
- .then(
- // Success
- returnToBoard,
- // Failure
- function ( errorCode,
errorObj ) {
- if (
/spamfilter$/.test( errorCode ) && errorObj.error.spamfilter ===
'flow-spam-confirmedit-form' ) {
-
error.setLabel(
-
// CAPTCHA form
-
new OO.ui.HtmlSnippet( errorObj.error.info )
- );
- } else {
-
error.setLabel( errorObj.error && errorObj.error.info || errorObj.exception );
- }
-
editor.popPending();
- }
- );
- } )
- .on( 'afterCancel', function () {
- returnToBoard();
- } );
- }
-
- // Replace the 'reply' buttons so they all produce replyWidgets
rather
- // than the reply forms from the API
- $board
- .on( 'click', '.flow-ui-reply-link-trigger', function
() {
- var replyWidget,
- $topic = $( this ).closest(
'.flow-topic' ),
- placeholder = mw.msg(
'flow-reply-topic-title-placeholder', $topic.find( '.flow-topic-title'
).text().trim() ),
- replyTo = $( this ).data( 'postId' ),
- // replyTo can refer to a post ID or a
topic ID
- // For posts, the ReplyWidget should go
in .flow-replies
- // For topics, it's directly inside the
topic
- $targetContainer = $( '#flow-post-' +
replyTo + ' > .flow-replies, #flow-topic-' + replyTo ),
- $existingWidget =
$targetContainer.children( '.flow-ui-replyWidget' );
-
- // Check that there's not already a reply
widget existing in the same place
- if ( $existingWidget.length > 0 ) {
- // Focus the existing reply widget
- $existingWidget.data( 'self'
).activateEditor();
- $existingWidget.data( 'self' ).focus();
- return false;
- }
-
- replyWidget = new mw.flow.ui.ReplyWidget(
$topic.data( 'flowId' ), replyTo, {
- placeholder: placeholder,
- expandable: false
- } );
- // Create a reference so we can call it from
the DOM above
- replyWidget.$element.data( 'self', replyWidget
);
-
- // Add reply form below the post being replied
to (WRT max depth)
- $targetContainer.append( replyWidget.$element );
- replyWidget.activateEditor();
-
- replyWidget
- .on( 'saveContent', function ( workflow
) {
- replyWidget.destroy();
- replyWidget.$element.remove();
-
- // HACK get the old system to
rerender the topic
- return
flowBoard.flowBoardComponentRefreshTopic(
- $topic,
- workflow
- );
- } )
- .on( 'cancel', function () {
- replyWidget.destroy();
- replyWidget.$element.remove();
- } );
-
- return false;
- } );
-
- function deactivateReplyLinks( $element ) {
- // Cancel the interactive handler so "old" system
doesn't get triggered for internal replies
- $element.find( 'a.flow-reply-link' ).each( function () {
- // Store the needed details so we can get rid
of the URL in JS mode
- var href = $( this ).attr( 'href' ),
- uri = new mw.Uri( href ),
- postId = uri.query.topic_postId;
-
- $( this )
- .data( 'postId', postId )
- .attr( 'data-flow-interactive-handler',
'' )
- .attr( 'href', '' )
- .addClass( 'flow-ui-reply-link-trigger'
);
- } );
- }
- deactivateReplyLinks( $board );
-
- // New topic form
- newTopicWidget = new mw.flow.ui.NewTopicWidget(
pageTitle.getPrefixedDb() );
- newTopicWidget.on( 'save', function ( newTopicId ) {
- // Display the new topic with the old system
- var $stub = $( '<div
class="flow-topic"><div></div></div>' ).prependTo( flowBoard.$container.find(
'.flow-topics' ) );
- return flowBoard.flowBoardComponentRefreshTopic(
$stub.find( 'div' ), newTopicId );
- } );
- $( 'form.flow-newtopic-form' ).replaceWith(
newTopicWidget.$element );
-
- $board.on( 'click', '.flow-ui-edit-post-link', function ( event
) {
- var editPostWidget,
- $topic = $( this ).closest( '.flow-topic' ),
- topicId = $topic.data( 'flow-id' ),
- $post = $( this ).closest( '.flow-post' ),
- $postMain = $post.children( '.flow-post-main' ),
- postId = $post.data( 'flow-id' ),
- $board = $( '.flow-board' ),
- flowBoard = mw.flow.getPrototypeMethod(
'component', 'getInstanceByElement' )( $board );
-
- editPostWidget = new mw.flow.ui.EditPostWidget(
topicId, postId );
- editPostWidget
- .on( 'saveContent', function ( workflow ) {
- editPostWidget.destroy();
- editPostWidget.$element.remove();
-
- // HACK get the old system to rerender
the topic
- return
flowBoard.flowBoardComponentRefreshTopic(
- $topic,
- workflow
- );
- } )
- .on( 'cancel', function () {
- editPostWidget.destroy();
- editPostWidget.$element.replaceWith(
$postMain );
- } );
-
- $postMain.replaceWith( editPostWidget.$element );
-
- event.preventDefault();
- } );
-
- function startEditTopicSummary( isFullBoard, topicId, action ) {
- var editTopicSummaryWidget,
- $topic = $( '#flow-topic-' + topicId ),
- $summaryContainer = $topic.find(
'.flow-topic-summary-container' ),
- $topicSummary = $summaryContainer.find(
'.flow-topic-summary' ),
- options = {},
- pageName = mw.config.get( 'wgPageName' ),
- title = mw.Title.newFromText( pageName );
-
- if ( !$summaryContainer.length ) {
- return;
- }
-
- if ( action === 'lock' || action === 'unlock' ) {
- options = {
- cancelMsgKey: 'flow-skip-summary'
- };
- }
-
- editTopicSummaryWidget = new
mw.flow.ui.EditTopicSummaryWidget( topicId, options );
- editTopicSummaryWidget
- .on( 'saveContent', function ( workflow ) {
- editTopicSummaryWidget.destroy();
-
editTopicSummaryWidget.$element.remove();
-
- if ( isFullBoard ) {
- // HACK get the old system to
rerender the topic
- return
flowBoard.flowBoardComponentRefreshTopic(
- $topic,
- workflow
- );
- } else {
- // HACK: redirect to topic view
- window.location.href =
title.getUrl();
- }
- } )
- .on( 'cancel', function () {
- editTopicSummaryWidget.destroy();
-
editTopicSummaryWidget.$element.remove();
- if ( isFullBoard ) {
- $summaryContainer.append(
$topicSummary );
- } else {
- // HACK: redirect to topic view
- window.location.href =
title.getUrl();
- }
- } );
-
- $topicSummary.remove();
- $summaryContainer.append(
editTopicSummaryWidget.$element );
- }
-
- $board.on( 'click', '.flow-ui-summarize-topic-link', function (
event ) {
- var $topic = $( this ).closest( '.flow-topic' ),
- topicId = $topic.data( 'flow-id' );
- startEditTopicSummary( true, topicId );
- event.preventDefault();
- } );
-
- $board.on( 'click', '.flow-ui-topicmenu-lock', function () {
- var promise,
- action = $( this ).data( 'role' ),
- $topic = $( this ).closest( '.flow-topic' ),
- topicId = $topic.data( 'flow-id' ),
- api = new mw.flow.dm.APIHandler();
-
- if ( action === 'lock' ) {
- promise = api.resolveTopic( topicId );
- } else {
- promise = api.reopenTopic( topicId );
- }
-
- promise
- .then( function ( workflow ) {
- return
flowBoard.flowBoardComponentRefreshTopic(
- $topic,
- workflow
- );
- } )
- .then( function ( data ) {
- var revisionId = data.topic.posts[
topicId ],
- revision =
data.topic.revisions[ revisionId ],
- summaryContent = OO.getProp(
revision, 'summary', 'revision', 'content', 'content' ),
- skipSummarize = action ===
'unlock' && !summaryContent;
-
- if ( !skipSummarize ) {
- startEditTopicSummary( true,
topicId, action );
- }
- } );
-
- // Prevent default
- return false;
- } );
-
- // Board description widget
- descriptionWidget = new mw.flow.ui.BoardDescriptionWidget(
dmBoard, {
- $existing: $( '.flow-ui-boardDescriptionWidget-content'
).contents(),
- $categories: $( '.flow-board-header-category-view-nojs'
).contents()
- } );
- $( '.flow-ui-boardDescriptionWidget' ).replaceWith(
descriptionWidget.$element );
-
- // The category widget is inside the board description widget.
- // Remove it here
- $( '.flow-board-header-category-view-nojs' ).detach();
- // HACK: Remove the MW page categories
- $( '.catlinks:not(.flow-ui-categoriesWidget)' ).detach();
-
- // Fall back to mw.flow.data, which was used until September
2015
- // NOTICE: This block must be after the initialization of the
ui widgets so
- // they can populate themselves according to the events.
- dataBlob = mw.config.get( 'wgFlowData' ) || ( mw.flow &&
mw.flow.data );
- if ( dataBlob && dataBlob.blocks ) {
- // Populate the rendered topics or topic (if we are in
a single-topic view)
- mw.flow.system.populateBoardTopicsFromJson(
dataBlob.blocks.topiclist || dataBlob.blocks.topic );
- // Populate header
- mw.flow.system.populateBoardDescriptionFromJson(
dataBlob.blocks.header || {} );
- // Populate the ToC topics
- if ( dataBlob.toc ) {
- mw.flow.system.populateBoardTopicsFromJson(
dataBlob.toc );
- }
- } else {
- mw.flow.system.populateBoardFromApi();
- }
-
- preloadTopic = OO.getProp( dataBlob, 'blocks', 'topiclist',
'submitted', 'topic' );
- preloadContent = OO.getProp( dataBlob, 'blocks', 'topiclist',
'submitted', 'content' );
- preloadFormat = OO.getProp( dataBlob, 'blocks', 'topiclist',
'submitted', 'format' );
- if ( preloadTopic || preloadContent ) {
- newTopicWidget.preload( preloadTopic, preloadContent,
preloadFormat );
}
// Show the board
- finishLoading();
+ initializer.finishLoading();
} );
}( jQuery ) );
diff --git a/modules/mw.flow.Initializer.js b/modules/mw.flow.Initializer.js
new file mode 100644
index 0000000..c18f566
--- /dev/null
+++ b/modules/mw.flow.Initializer.js
@@ -0,0 +1,902 @@
+( function ( $ ) {
+ /**
+ * Initializer object for flow-initialize
+ * @class
+ *
+ * @constructor
+ * @param {Object} config Configuration object
+ */
+ mw.flow.Initializer = function ( config ) {
+ config = config || {};
+
+ this.$component = null;
+ this.$board = null;
+ this.siderailCollapsed = mw.user.options.get(
'flow-side-rail-state' ) === 'collapsed';
+ this.pageTitle = config.pageTitle || mw.Title.newFromText(
mw.config.get( 'wgPageName' ) );
+
+ this.system = null;
+ this.board = null;
+ this.navWidget = null;
+ };
+
+ /* Inheritance */
+
+ OO.initClass( mw.flow.Initializer );
+
+ /**
+ * Sets the DOM element that is the flow component
+ *
+ * @param {jQuery} $component The DOM element that is the component
+ * @return {boolean} The component DOM element exists and is set
+ */
+ mw.flow.Initializer.prototype.setComponentDom = function ( $component )
{
+ if ( !$component || !$component.length ) {
+ return false;
+ }
+ this.$component = $component;
+ return true;
+ };
+
+ /**
+ * Sets the DOM element that is the flow board
+ *
+ * @param {jQuery} $board The DOM element that is the board
+ * @return {boolean} The board DOM element exists and is set
+ */
+ mw.flow.Initializer.prototype.setBoardDom = function ( $board ) {
+ if ( !$board || !$board.length ) {
+ return false;
+ }
+ this.$board = $board;
+ return true;
+ };
+
+ /**
+ * Set the flowBoard object representing the 'old' Flow system board
+ *
+ * @param {Object} board flowBoard _RecursiveConstructor
+ */
+ mw.flow.Initializer.prototype.setBoardObject = function ( board ) {
+ var self = this;
+
+ this.flowBoard = board;
+
+ this.flowBoard.connect( this, {
+ loadmore: function ( topiclist ) {
+ // Add to dm board
+ if ( self.system ) {
+
self.system.populateBoardTopicsFromJson( topiclist );
+ }
+
+ // Replace reply forms
+ self.replaceReplyForms( self.$board );
+ // Deactivate reply links
+ self.setupReplyLinkActions( self.$board );
+ },
+ // HACK: Update the DM when topic is refreshed
+ refreshTopic: function ( workflowId, topicData ) {
+ var revisionId, revision,
+ topic = self.board.getItemById(
workflowId ),
+ data = topicData.flow[ 'view-topic'
].result.topic;
+
+ if ( !topic ) {
+ // New topic
+
mw.flow.system.populateBoardTopicsFromJson( data, 0 );
+ } else {
+ // Topic already exists. Repopulate
+ revisionId = data.posts[ workflowId ];
+ revision = data.revisions[ revisionId ];
+ topic.populate( revision );
+ }
+
+ // Replace reply forms
+ self.replaceReplyForms( topicData.$topic );
+ // Deactivate reply links
+ self.setupReplyLinkActions( topicData.$topic );
+ }
+ } );
+ };
+
+ /**
+ * Set up the window overlay
+ */
+ mw.flow.Initializer.prototype.setupWindowOverlay = function () {
+ // Set up window overlay
+ $( 'body' ).append( mw.flow.ui.windowOverlay.$element );
+ mw.flow.ui.windowOverlay.$element.append(
mw.flow.ui.windowManager.$element );
+ };
+
+ /**
+ * Set up the sidebar widget if needed
+ */
+ mw.flow.Initializer.prototype.setupSidebarWidget = function () {
+ var sidebarExpandWidget;
+
+ if (
+ this.$component.hasClass( 'topic-page' ) &&
+ $( 'body' ).hasClass( 'action-view' )
+ ) {
+ this.$board.toggleClass( 'flow-board-expanded',
this.siderailCollapsed );
+
+ // We are in single-topic view. Initialize the sidebar
expand widget
+ sidebarExpandWidget = new
mw.flow.ui.SidebarExpandWidget( {
+ collapsed: this.siderailCollapsed,
+ expandedButtonTitle: mw.msg(
'flow-topic-collapse-siderail' ),
+ collapsedButtonTitle: mw.msg(
'flow-topic-expand-siderail' )
+ } );
+ sidebarExpandWidget.$element.insertAfter( this.$board );
+
+ // Events
+ sidebarExpandWidget.on( 'toggle', function ( collapsed
) {
+ this.$board.toggleClass( 'flow-board-expanded',
collapsed );
+ } );
+ }
+ };
+
+ /**
+ * Initialize the UI widgets
+ */
+ mw.flow.Initializer.prototype.initializeWidgets = function () {
+ // Set up window overlay
+ this.setupWindowOverlay();
+
+ // Set up sidebar widget if it needs to be there
+ this.setupSidebarWidget();
+
+ // Set up navigation widget
+ this.setupNavigationWidget( $( '.flow-board-navigation' ) );
+
+ // Set up new topic widget
+ this.setupNewTopicWidget( $( 'form.flow-newtopic-form' ) );
+
+ // Set up description widget
+ this.setupDesciptionWidget( $(
'.flow-ui-boardDescriptionWidget' ) );
+
+ // Replace reply forms on the board
+ this.replaceReplyForms( this.$board );
+
+ /* Take over click actions */
+ this.setupReplyLinkActions( this.$board );
+ this.setupEditPostAction( this.$board );
+ this.setupEditTopicSummaryAction();
+ };
+
+ /**
+ * Initialize the 'old' Flow ui component
+ */
+ mw.flow.Initializer.prototype.initOldComponent = function () {
+ if ( this.$component ) {
+ mw.flow.initComponent( this.$component );
+ }
+ };
+
+ /**
+ * Initialize the data model objects
+ * @param {Object} config Configuration options for the
mw.flow.dm.System
+ */
+ mw.flow.Initializer.prototype.initDataModel = function ( config ) {
+ var self = this;
+
+ this.system = new mw.flow.dm.System( config );
+ this.board = this.system.getBoard();
+ // Initialize the old system to accept the default
+ // order for the topic order widget
+ this.flowBoard.topicIdSort = this.board.getSortOrder();
+
+ // Events
+ this.board.connect( this, {
+ add: function ( newItems ) {
+ var i, len, item, itemId;
+
+ for ( i = 0, len = newItems.length; i < len;
i++ ) {
+ item = newItems[ i ];
+ itemId = item.getId();
+
+ if ( $.inArray( itemId,
self.flowBoard.orderedTopicIds ) === -1 ) {
+
self.flowBoard.orderedTopicIds.push( itemId );
+ }
+
+ self.flowBoard.topicTitlesById[ itemId
] = item.getContent();
+
self.flowBoard.updateTimestampsByTopicId[ itemId ] = item.getLastUpdate();
+ }
+ self.flowBoard.sortTopicIds( self.flowBoard );
+ },
+ // E.g. on topic re-order, before re-population.
+ clear: function () {
+ self.flowBoard.orderedTopicIds = [];
+ self.flowBoard.topicTitlesById = {};
+ }
+ // We shouldn't have to worry about 'remove', since by
the time we have filtering,
+ // orderedTopicIds should be gone.
+ } );
+ };
+
+ /**
+ * Get the data model system
+ *
+ * @return {mw.flow.dm.System} DM system
+ */
+ mw.flow.Initializer.prototype.getDataModelSystem = function () {
+ return this.system;
+ };
+
+ /**
+ * Populate the data model
+ *
+ * @param {Object} dataBlob Data blob to populate the system with
+ */
+ mw.flow.Initializer.prototype.populateDataModel = function ( dataBlob )
{
+ var preloadTopic = OO.getProp( dataBlob, 'blocks', 'topiclist',
'submitted', 'topic' ),
+ preloadContent = OO.getProp( dataBlob, 'blocks',
'topiclist', 'submitted', 'content' ),
+ preloadFormat = OO.getProp( dataBlob, 'blocks',
'topiclist', 'submitted', 'format' );
+
+ if ( dataBlob && dataBlob.blocks ) {
+ // Populate the rendered topics or topic (if we are in
a single-topic view)
+ this.system.populateBoardTopicsFromJson(
dataBlob.blocks.topiclist || dataBlob.blocks.topic );
+ // Populate header
+ this.system.populateBoardDescriptionFromJson(
dataBlob.blocks.header || {} );
+ // Populate the ToC topics
+ if ( dataBlob.toc ) {
+ this.system.populateBoardTopicsFromJson(
dataBlob.toc );
+ }
+ } else {
+ this.system.populateBoardFromApi();
+ }
+ if ( preloadTopic || preloadContent ) {
+ this.newTopicWidget.preload( preloadTopic,
preloadContent, preloadFormat );
+ }
+ };
+
+ /**
+ * Set up the navigation widget and its events
+ *
+ * @param {jQuery} $navDom Navigation widget DOM element
+ */
+ mw.flow.Initializer.prototype.setupNavigationWidget = function (
$navDom ) {
+ var self = this;
+
+ if ( !$navDom.length ) {
+ return;
+ }
+
+ this.navWidget = new mw.flow.ui.NavigationWidget( this.system, {
+ defaultSort: this.flowBoard.topicIdSort
+ } );
+ $navDom.append( this.navWidget.$element );
+
+ // Events
+ // Load a topic from the ToC that isn't rendered on
+ // the page yet. This will be gone once board, topic
+ // and post are widgetized.
+ this.navWidget.connect( this, {
+ loadTopic: function ( topicId ) {
+ self.flowBoard.jumpToTopic( topicId );
+ },
+ reorderTopics: function ( newOrder ) {
+ self.flowBoard.topicIdSort = newOrder;
+ }
+ } );
+
+ // Connect to system events
+
+ // HACK: These event handlers should be in the prospective
widgets
+ // they will move once we have Board UI and Topic UI widgets
+ this.system.connect( this, {
+ resetBoardStart: function () {
+ self.$component.addClass( 'flow-api-inprogress'
);
+ // Before we reinitialize the board we have to
detach
+ // the navigation widget. This should not be
necessary when
+ // the board and topics are OOUI widgets
+ self.navWidget.$element.detach();
+ },
+ resetBoardEnd: function ( data ) {
+ var $rendered;
+
+ // HACK: Trim the data for only the rendered
topics
+ // We can't have the API request use a limit
because when we
+ // rebuild the board (when we reorder) we want
the full 50
+ // topics for the ToC widget. When the topic-
and board-widget
+ // are operational, we should render properly
without using this
+ // hack.
+ data.topiclist.roots =
data.topiclist.roots.splice( 0, mw.flow.system.getRenderedTopics() );
+ $rendered = $(
+
mw.flow.TemplateEngine.processTemplateGetFragment(
+ 'flow_block_loop',
+ { blocks: data }
+ )
+ ).children();
+ // Run this on a short timeout so that the
other board handler in FlowBoardComponentLoadMoreFeatureMixin can run
+ // TODO: Using a timeout doesn't seem like the
right way to do this.
+ setTimeout( function () {
+ // Reinitialize the whole board with
these nodes
+ self.$board.empty().append( $rendered[
1 ] );
+
+ // Since we've replaced the entire
board, we need to reinitialize
+ // it. This also takes away the
original navWidget, so we need to
+ // make sure it's reinitialized too
+ self.flowBoard.reinitializeContainer(
$rendered );
+ $( '.flow-board-navigation' ).append(
self.navWidget.$element );
+
+ self.$component.removeClass(
'flow-api-inprogress' );
+ }, 50 );
+ }
+ } );
+ };
+
+ /**
+ * Set up the new topic widget and its events
+ *
+ * @param {jQuery} $form New topic form DOM element
+ */
+ mw.flow.Initializer.prototype.setupNewTopicWidget = function ( $form ) {
+ var self = this;
+
+ this.newTopicWidget = new mw.flow.ui.NewTopicWidget(
this.pageTitle.getPrefixedDb() );
+
+ // Events
+ this.newTopicWidget.connect( this, {
+ save: function ( newTopicId ) {
+ // Display the new topic with the old system
+ var $stub = $( '<div
class="flow-topic"><div></div></div>' ).prependTo(
self.flowBoard.$container.find( '.flow-topics' ) );
+ return
this.flowBoard.flowBoardComponentRefreshTopic( $stub.find( 'div' ), newTopicId
);
+ }
+ } );
+
+ $form.replaceWith( this.newTopicWidget.$element );
+ };
+
+ /**
+ * Set up the description widget and its events
+ *
+ * @param {jQuery} $element Description DOM element
+ */
+ mw.flow.Initializer.prototype.setupDesciptionWidget = function (
$element ) {
+ var descriptionWidget;
+
+ if ( !$element.length ) {
+ return;
+ }
+
+ descriptionWidget = new mw.flow.ui.BoardDescriptionWidget(
this.board, {
+ $existing: $( '.flow-ui-boardDescriptionWidget-content'
).contents(),
+ $categories: $( '.flow-board-header-category-view-nojs'
).contents()
+ } );
+
+ // The category widget is inside the board description widget.
+ // Remove it from the nojs version here
+ $( '.flow-board-header-category-view-nojs' ).detach();
+ // HACK: Remove the MW page categories
+ $( '.catlinks:not(.flow-ui-categoriesWidget)' ).detach();
+
+ $element.replaceWith( descriptionWidget.$element );
+ };
+
+ /**
+ * Replace the reply forms given by the php version with js editors
+ *
+ * @param {jQuery} $element The element to conduct the replacements in
+ */
+ mw.flow.Initializer.prototype.replaceReplyForms = function ( $element )
{
+ var self = this;
+
+ if ( !$element || !$element.length ) {
+ return;
+ }
+
+ $element.find( '.flow-post.flow-reply-form' ).each( function ()
{
+ var $topic = $( this ).parent(),
+ placeholder = mw.msg(
'flow-reply-topic-title-placeholder', $topic.find( '.flow-topic-title'
).text().trim() ),
+ replyTo = $( this ).find(
'input[name="topic_replyTo"]' ).val(),
+ replyWidget = new mw.flow.ui.ReplyWidget(
$topic.data( 'flowId' ), replyTo, {
+ placeholder: placeholder
+ } );
+
+ replyWidget.on( 'saveContent', function ( workflow ) {
+ replyWidget.destroy();
+ replyWidget.$element.remove();
+
+ // HACK: get the old system to rerender the
topic
+ return
self.flowBoard.flowBoardComponentRefreshTopic(
+ $topic,
+ workflow
+ );
+ } );
+ replyWidget.$element.data( 'self', replyWidget );
+
+ // Replace the reply form with the new editor widget
+ $( this ).replaceWith( replyWidget.$element );
+ } );
+ };
+
+ /**
+ * Take over the action of the 'edit post' links
+ *
+ * @param {jQuery} $element The element to conduct the replacements in
+ */
+ mw.flow.Initializer.prototype.setupEditPostAction = function ( $element
) {
+ $element.on( 'click', '.flow-ui-edit-post-link', function (
event ) {
+ var editPostWidget,
+ $topic = $( this ).closest( '.flow-topic' ),
+ topicId = $topic.data( 'flow-id' ),
+ $post = $( this ).closest( '.flow-post' ),
+ $postMain = $post.children( '.flow-post-main' ),
+ postId = $post.data( 'flow-id' ),
+ $board = $( '.flow-board' ),
+ flowBoard = mw.flow.getPrototypeMethod(
'component', 'getInstanceByElement' )( $board );
+
+ editPostWidget = new mw.flow.ui.EditPostWidget(
topicId, postId );
+ editPostWidget
+ .on( 'saveContent', function ( workflow ) {
+ editPostWidget.destroy();
+ editPostWidget.$element.remove();
+
+ // HACK get the old system to rerender
the topic
+ return
flowBoard.flowBoardComponentRefreshTopic(
+ $topic,
+ workflow
+ );
+ } )
+ .on( 'cancel', function () {
+ editPostWidget.destroy();
+ editPostWidget.$element.replaceWith(
$postMain );
+ } );
+
+ $postMain.replaceWith( editPostWidget.$element );
+
+ event.preventDefault();
+ } );
+ };
+
+ /**
+ * Take over the action of the 'edit topic summary' links
+ */
+ mw.flow.Initializer.prototype.setupEditTopicSummaryAction = function ()
{
+ var self = this;
+
+ this.$board
+ // Summarize action
+ .on( 'click', '.flow-ui-summarize-topic-link', function
( event ) {
+ var $topic = $( this ).closest( '.flow-topic' ),
+ topicId = $topic.data( 'flow-id' );
+
+ self.startEditTopicSummary( true, topicId );
+ event.preventDefault();
+ } )
+ // Lock action
+ .on( 'click', '.flow-ui-topicmenu-lock', function () {
+ var promise,
+ action = $( this ).data( 'role' ),
+ $topic = $( this ).closest(
'.flow-topic' ),
+ topicId = $topic.data( 'flow-id' ),
+ api = new mw.flow.dm.APIHandler();
+
+ if ( action === 'lock' ) {
+ promise = api.resolveTopic( topicId );
+ } else {
+ promise = api.reopenTopic( topicId );
+ }
+
+ promise
+ .then( function ( workflow ) {
+ return
self.flowBoard.flowBoardComponentRefreshTopic(
+ $topic,
+ workflow
+ );
+ } )
+ .then( function ( data ) {
+ var revisionId =
data.topic.posts[ topicId ],
+ revision =
data.topic.revisions[ revisionId ],
+ summaryContent =
OO.getProp( revision, 'summary', 'revision', 'content', 'content' ),
+ skipSummarize = action
=== 'unlock' && !summaryContent;
+
+ if ( !skipSummarize ) {
+
self.startEditTopicSummary( true, topicId, action );
+ }
+ } );
+
+ // Prevent default
+ return false;
+ } );
+ };
+
+ /**
+ * Take over the action of the 'reply' links
+ *
+ * @param {jQuery} $element The element to conduct the replacements in
+ */
+ mw.flow.Initializer.prototype.setupReplyLinkActions = function (
$element ) {
+ var self = this;
+
+ // Cancel the interactive handler so "old" system doesn't get
triggered for internal replies
+ $element.find( 'a.flow-reply-link' ).each( function () {
+ // Store the needed details so we can get rid of the
URL in JS mode
+ var href = $( this ).attr( 'href' ),
+ uri = new mw.Uri( href ),
+ postId = uri.query.topic_postId;
+
+ $( this )
+ .data( 'postId', postId )
+ .attr( 'data-flow-interactive-handler', '' )
+ .attr( 'href', '' )
+ .addClass( 'flow-ui-reply-link-trigger' )
+ .on( 'click', function () {
+ var replyWidget,
+ $topic = $( this ).closest(
'.flow-topic' ),
+ placeholder = mw.msg(
'flow-reply-topic-title-placeholder', $topic.find( '.flow-topic-title'
).text().trim() ),
+ replyTo = $( this ).data(
'postId' ),
+ // replyTo can refer to a post
ID or a topic ID
+ // For posts, the ReplyWidget
should go in .flow-replies
+ // For topics, it's directly
inside the topic
+ $targetContainer = $(
'#flow-post-' + replyTo + ' > .flow-replies, #flow-topic-' + replyTo ),
+ $existingWidget =
$targetContainer.children( '.flow-ui-replyWidget' );
+
+ // Check that there's not already a
reply widget existing in the same place
+ if ( $existingWidget.length > 0 ) {
+ // Focus the existing reply
widget
+ $existingWidget.data( 'self'
).activateEditor();
+ $existingWidget.data( 'self'
).focus();
+ return false;
+ }
+
+ replyWidget = new
mw.flow.ui.ReplyWidget( $topic.data( 'flowId' ), replyTo, {
+ placeholder: placeholder,
+ expandable: false
+ } );
+ // Create a reference so we can call it
from the DOM above
+ replyWidget.$element.data( 'self',
replyWidget );
+
+ // Add reply form below the post being
replied to (WRT max depth)
+ $targetContainer.append(
replyWidget.$element );
+ replyWidget.activateEditor();
+
+ replyWidget
+ .on( 'saveContent', function (
workflow ) {
+ replyWidget.destroy();
+
replyWidget.$element.remove();
+
+ // HACK get the old
system to rerender the topic
+ return
self.flowBoard.flowBoardComponentRefreshTopic(
+ $topic,
+ workflow
+ );
+ } )
+ .on( 'cancel', function () {
+ replyWidget.destroy();
+
replyWidget.$element.remove();
+ } );
+
+ return false;
+ } );
+ } );
+
+ };
+
+ /**
+ * Initialize the edit topic summary action
+ *
+ * @param {boolean} isFullBoard The page is a full board page
+ * @param {string} topicId Topic id
+ * @param {string} [action] Lock action 'lock' or 'unlock'. If not
given, the action
+ * is assumed as summary only.
+ */
+ mw.flow.Initializer.prototype.startEditTopicSummary = function (
isFullBoard, topicId, action ) {
+ var editTopicSummaryWidget,
+ self = this,
+ $topic = $( '#flow-topic-' + topicId ),
+ $summaryContainer = $topic.find(
'.flow-topic-summary-container' ),
+ $topicSummary = $summaryContainer.find(
'.flow-topic-summary' ),
+ options = {},
+ pageName = mw.config.get( 'wgPageName' ),
+ title = mw.Title.newFromText( pageName );
+
+ if ( !$summaryContainer.length ) {
+ return;
+ }
+
+ // TODO: This should be managed by the EditTopicSummary widget
+ if ( action === 'lock' || action === 'unlock' ) {
+ options = {
+ cancelMsgKey: 'flow-skip-summary'
+ };
+ }
+
+ editTopicSummaryWidget = new mw.flow.ui.EditTopicSummaryWidget(
topicId, options );
+ editTopicSummaryWidget
+ .on( 'saveContent', function ( workflow ) {
+ editTopicSummaryWidget.destroy();
+ editTopicSummaryWidget.$element.remove();
+
+ if ( isFullBoard ) {
+ // HACK get the old system to rerender
the topic
+ return
self.flowBoard.flowBoardComponentRefreshTopic(
+ $topic,
+ workflow
+ );
+ } else {
+ // HACK: redirect to topic view
+ window.location.href = title.getUrl();
+ }
+ } )
+ .on( 'cancel', function () {
+ editTopicSummaryWidget.destroy();
+ editTopicSummaryWidget.$element.remove();
+ if ( isFullBoard ) {
+ $summaryContainer.append( $topicSummary
);
+ } else {
+ // HACK: redirect to topic view
+ window.location.href = title.getUrl();
+ }
+ } );
+
+ $topicSummary.remove();
+ $summaryContainer.append( editTopicSummaryWidget.$element );
+ };
+
+ /**
+ * Replace the editor in no-js pages, like editing in a separate window
+ *
+ * @param {jQuery} $element The element to conduct the replacements in
+ */
+ mw.flow.Initializer.prototype.replaceNoJSEditor = function ( $element )
{
+ var editPostWidget,
+ $post = $element.parent(),
+ $topic = $post.closest( '.flow-topic' );
+
+ function saveOrCancelHandler( workflow ) {
+ editPostWidget.destroy();
+ editPostWidget.$element.remove();
+
+ // HACK get the old system to rerender the topic
+ return this.flowBoard.flowBoardComponentRefreshTopic(
+ $topic,
+ workflow
+ );
+ }
+
+ if ( !$element.length ) {
+ return;
+ }
+
+ editPostWidget = new mw.flow.ui.EditPostWidget( $topic.data(
'flowId' ), $post.data( 'flowId' ) );
+
+ editPostWidget
+ .on( 'saveContent', saveOrCancelHandler )
+ // HACK: In this case, we are in an edge case where the
topic already
+ // loaded with the editor open. We can't trust the
content of the editor
+ // for displaying the post in case of a 'cancel' event
and we don't have
+ // the actual content stored in the DOM anywhere else.
+ // We must reload the topic -- just like we do on save
-- for a cancel
+ // event too.
+ .on( 'cancel', saveOrCancelHandler.bind( null,
$topic.data( 'flowId' ) ) );
+
+ $element.replaceWith( editPostWidget.$element );
+ };
+
+ /**
+ * Create an editor widget
+ *
+ * @param {jQuery} $domToReplace The element, usually a form, that the
new editor replaces
+ * @param {string} [content] The content of the editing area
+ * @param {string} [saveMsgKey] The message key for the editor save
button
+ */
+ mw.flow.Initializer.prototype.createEditorWidget = function (
$domToReplace, content, saveMsgKey ) {
+ var $wrapper,
+ anonWarning = new mw.flow.ui.AnonWarningWidget(),
+ error = new OO.ui.LabelWidget( {
+ classes: [
'flow-ui-boardDescriptionWidget-error flow-errors errorbox' ]
+ } ),
+ editor = new mw.flow.ui.EditorWidget( {
+ saveMsgKey: saveMsgKey
+ } );
+
+ error.toggle( false );
+ editor.toggle( true );
+ anonWarning.toggle( mw.user.isAnon() );
+
+ // HACK: We still need a reference to the error widget, for
+ // the api responses in the intialized widgets that use this
+ // function, so make a forced connection
+ editor.error = error;
+
+ // Prepare the editor
+ editor.pushPending();
+ editor.activate();
+
+ editor.setContent( content, 'wikitext' )
+ .then( function () {
+ editor.popPending();
+ } );
+
+ editor
+ .on( 'saveContent', function ( content, contentFormat )
{
+ var $captchaField, captcha;
+
+ editor.pushPending();
+
+ $captchaField = error.$label.find(
'[name="wpCaptchaWord"]' );
+ if ( $captchaField.length > 0 ) {
+ captcha = {
+ id: this.error.$label.find(
'[name="wpCaptchaId"]' ).val(),
+ answer: $captchaField.val()
+ };
+ }
+ error.setLabel( '' );
+ error.toggle( false );
+
+ // HACK: This is a cheat so that we can have a
single function
+ // that creates the editor, but multiple uses,
especially for the
+ // APIhandler in different cases
+ editor.emit( 'afterSaveContent', content,
contentFormat, captcha );
+ } )
+ .on( 'cancel', function () {
+ editor.pushPending();
+ editor.emit( 'afterCancel' );
+ // returnToBoard();
+ } );
+
+ $wrapper = $( '<div>' )
+ .append(
+ error.$element,
+ anonWarning.$element,
+ editor.$element
+ );
+ $domToReplace.replaceWith( $wrapper );
+
+ return editor;
+ };
+
+ /**
+ * Check whether we are on a diff page
+ *
+ * @return {boolean} The page is a diff page
+ */
+ mw.flow.Initializer.prototype.isDiffPage = function () {
+ return !!( $( 'form[data-module="topic"]' ).length ||
+ $( 'form[data-module="header"]' ).length );
+ };
+
+ /**
+ * Set up editors in undo pages
+ */
+ mw.flow.Initializer.prototype.setupUndoPage = function () {
+ if ( $( 'form[data-module="topic"]' ).length ) {
+ this.replaceEditorInUndoEditPost( $(
'form[data-module="topic"]' ) );
+ } else if ( $( 'form[data-module="header"]' ).length ) {
+ this.replaceEditorInUndoHeaderPost( $(
'form[data-module="header"]' ) );
+ }
+ };
+
+ /**
+ * Replace the editor in undo edit post pages
+ *
+ * @param {jQuery} $form The form where the no-js editor exists to be
replaced
+ */
+ mw.flow.Initializer.prototype.replaceEditorInUndoEditPost = function (
$form ) {
+ var apiHandler, content, postId, editor, prevRevId,
+ pageName = mw.config.get( 'wgPageName' ),
+ title = mw.Title.newFromText( pageName ),
+ topicId = title.getNameText(),
+ returnToTitle = function () {
+ // HACK: redirect to topic view
+ window.location.href = title.getUrl();
+ };
+
+ if ( !$form.length ) {
+ return;
+ }
+
+ postId = $form.find( 'input[name="topic_postId"]' ).val();
+ prevRevId = $form.find( 'input[name="topic_prev_revision"]'
).val();
+ content = $form.find( 'textarea' ).val();
+
+ apiHandler = new mw.flow.dm.APIHandler(
+ 'Topic:' + topicId,
+ {
+ currentRevision: prevRevId
+ }
+ );
+
+ // Create the editor
+ editor = this.createEditorWidget(
+ $form,
+ content,
+ mw.user.isAnon() ?
'flow-post-action-edit-post-submit-anonymously' :
'flow-post-action-edit-post-submit'
+ );
+
+ // Events
+ editor
+ .on( 'afterSaveContent', function ( content,
contentFormat, captcha ) {
+ apiHandler.savePost( topicId, postId, content,
contentFormat, captcha )
+ .then(
+ // Success
+ returnToTitle,
+ // Failure
+ function ( errorCode, errorObj
) {
+ if (
/spamfilter$/.test( errorCode ) && errorObj.error.spamfilter ===
'flow-spam-confirmedit-form' ) {
+
editor.error.setLabel(
+ //
CAPTCHA form
+ new
OO.ui.HtmlSnippet( errorObj.error.info )
+ );
+ } else {
+
editor.error.setLabel( errorObj.error && errorObj.error.info ||
errorObj.exception );
+ }
+
+ editor.error.toggle(
true );
+ editor.popPending();
+ }
+ );
+ } )
+ .on( 'afterCancel', returnToTitle );
+ };
+
+ /**
+ * Replace the editor in undo edit header pages
+ *
+ * @param {jQuery} $form The form where the no-js editor exists to be
replaced
+ */
+ mw.flow.Initializer.prototype.replaceEditorInUndoHeaderPost = function
( $form ) {
+ var prevRevId, editor, content,
+ error, apiHandler,
+ pageName = mw.config.get( 'wgPageName' ),
+ title = mw.Title.newFromText( pageName ),
+ returnToBoard = function () {
+ window.location.href = title.getUrl();
+ };
+
+ if ( !$form.length ) {
+ return;
+ }
+
+ prevRevId = $form.find( 'input[name="header_prev_revision"]'
).val();
+ content = $form.find( 'textarea[name="header_content"]' ).val();
+
+ apiHandler = new mw.flow.dm.APIHandler(
+ title.getPrefixedDb(),
+ {
+ currentRevision: prevRevId
+ }
+ );
+
+ // Create the editor
+ editor = this.createEditorWidget(
+ $form,
+ content,
+ mw.user.isAnon() ?
'flow-edit-header-submit-anonymously' : 'flow-edit-header-submit'
+ );
+
+ // Events
+ editor
+ .on( 'afterSaveContent', function ( content,
contentFormat, captcha ) {
+ apiHandler.saveDescription( content,
contentFormat, captcha )
+ .then(
+ // Success
+ returnToBoard,
+ // Failure
+ function ( errorCode, errorObj
) {
+ if (
/spamfilter$/.test( errorCode ) && errorObj.error.spamfilter ===
'flow-spam-confirmedit-form' ) {
+ error.setLabel(
+ //
CAPTCHA form
+ new
OO.ui.HtmlSnippet( errorObj.error.info )
+ );
+ } else {
+ error.setLabel(
errorObj.error && errorObj.error.info || errorObj.exception );
+ }
+ editor.popPending();
+ }
+ );
+ } )
+ .on( 'afterCancel', function () {
+ returnToBoard();
+ } );
+ };
+
+ /**
+ * Finish the loading process
+ */
+ mw.flow.Initializer.prototype.finishLoading = function () {
+ if ( this.$component ) {
+ this.$component.addClass( 'flow-component-ready' );
+ }
+ $( '.flow-ui-load-overlay' ).addClass( 'oo-ui-element-hidden' );
+ };
+}( jQuery ) );
--
To view, visit https://gerrit.wikimedia.org/r/248082
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I315d7c423eaeaa0686f748644c460967fb2c7b9e
Gerrit-PatchSet: 12
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Mooeypoo <[email protected]>
Gerrit-Reviewer: Sbisson <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits