Merge branch 'dmrewrite' of ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/VisualEditor into dmrewrite

This commit is contained in:
Inez Korczynski 2012-06-18 13:19:24 -07:00
commit 1fa1908402
5 changed files with 732 additions and 319 deletions

View file

@ -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',

View file

@ -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;
}

View file

@ -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;

View file

@ -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 = $( '<div class="ve-surface"></div>' );
this.$toolbar = null;
this.$spinner = $( '<div class="ve-init-viewPageTarget-loadingSpinner"></div>' );
this.$toolbarSaveButton = $( '<div class="ve-init-viewPageTarget-toolbar-saveButton"></div>' );
this.$saveDialog = $( '<div class="es-inspector ve-init-viewPageTarget-saveDialog"></div>' );
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 = '\
<div class="es-inspector ve-init-viewPageTarget-saveDialog">\
<div class="es-inspector-title ve-init-viewPageTarget-saveDialog-title"></div>\
<div class="es-inspector-button ve-init-viewPageTarget-saveDialog-closeButton"></div>\
<div class="ve-init-viewPageTarget-saveDialog-body">\
@ -72,103 +72,181 @@ ve.init.ViewPageTarget.saveDialogTemplate = '\
</div>\
<div class="ve-init-viewPageTarget-saveDialog-foot">\
<p class="ve-init-viewPageTarget-saveDialog-license"></p>\
</div>\
</div>';
</div>';
/* 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(
$( '<div></div>' )
.addClass(
've-init-viewPageTarget-button ' +
've-init-viewPageTarget-button-disabled ' +
've-init-viewPageTarget-saveButton'
)
.append(
$( '<span class="ve-init-viewPageTarget-saveButton-label"></span>' )
.text( mw.msg( 'savearticle' ) )
)
.append( $( '<span class="ve-init-viewPageTarget-saveButton-icon"></span>' ) )
.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.<br /><br />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.\
<b>DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION!</b>"
)
.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(
$( '<span class="ve-init-viewPageTarget-toolbar-saveButton-label"></span>' )
.text( mw.msg( 'savearticle' ) )
)
.append( $( '<span class="ve-init-viewPageTarget-toolbar-saveButton-icon"></span>' ) )
.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.<br /><br />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.\
<b>DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION!</b>"
);
};
/**
* 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 = $( '<div></div>' )
.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();

View file

@ -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 = $( '<div></div>' ).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, $( '<div></div>' ).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 );