diff --git a/VisualEditor.php b/VisualEditor.php
index 5ed04d65a7..c315d0bc9a 100644
--- a/VisualEditor.php
+++ b/VisualEditor.php
@@ -126,12 +126,12 @@ $wgResourceModules += array(
// ve
'jquery/jquery.json.js',
've2/ve.js',
+ 've2/ve.EventEmitter.js',
)
),
'ext.visualEditor.core' => $wgVisualEditorResourceTemplate + array(
'scripts' => array(
// ve
- 've2/ve.EventEmitter.js',
've2/ve.Factory.js',
've2/ve.Position.js',
've2/ve.Range.js',
diff --git a/modules/ve2/ce/styles/ve.ce.Node.css b/modules/ve2/ce/styles/ve.ce.Node.css
index d3d96ace14..27e8520f86 100644
--- a/modules/ve2/ce/styles/ve.ce.Node.css
+++ b/modules/ve2/ce/styles/ve.ce.Node.css
@@ -25,3 +25,8 @@
.ve-ce-branchNode p:empty:before {
content: url('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
}
+
+li.ve-ce-branchNode p.ve-ce-branchNode:first-child {
+ margin: 0;
+ padding: 0;
+}
\ No newline at end of file
diff --git a/modules/ve2/init/styles/ve.init.ViewPageTarget.css b/modules/ve2/init/styles/ve.init.ViewPageTarget.css
index c6d7397a3b..a07b752052 100644
--- a/modules/ve2/init/styles/ve.init.ViewPageTarget.css
+++ b/modules/ve2/init/styles/ve.init.ViewPageTarget.css
@@ -1,9 +1,9 @@
+.ve-surface {
+ margin-top: 0.8em;
+}
+
.es-toolbar-wrapper {
margin: -1em -1em 1em -1em;
- transition: margin 250ms, height 250ms;
- -moz-transition: margin 250ms, height 250ms;
- -webkit-transition: margin 250ms, height 250ms;
- -o-transition: margin 250ms, height 250ms;
}
.es-toolbar {
@@ -31,7 +31,7 @@
float: right;
}
-.ve-init-viewPageTarget-button {
+.ve-init-viewPageTarget-toolbar-saveButton {
display: inline-block;
border: solid 1px
transparent;
@@ -47,40 +47,31 @@
border: solid 1px transparent;
}
-.ve-init-viewPageTarget-button:before {
+.ve-init-viewPageTarget-toolbar-saveButton:before {
content: " ";
position: absolute;
display: block;
height: 22px;
}
-.ve-init-viewPageTarget-button:hover {
+.ve-init-viewPageTarget-toolbar-saveButton:hover {
border-color: #eeeeee;
}
-.ve-init-viewPageTarget-button:active,
-.ve-init-viewPageTarget-button-down {
+.ve-init-viewPageTarget-toolbar-saveButton:active,
+.ve-init-viewPageTarget-toolbar-saveButton-down {
border-color: #dddddd;
-webkit-box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07);
-moz-box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07);
box-shadow: inset 0px 1px 4px 0px rgba(0, 0, 0, 0.07);
}
-.ve-init-viewPageTarget-button-disabled {
+.ve-init-viewPageTarget-toolbar-saveButton-disabled {
opacity: 0.5;
-moz-opacity: 0.5;
}
-/* inspector styles */
-.ve-init-viewPageTarget-saveButton {
- border: 1px solid transparent;
- border-radius: 0.125em;
- -webkit-border-radius: 0.125em;
- -moz-border-radius: 0.125em;
- -o-border-radius: 0.125em;
-}
-
-.ve-init-viewPageTarget-saveButton-icon {
+.ve-init-viewPageTarget-toolbar-saveButton-icon {
display: inline-block;
vertical-align: middle;
width: 32px;
@@ -91,7 +82,7 @@
background-repeat: no-repeat;
}
-.ve-init-viewPageTarget-saveButton-label {
+.ve-init-viewPageTarget-toolbar-saveButton-label {
display: inline-block;
vertical-align: middle;
height: 32px;
@@ -110,36 +101,39 @@
.ve-init-viewPageTarget-saveDialog-saveButton {
position: absolute;
- border: 1px solid rgb(196,229,154);
+ cursor: pointer;
+ border: 1px solid #c3e59a;
margin-top: 10px;
right: 10px;
font-size: 1em;
padding: 0.5em 1em;
border-radius: 0.25em;
-moz-border-radius: 0.25em;
- background-image: url(../../ui/styles/images/close.png);
- background-position: center right;
- background-repeat: no-repeat;
/* Fancy CSS background */
- background-image: linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
- background-image: -o-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
- background-image: -moz-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
- background-image: -webkit-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
- background-image: -ms-linear-gradient(bottom, rgb(195,229,154) 0%, rgb(240,251,225) 100%);
+ background-image: linear-gradient(bottom, #c3e59a 0%, #f0fbe1 100%);
+ background-image: -o-linear-gradient(bottom, #c3e59a 0%, #f0fbe1 100%);
+ background-image: -moz-linear-gradient(bottom, #c3e59a 0%, #f0fbe1 100%);
+ background-image: -webkit-linear-gradient(bottom, #c3e59a 0%, #f0fbe1 100%);
+ background-image: -ms-linear-gradient(bottom, #c3e59a 0%, #f0fbe1 100%);
background-image: -webkit-gradient(
linear,
left bottom,
left top,
- color-stop(0, rgb(195,229,154)),
- color-stop(1, rgb(240,251,225))
+ color-stop(0, #c3e59a),
+ color-stop(1, #f0fbe1)
);
}
+.ve-init-viewPageTarget-saveDialog-saveButton:hover {
+ border-color: #a7cd76;
+}
+
.ve-init-viewPageTarget-saveDialog-saveButton-icon {
display: inline-block;
vertical-align: middle;
height: 2em;
width: 28px;
+ margin-right: -4px;
background: transparent;
background-image: url(../../ui/styles/images/accept-clear.png);
background-position: right top;
@@ -171,7 +165,7 @@
}
.ve-init-viewPageTarget-saveDialog input[type='text'] {
- width: 96%;
+ width: 98%;
font-size: 12px;
padding: 4px;
margin: 10px 0;
diff --git a/modules/ve2/init/targets/ve.init.ViewPageTarget.js b/modules/ve2/init/targets/ve.init.ViewPageTarget.js
index 71d5f4f7a8..db4d9e56f7 100644
--- a/modules/ve2/init/targets/ve.init.ViewPageTarget.js
+++ b/modules/ve2/init/targets/ve.init.ViewPageTarget.js
@@ -11,34 +11,35 @@ ve.init.ViewPageTarget = function() {
ve.init.Target.call( this, mw.config.get( 'wgPageName' ) );
// Properties
- this.$content = $( '#content' );
- this.$page = $( '#mw-content-text' );
- this.$view = $( '#bodyContent' );
- this.$toc = $( '#toc' );
- this.$heading = $( '#firstHeading' );
this.$surface = $( '
' );
- this.$toolbar = null;
+ this.$spinner = $( '' );
+ this.$toolbarSaveButton = $( '' );
+ this.$saveDialog = $( '' );
this.surface = null;
this.active = false;
this.edited = false;
this.activating = false;
this.deactivating = false;
+ this.scrollTop = null;
+ this.section = null;
this.proxiedOnSurfaceModelTransact = ve.proxy( this.onSurfaceModelTransact, this );
- this.surfaceOptions = {
- 'toolbars': {
- 'top': {
- // If mobile device, float false
- 'float': !this.isMobileDevice,
- // Toolbar modes
- 'modes': ['wikitext']
- }
- }
- };
+ this.surfaceOptions = { 'toolbars': { 'top': { 'float': !this.isMobileDevice } } };
+
+ // Events
+ this.addListenerMethods( this, {
+ 'load': 'onLoad',
+ 'save': 'onSave',
+ 'loadError': 'onLoadError',
+ 'saveError': 'onSaveError'
+ } );
// Initialization
if ( mw.config.get('wgCanonicalNamespace') === 'VisualEditor' ) {
// Clicking the edit tab is the only way any other code gets run, and this sets that up
- this.setupTabs();
+ this.setupSkinTabs();
+ this.setupSectionEditLinks();
+ this.setupToolbarSaveButton();
+ this.setupSaveDialog();
}
};
@@ -46,7 +47,6 @@ ve.init.ViewPageTarget = function() {
/*jshint multistr: true*/
ve.init.ViewPageTarget.saveDialogTemplate = '\
-\
\
\
\
@@ -72,103 +72,181 @@ ve.init.ViewPageTarget.saveDialogTemplate = '\
\
\
-
';
+ ';
/* Methods */
/**
- * ...
+ * Switches to edit mode.
*
* @method
*/
-ve.init.ViewPageTarget.prototype.onEditTabClick = function( e, section ) {
- // Ignore multiple clicks while editor is active
+ve.init.ViewPageTarget.prototype.activate = function() {
if ( !this.active && !this.activating ) {
this.activating = true;
- // UI updates
- this.setSelectedTab( 'ca-edit' );
+ // User interface changes
+ this.transformSkinTabs();
this.showSpinner();
- this.$toc.addClass( 've-init-viewPageTarget-pageToc' ).slideUp( 'fast' );
- // Remember scroll position
- var scrollTop = $( window ).scrollTop();
- // Asynchronous initialization - load ve modules at the same time as the content
- this.load( ve.proxy( function( error, dom ) {
- this.onLoad( error, dom );
- if ( section !== undefined ) {
- // HACK: All of this code is fragile, be careful and suspicious
- var $heading = this.$surface
- .find( '.ve-ce-documentNode' )
- .find( 'h1, h2, h3, h4, h5, h6' )
- .eq( section );
- surfaceView = this.surface.getView(),
- surfaceModel = surfaceView.getModel(),
- doc = surfaceModel.getDocument();
- if ( $heading.length ) {
- var offset = doc.getNearestContentOffset(
- $heading.data( 'node' ).getModel().getOffset()
- );
- surfaceModel.setSelection( new ve.Range( offset, offset ) );
- surfaceView.showSelection( surfaceModel.getSelection() );
- // Restore scroll position
- $( window ).scrollTop( scrollTop );
- }
- }
- }, this ) );
+ this.hideTableOfContents();
+ this.mutePageContent();
+ this.mutePageTitle();
+ this.saveScrollPosition();
+ this.load();
}
- // Prevent the edit tab's normal behavior
- e.preventDefault();
- return false;
};
/**
- * ...
+ * Switches to view mode.
*
* @method
*/
-ve.init.ViewPageTarget.prototype.onEditSectionLinkClick = function( e ) {
- var heading = $( e.target ).closest( 'h1, h2, h3, h4, h5, h6' )[0],
- tocHeading = this.$page.find( '#toc h2' )[0];
- section = 0;
- this.$page.find( 'h1, h2, h3, h4, h5, h6' ).each( function() {
- if ( this === heading ) {
- return false;
- }
- if ( this !== tocHeading ) {
- section++;
- }
- } );
- return this.onEditTabClick( e, section );
-};
-
-/**
- * ...
- *
- * @method
- */
-ve.init.ViewPageTarget.prototype.onViewTabClick = function( e ) {
- // Don't do anything special unless we are in editing mode
+ve.init.ViewPageTarget.prototype.deactivate = function() {
if ( this.active && !this.deactivating ) {
- this.deactivating = true;
if (
!this.surface.getModel().getHistory().length ||
confirm( 'Are you sure you want to go back to view mode without saving first?' )
) {
+ this.deactivating = true;
+ // User interface changes
+ this.restoreSkinTabs();
+ this.hideSpinner();
+ this.detachToolbarSaveButton();
+ this.detachSaveDialog();
this.tearDownSurface();
+ this.showTableOfContents();
this.deactivating = false;
}
- // Prevent the edit tab's normal behavior
- e.preventDefault();
- return false;
}
};
/**
- * ...
+ * Handles successful DOM load event.
*
* @method
+ * @param {HTMLElement} dom Parsed DOM from server
*/
-ve.init.ViewPageTarget.prototype.onSaveDialogSaveButtonClick = function( e ) {
+ve.init.ViewPageTarget.prototype.onLoad = function( dom ) {
+ this.edited = false;
+ this.setUpSurface( dom );
+ this.attachToolbarSaveButton();
+ this.attachSaveDialog();
+ this.restoreScrollPosition();
+ this.restoreEditSection();
+ this.$surface.find( '.ve-ce-documentNode' ).focus();
+ this.activating = false;
+};
+
+/**
+ * Handles failed DOM load event.
+ *
+ * @method
+ * @param {Object} data HTTP Response object
+ * @param {String} status Text status message
+ * @param {Mixed} error Thrown exception or HTTP error string
+ */
+ve.init.ViewPageTarget.prototype.onLoadError = function( response, status, error ) {
+ // TODO: Something...
+};
+
+/**
+ * Handles successful DOM save event.
+ *
+ * @method
+ * @param {HTMLElement} html Rendered HTML from server
+ */
+ve.init.ViewPageTarget.prototype.onSave = function( html ) {
+ this.hideSaveDialog();
+ this.replacePageContent( html );
+ this.deactivate();
+};
+
+/**
+ * Handles failed DOM save event.
+ *
+ * @method
+ * @param {Object} data HTTP Response object
+ * @param {String} status Text status message
+ * @param {Mixed} error Thrown exception or HTTP error string
+ */
+ve.init.ViewPageTarget.prototype.onSaveError = function( response, status, error ) {
+ // TODO: Something...
+};
+
+/**
+ * Handles clicks on the edit tab.
+ *
+ * @method
+ * @param {Event} e DOM event
+ */
+ve.init.ViewPageTarget.prototype.onEditTabClick = function( event ) {
+ console.log( this );
+ this.activate();
+ // Prevent the edit tab's normal behavior
+ event.preventDefault();
+ return false;
+};
+
+/**
+ * Handles clicks on a section edit link.
+ *
+ * @method
+ * @param {Event} event DOM event
+ */
+ve.init.ViewPageTarget.prototype.onEditSectionLinkClick = function( event ) {
+ this.saveEditingSection( $( event.target ).closest( 'h1, h2, h3, h4, h5, h6' )[0] );
+ this.activate();
+ // Prevent the edit tab's normal behavior
+ event.preventDefault();
+ return false;
+};
+
+/**
+ * Handles clicks on the view tab.
+ *
+ * @method
+ * @param {Event} event DOM event
+ */
+ve.init.ViewPageTarget.prototype.onViewTabClick = function( event ) {
+ console.log( this );
+ this.deactivate();
+ // Prevent the edit tab's normal behavior
+ event.preventDefault();
+ return false;
+};
+
+/**
+ * Handles clicks on the save button in the toolbar.
+ *
+ * @method
+ * @param {Event} event DOM event
+ */
+ve.init.ViewPageTarget.prototype.onToolbarSaveButtonClick = function( event ) {
+ if ( this.edited ) {
+ this.showSaveDialog();
+ }
+};
+
+/**
+ * Handles the first transaction in the surface model.
+ *
+ * This handler is removed the first time it's used, but added each time the surface is setup.
+ *
+ * @method
+ * @param {ve.Transaction} tx Processed transaction
+ */
+ve.init.ViewPageTarget.prototype.onSurfaceModelTransact = function( tx ) {
+ this.edited = true;
+ this.enableToolbarSaveButton();
+ this.surface.getModel().removeListener( 'transact', this.proxiedOnSurfaceModelTransact );
+};
+
+/**
+ * Handles clicks on the save button in the save dialog.
+ *
+ * @method
+ * @param {Event} event DOM event
+ */
+ve.init.ViewPageTarget.prototype.onSaveDialogSaveButtonClick = function( event ) {
this.showSpinner();
// Save
this.save(
@@ -183,183 +261,65 @@ ve.init.ViewPageTarget.prototype.onSaveDialogSaveButtonClick = function( e ) {
};
/**
- * ...
+ * Handles clicks on the close button in the save dialog.
*
* @method
+ * @param {Event} event DOM event
*/
-ve.init.ViewPageTarget.prototype.onSurfaceModelTransact = function() {
- if ( !this.edited ) {
- this.edited = true;
- this.$toolbar.find( '.ve-init-viewPageTarget-saveButton ' )
- .removeClass( 've-init-viewPageTarget-button-disabled' );
- this.surface.getModel().removeListener( 'transact', this.proxiedOnSurfaceModelTransact );
- }
+ve.init.ViewPageTarget.prototype.onSaveDialogCloseButtonClick = function( event ) {
+ this.hideSaveDialog();
};
/**
- * ...
- *
- * @method
- */
-ve.init.ViewPageTarget.prototype.onSaveButtonClick = function( e ) {
- if ( this.edited ) {
- this.$dialog.fadeIn( 'fast' );
- this.$dialog.find( 'input:first' ).focus();
- }
-};
-
-/**
- * ...
- *
- * @method
- */
-ve.init.ViewPageTarget.prototype.onSaveDialogCloseButtonClick = function( e ) {
- this.$dialog.fadeOut( 'fast' );
- this.$surface.find( '.ve-ce-documentNode' ).focus();
-};
-
-/**
- * ...
- *
- * @method
- */
-ve.init.ViewPageTarget.prototype.onLoad = function( error, dom ) {
- this.activating = false;
- if ( error ) {
- // TODO: Error handling in the UI
- } else {
- this.edited = false;
- this.setUpSurface( dom );
- this.$surface.find( '.ve-ce-documentNode' ).focus();
- }
-};
-
-/**
- * ...
- *
- * @method
- */
-ve.init.ViewPageTarget.prototype.onSave = function( error, content ) {
- if ( error ) {
- // TODO: Handle error in UI
- } else {
- // Hide the save dialog
- this.$dialog.fadeOut();
- // Refresh page with changed content
- this.$content.find( '#mw-content-text' ).html( content );
- // Restore the page to how it used to be
- this.tearDownSurface();
- }
-};
-
-/**
- * ...
+ * Switches to editing mode.
*
* @method
+ * @param {HTMLElement} dom HTML DOM to edit
*/
ve.init.ViewPageTarget.prototype.setUpSurface = function( dom ) {
// Initialize surface
- this.$surface.appendTo( this.$content );
+ this.attachSurface();
this.surface = new ve.Surface( this.$surface, dom, this.surfaceOptions );
this.surface.getModel().on( 'transact', this.proxiedOnSurfaceModelTransact );
// Transplant the toolbar
- this.$toolbar = this.$surface.find( '.es-toolbar-wrapper' );
- this.$toolbar.find( '.es-toolbar' ).slideDown( 'fast' );
- this.$heading
- .before( this.$toolbar )
- .addClass( 've-init-viewPageTarget-pageTitle' )
- .fadeTo( 'fast', 0.5 );
+ this.attachToolbar();
+ this.transformPageTitle();
// Update UI
- this.$view.hide();
- this.$spinner.remove();
- this.$dialog = $( ve.init.ViewPageTarget.saveDialogTemplate );
- // Add save and close buttons
- this.$toolbar
- .find( '.es-modes' )
- .append(
- $( '' )
- .addClass(
- 've-init-viewPageTarget-button ' +
- 've-init-viewPageTarget-button-disabled ' +
- 've-init-viewPageTarget-saveButton'
- )
- .append(
- $( '' )
- .text( mw.msg( 'savearticle' ) )
- )
- .append( $( '' ) )
- .mousedown( function( e ) {
- e.preventDefault();
- return false;
- } )
- .click( ve.proxy( this.onSaveButtonClick, this ) )
- );
- // Set up save dialog
- this.$dialog
- .find( '.ve-init-viewPageTarget-saveDialog-title' )
- .text( mw.msg( 'tooltip-save' ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-closeButton' )
- .click( ve.proxy( this.onSaveDialogCloseButtonClick, this ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-editSummary-label' )
- .text( mw.msg( 'summary' ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-minorEdit-label' )
- .text( mw.msg( 'minoredit' ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-watchList' )
- .prop( 'checked', ve.config.isPageWatched )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-watchList-label' )
- .text( mw.msg( 'watchthis' ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-saveButton' )
- .click( ve.proxy( this.onSaveDialogSaveButtonClick, this ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-saveButton-label' )
- .text( mw.msg( 'savearticle' ) )
- .end()
- .find( '.ve-init-viewPageTarget-saveDialog-license' )
- .html(
- "By editing this page, you agree to irrevocably release your \
- contributions under the CC-By-SA 3.0 License. If you don't want your \
- writing to be editied mercilessly and redistrubuted at will, then \
- don't submit it here.
You are also confirming that you \
- wrote this yourself, or copied it from a public domain or similar free \
- resource. See Project:Copyright for full details of the licenses \
- used on this site.\
- DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION!"
- )
- .end()
- .insertAfter( this.$toolbar.find( '.ve-init-viewPageTarget-saveButton' ) );
+ this.hidePageContent();
+ this.hideSpinner();
+ this.disableToolbarSaveButton();
this.active = true;
};
-ve.init.ViewPageTarget.prototype.tearDownSurface = function( content ) {
+/**
+ * Switches to viewing mode.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.tearDownSurface = function() {
// Reset tabs
- this.setSelectedTab( 'ca-view' );
+ this.restoreSkinTabs();
// Update UI
+ this.$surface.find( '.ve-ce-documentNode' ).blur();
this.$surface.empty().detach();
- this.$toolbar.find( '.es-toolbar' ).slideUp( 'fast', function() {
- $(this).parent().remove();
- } );
- this.$spinner.remove();
- $( '.es-contextView' ).remove();
- this.$view.show().fadeTo( 'fast', 1 );
- this.$heading.fadeTo( 'fast', 1 );
- setTimeout( ve.proxy( function() {
- $(this).removeClass( 've-init-viewPageTarget-pageTitle' );
- }, this.$heading ), 1000 );
- this.$toc.slideDown( 'fast', function() {
- $(this).removeClass( 've-init-viewPageTarget-pageToc' );
- } );
+ this.detachToolbar();
+ this.hideSpinner();
+ this.showPageContent();
+ this.restorePageTitle();
+ this.showTableOfContents();
+ // Remove handler if it's still active
+ this.surface.getModel().removeListener( 'transact', this.proxiedOnSurfaceModelTransact );
// Destroy editor
this.surface = null;
this.active = false;
};
-ve.init.ViewPageTarget.prototype.setupTabs = function(){
+/**
+ * Modifies tabs in the skin to support in-place editing.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.setupSkinTabs = function() {
// Only sysop users will have an edit tab in this namespace, so we might need to add one
if ( $( '#ca-edit' ).length === 0 ) {
// Add edit tab
@@ -395,32 +355,401 @@ ve.init.ViewPageTarget.prototype.setupTabs = function(){
);
}
$( '#ca-edit a' ).click( ve.proxy( this.onEditTabClick, this ) );
- $( '#mw-content-text .editsection a' ).click( ve.proxy( this.onEditSectionLinkClick, this ) );
$( '#ca-view a' ).click( ve.proxy( this.onViewTabClick, this ) );
};
/**
- * Shows a loading spinner.
+ * Modifies page content to make section edit links activate the editor.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.setupSectionEditLinks = function() {
+ $( '#mw-content-text .editsection a' ).click( ve.proxy( this.onEditSectionLinkClick, this ) );
+};
+
+/**
+ * Adds content and event bindings to the save button.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.setupToolbarSaveButton = function() {
+ this.$toolbarSaveButton
+ .append(
+ $( '' )
+ .text( mw.msg( 'savearticle' ) )
+ )
+ .append( $( '' ) )
+ .mousedown( function( e ) {
+ $(this).addClass( 've-init-viewPageTarget-toolbar-saveButton-down' );
+ e.preventDefault();
+ return false;
+ } )
+ .mouseup( function( e ) {
+ $(this).removeClass( 've-init-viewPageTarget-toolbar-saveButton-down' );
+ e.preventDefault();
+ return false;
+ } )
+ .click( ve.proxy( this.onToolbarSaveButtonClick, this ) );
+};
+
+/**
+ * Adds the save button to the user interface.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.attachToolbarSaveButton = function() {
+ $( '.es-toolbar .es-modes' ).append( this.$toolbarSaveButton );
+ this.disableToolbarSaveButton();
+};
+
+/**
+ * Removes the save button from the user interface.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.detachToolbarSaveButton = function() {
+ this.$toolbarSaveButton.detach();
+};
+
+/**
+ * Adds content and event bindings to the save dialog.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.setupSaveDialog = function() {
+ this.$saveDialog
+ .html( ve.init.ViewPageTarget.saveDialogTemplate )
+ .find( '.ve-init-viewPageTarget-saveDialog-title' )
+ .text( mw.msg( 'tooltip-save' ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-closeButton' )
+ .click( ve.proxy( this.onSaveDialogCloseButtonClick, this ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-editSummary-label' )
+ .text( mw.msg( 'summary' ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-minorEdit-label' )
+ .text( mw.msg( 'minoredit' ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-watchList' )
+ .prop( 'checked', mw.config.get( 'wgVisualEditor' ).isPageWatched )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-watchList-label' )
+ .text( mw.msg( 'watchthis' ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-saveButton' )
+ .click( ve.proxy( this.onSaveDialogSaveButtonClick, this ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-saveButton-label' )
+ .text( mw.msg( 'savearticle' ) )
+ .end()
+ .find( '.ve-init-viewPageTarget-saveDialog-license' )
+ .html(
+ "By editing this page, you agree to irrevocably release your \
+ contributions under the CC-By-SA 3.0 License. If you don't want your \
+ writing to be editied mercilessly and redistrubuted at will, then \
+ don't submit it here.
You are also confirming that you \
+ wrote this yourself, or copied it from a public domain or similar free \
+ resource. See Project:Copyright for full details of the licenses \
+ used on this site.\
+ DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION!"
+ );
+};
+
+/**
+ * Adds the save dialog to the user interface.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.attachSaveDialog = function() {
+ this.$saveDialog.insertAfter( this.$toolbarSaveButton );
+};
+
+/**
+ * Removes the save dialog from the user interface.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.detachSaveDialog = function() {
+ this.$saveDialog.detach();
+};
+
+/**
+ * Remembers the window's scroll position.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.saveScrollPosition = function() {
+ this.scrollTop = $( window ).scrollTop();
+};
+
+/**
+ * Restores the window's scroll position.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.restoreScrollPosition = function() {
+ if ( this.scrollTop ) {
+ $( window ).scrollTop( this.scrollTop );
+ this.scrollTop = null;
+ }
+};
+
+/**
+ * Shows the loading spinner.
*
* @method
*/
ve.init.ViewPageTarget.prototype.showSpinner = function() {
- this.$spinner = $( '' )
- .addClass( 've-init-viewPageTarget-loadingSpinner' )
- .prependTo( this.$heading );
+ this.$spinner.prependTo( $( '#firstHeading' ) );
};
/**
- * Resets all tabs in the UI and selects a specific one.
- *
- * If no ID is given, or no ID matches the given ID, all tabs will be unselected.
+ * Hides the loading spinner.
*
* @method
- * @param {String} id HTML ID of tab to select
*/
-ve.init.ViewPageTarget.prototype.setSelectedTab = function( id ) {
+ve.init.ViewPageTarget.prototype.hideSpinner = function() {
+ this.$spinner.detach();
+};
+
+/**
+ * Shows the page content.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.showPageContent = function() {
+ $( '#bodyContent' ).children().not( '#siteSub' ).show().fadeTo( 0, 1 );
+};
+
+/**
+ * Mutes the page content.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.mutePageContent = function() {
+ $( '#bodyContent' ).children().not( '#siteSub' ).fadeTo( 'fast', 0.25 );
+};
+
+/**
+ * Hides the page content.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.hidePageContent = function() {
+ $( '#bodyContent' ).children().not( '#siteSub' ).hide();
+};
+
+/**
+ * Shows the table of contents in the view mode.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.showTableOfContents = function() {
+ $( '#toc' ).slideDown( 'fast', function() {
+ $(this).removeClass( 've-init-viewPageTarget-pageToc' );
+ } );
+};
+
+/**
+ * Hides the table of contents in the view mode.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.hideTableOfContents = function() {
+ $( '#toc' ).addClass( 've-init-viewPageTarget-pageToc' ).slideUp( 'fast' );
+};
+
+/**
+ * Shows the save dialog.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.showSaveDialog = function() {
+ this.$saveDialog.fadeIn( 'fast' ).find( 'input:first' ).focus();
+};
+
+/**
+ * Hides the save dialog
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.hideSaveDialog = function() {
+ this.$saveDialog.fadeOut( 'fast' );
+ this.$surface.find( '.ve-ce-documentNode' ).focus();
+};
+
+/**
+ * Enables the toolbar save button.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.enableToolbarSaveButton = function() {
+ this.$toolbarSaveButton.removeClass( 've-init-viewPageTarget-toolbar-saveButton-disabled' );
+};
+
+/**
+ * Disables the toolbar save button.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.disableToolbarSaveButton = function() {
+ this.$toolbarSaveButton.addClass( 've-init-viewPageTarget-toolbar-saveButton-disabled' );
+};
+
+/**
+ * Shows the toolbar.
+ *
+ * This also transplants the toolbar to a new location.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.attachToolbar = function() {
+ $( '.es-toolbar-wrapper' )
+ .insertBefore( $( '#firstHeading' ) )
+ .find( '.es-toolbar' )
+ .slideDown( 'fast' );
+};
+
+/**
+ * Hides the toolbar.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.detachToolbar = function() {
+ $( '.es-toolbar' ).slideUp( 'fast', function() {
+ $(this).parent().remove();
+ } );
+};
+
+/**
+ * Enables the toolbar save button.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.transformPageTitle = function() {
+ $( '#firstHeading' ).addClass( 've-init-viewPageTarget-pageTitle' );
+};
+
+/**
+ * Enables the toolbar save button.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.mutePageTitle = function() {
+ $( '#firstHeading' ).fadeTo( 'fast', 0.25 );
+ $( '#siteSub' ).fadeTo( 'fast', 0.25 );
+};
+
+/**
+ * Disables the toolbar save button.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.restorePageTitle = function() {
+ $( '#firstHeading' ).fadeTo( 'fast', 1 );
+ $( '#siteSub' ).fadeTo( 'fast', 1 );
+ setTimeout( function() {
+ $( '#firstHeading' ).removeClass( 've-init-viewPageTarget-pageTitle' );
+ }, 1000 );
+};
+
+/**
+ * Modifies page tabs to show that editing is taking place.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.transformSkinTabs = function() {
$( '#p-views' ).find( 'li.selected' ).removeClass( 'selected' );
- $( '#' + id ).addClass( 'selected' );
+ $( '#ca-edit' ).addClass( 'selected' );
+};
+
+/**
+ * Modifies page tabs to show that viewing is taking place.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.restoreSkinTabs = function() {
+ $( '#p-views' ).find( 'li.selected' ).removeClass( 'selected' );
+ $( '#ca-view' ).addClass( 'selected' );
+};
+
+/**
+ * Replaces the page content with new HTML.
+ *
+ * @method
+ * @param {HTMLElement} html Rendered HTML from server
+ */
+ve.init.ViewPageTarget.prototype.replacePageContent = function( html ) {
+ $( '#mw-content-text' ).html( html );
+};
+
+/**
+ * Attaches the surface to the page.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.attachSurface = function() {
+ $( '#content' ).append( this.$surface );
+};
+
+/**
+ * Attaches the surface to the page.
+ *
+ * @method
+ */
+ve.init.ViewPageTarget.prototype.detachSurface = function() {
+ this.$surface.detach();
+ $( '.es-contextView' ).remove();
+};
+
+/**
+ * Gets the numeric index of a section in the page.
+ *
+ * @method
+ * @param {HTMLElement} heading Heading element of section
+ */
+ve.init.ViewPageTarget.prototype.saveEditSection = function( heading ) {
+ var $page = $( '#mw-content-text' );
+ tocHeading = $page.find( '#toc h2' )[0];
+ section = 0;
+ $page.find( 'h1, h2, h3, h4, h5, h6' ).each( function() {
+ if ( this === heading ) {
+ return false;
+ }
+ if ( this !== tocHeading ) {
+ section++;
+ }
+ } );
+ this.section = section;
+};
+
+/**
+ * Moves the cursor in the editor to a given section.
+ *
+ * @method
+ * @param {Number} section Section to move cursor to
+ */
+ve.init.ViewPageTarget.prototype.restoreEditSection = function() {
+ if ( this.section ) {
+ var surfaceView = this.surface.getView(),
+ surfaceModel = surfaceView.getModel();
+ this.$surface
+ .find( '.ve-ce-documentNode' )
+ .find( 'h1, h2, h3, h4, h5, h6' )
+ .eq( this.section )
+ .each( function() {
+ var headingNode = $(this).data( 'node' );
+ if ( headingNode ) {
+ var offset = surfaceModel.getDocument().getNearestContentOffset(
+ headingNode.getModel().getOffset()
+ );
+ surfaceModel.setSelection( new ve.Range( offset, offset ) );
+ surfaceView.showSelection( surfaceModel.getSelection() );
+ }
+ } );
+ this.section = null;
+ }
};
/* Inheritance */
@@ -429,7 +758,4 @@ ve.extendClass( ve.init.ViewPageTarget, ve.init.Target );
/* Initialization */
-// TODO: Clean this stuff up
-
-ve.config = mw.config.get( 'wgVisualEditor' );
-ve.init.current = new ve.init.ViewPageTarget();
+ve.init.viewPageTarget = new ve.init.ViewPageTarget();
diff --git a/modules/ve2/init/ve.init.Target.js b/modules/ve2/init/ve.init.Target.js
index 095c16e0eb..7a4260a2a5 100644
--- a/modules/ve2/init/ve.init.Target.js
+++ b/modules/ve2/init/ve.init.Target.js
@@ -6,19 +6,129 @@
* @param {String} title Page title of target
*/
ve.init.Target = function( title ) {
+ // Inheritance
+ ve.EventEmitter.call( this );
+
// Properties
this.title = title;
this.editToken = mw.user.tokens.get( 'editToken' );
this.apiUrl = mw.util.wikiScript( 'api' );
this.modules = ['ext.visualEditor.core'];
- this.isDomLoading = false;
- this.isDomSaving = false;
+ this.loading = false;
+ this.saving = false;
+ this.dom = null;
this.isMobileDevice = (
'ontouchstart' in window ||
( window.DocumentTouch && document instanceof DocumentTouch )
);
};
+/* Static Methods */
+
+/**
+ * Handle response to a successful load request.
+ *
+ * This method is called within the context of a target instance. If successful the DOM from the
+ * server will be parsed, stored in {this.dom} and then {ve.init.Target.onReady} will be called once
+ * the modules are ready.
+ *
+ * @static
+ * @method
+ * @param {Object} response XHR Response object
+ * @param {String} status Text status message
+ * @emits loadError (message)
+ */
+ve.init.Target.onLoad = function( response, status ) {
+ var data = response['ve-parsoid'];
+ if ( !data ) {
+ this.loading = false;
+ this.emit( 'loadError', 'Invalid response from server' );
+ } else if ( typeof data.parsed !== 'string' ) {
+ this.loading = false;
+ this.emit( 'loadError', 'Invalid HTML content in response from server' );
+ } else {
+ this.dom = $( '' ).html( data.parsed )[0];
+ // Everything worked, the page was loaded, continue as soon as the module is ready
+ mw.loader.using( this.modules, ve.proxy( ve.init.Target.onReady, this ) );
+ }
+};
+
+/**
+ * Handle both DOM and modules being loaded and ready.
+ *
+ * This method is called within the context of a target instance. After the load event is emitted
+ * this.dom is cleared, allowing it to be garbage collected.
+ *
+ * @static
+ * @method
+ * @emits load (dom)
+ */
+ve.init.Target.onReady = function() {
+ this.loading = false;
+ this.emit( 'load', this.dom );
+ // Release DOM data
+ this.dom = null;
+};
+
+/**
+ * Handle response to a successful load request.
+ *
+ * This method is called within the context of a target instance.
+ *
+ * @static
+ * @method
+ * @param {Object} response XHR Response object
+ * @param {String} status Text status message
+ * @param {Mixed} error Thrown exception or HTTP error string
+ * @emits load (dom)
+ */
+ve.init.Target.onLoadError = function( response, text, exception ) {
+ this.loading = false;
+ this.emit( 'loadError', response, text, exception );
+};
+
+/**
+ * Handle response to a successful save request.
+ *
+ * This method is called within the context of a target instance.
+ *
+ * @static
+ * @method
+ * @param {Object} response XHR Response object
+ * @param {String} status Text status message
+ * @emits save (html)
+ */
+ve.init.Target.onSave = function( response, status ) {
+ this.saving = false;
+ var data = response['ve-parsoid'];
+ if ( !response ) {
+ this.emit( 'saveError', 'Invalid response from server' );
+ } else if ( data.result !== 'success' ) {
+ this.emit( 'saveError', 'Unsuccessful request: ' + data.result );
+ } else if ( typeof data.content !== 'string' ) {
+ this.emit( 'saveError', 'Invalid HTML content in response from server' );
+ } else {
+ this.emit( 'save', data.content );
+ }
+};
+
+/**
+ * Handle response to a successful save request.
+ *
+ * This method is called within the context of a target instance.
+ *
+ * @static
+ * @method
+ * @param {Object} data HTTP Response object
+ * @param {String} status Text status message
+ * @param {Mixed} error Thrown exception or HTTP error string
+ * @emits save (html)
+ */
+ve.init.Target.onSaveError = function( response, status, error ) {
+ this.saving = false;
+ this.emit( 'saveError', response, status, error );
+};
+
/* Methods */
/**
@@ -39,13 +149,13 @@ ve.init.Target = function( title ) {
*/
ve.init.Target.prototype.load = function( callback ) {
// Prevent duplicate requests
- if ( this.isDomLoading ) {
+ if ( this.loading ) {
return false;
}
// Start loading the module immediately
mw.loader.load( this.modules );
// Load DOM
- this.isDomLoading = true;
+ this.loading = true;
$.ajax( {
'url': this.apiUrl,
'data': {
@@ -59,21 +169,8 @@ ve.init.Target.prototype.load = function( callback ) {
'cache': 'false',
// Wait up to 9 seconds
'timeout': 9000,
- 'error': callback,
- 'success': ve.proxy( function( data ) {
- this.isDomLoading = false;
- var response = data['ve-parsoid'];
- if ( !response ) {
- callback( 'Invalid response from server' );
- } else if ( typeof response.parsed !== 'string' ) {
- callback( 'Invalid HTML content in response from server' );
- } else {
- // Everything worked, the page was loaded, continue as soon as the module is ready
- mw.loader.using( this.modules, function() {
- callback( null, $( '' ).html( data['ve-parsoid'].parsed )[0] );
- } );
- }
- }, this )
+ 'error': ve.proxy( ve.init.Target.onLoadError, this ),
+ 'success': ve.proxy( ve.init.Target.onLoad, this )
} );
return true;
};
@@ -103,11 +200,11 @@ ve.init.Target.prototype.load = function( callback ) {
*/
ve.init.Target.prototype.save = function( dom, options, callback ) {
// Prevent duplicate requests
- if ( this.isDomSaving ) {
+ if ( this.saving ) {
return false;
}
// Save DOM
- this.isDomSaving = true;
+ this.saving = true;
$.ajax( {
'url': this.apiUrl,
'data': {
@@ -123,21 +220,12 @@ ve.init.Target.prototype.save = function( dom, options, callback ) {
},
'dataType': 'json',
'type': 'POST',
- 'error': callback,
- 'success': ve.proxy( function( data ) {
- this.isDomSaving = false;
- var response = data['ve-parsoid'];
- if ( !response ) {
- callback( 'Invalid response from server' );
- } else if ( response.result !== 'success' ) {
- callback( 'Unsuccessful request: ' + response.result );
- } else if ( typeof response.content !== 'string' ) {
- callback( 'Invalid HTML content in response from server' );
- } else {
- // Everything worked, the page was saved, continue immediately
- callback( null, response.content );
- }
- }, this )
+ 'error': ve.proxy( ve.init.Target.onSaveError, this ),
+ 'success': ve.proxy( ve.init.Target.onSave, this )
} );
return true;
};
+
+/* Inheritance */
+
+ve.extendClass( ve.init.Target, ve.EventEmitter );