Refactored veCore class into ve.init and ve.init.EditPageTarget

Change-Id: I52ef09aee38471ca49f3f4a845daf02e6dcc024c
This commit is contained in:
Trevor Parscal 2012-06-10 23:54:41 -07:00
parent 38e64418b5
commit ce8efd6b15
12 changed files with 635 additions and 580 deletions

View file

@ -2,57 +2,58 @@
class VisualEditorHooks {
/**
* Adds VisualEditor JS to the output if in the correct namespace
* Adds VisualEditor JS to the output if in the correct namespace.
*
* This is attached to the MediaWiki 'BeforePageDisplay' hook.
*
* @param $output OutputPage
* @param $skin Skin
*/
public static function onPageDisplay( &$output, &$skin ) {
if ( self::loadVisualEditor( $output, $skin ) ) {
$output->addModules( array( 'ext.visualEditor.core' ) );
public static function onBeforePageDisplay( &$output, &$skin ) {
global $wgTitle;
if (
// Vector skin supported for now.
$skin->getSkinName() === 'vector' &&
// Be sure current page is VisualEditor:Something
$wgTitle->getNamespace() === NS_VISUALEDITOR
) {
$output->addModules( array( 'ext.visualEditor.editPageInit' ) );
}
return true;
}
/**
* Determines whether or not we should construct the loader.
* Adds extra variables to the page config.
*
* @param $output OutputPage
* @param $skin Skin
* This is attached to the MediaWiki 'MakeGlobalVariablesScript' hook.
*/
public static function loadVisualEditor( &$output, &$skin ) {
global $wgTitle;
// Vector skin supported for now.
if ( $skin->getSkinName() !== 'vector' ) {
return false;
}
// Be sure current page is VisualEditor:Something
if ( $wgTitle->getNamespace() !== NS_VISUALEDITOR ) {
return false;
}
return true;
}
public static function makeGlobalVariablesScript( &$vars ) {
public static function onMakeGlobalVariablesScript( &$vars ) {
global $wgUser, $wgTitle;
$vars['vePageWatched'] = $wgUser->isWatched( $wgTitle ) ? true : false;
$vars['wgVisualEditor'] = array(
'isPageWatched' => $wgUser->isWatched( $wgTitle )
);
return true;
}
/**
*
*/
public static function namespaceProtection( &$title, &$user, $action, &$result ){
* Prevents editing in the VisualEditor namespace by non-sysop users.
*
* This is attached to the MediaWiki 'userCan' hook.
*/
public static function onUserCan( &$title, &$user, $action, &$result ) {
global $wgUser, $wgNamespaceProtection;
if ( array_key_exists( $title->mNamespace, $wgNamespaceProtection ) ) {
$nsProt = $wgNamespaceProtection[ $title->mNamespace ];
if ( !is_array($nsProt) ) $nsProt = array($nsProt);
foreach( $nsProt as $right ) {
if( '' != $right && !$user->isAllowed( $right ) ) {
$result = false;
}
$nsProt = $wgNamespaceProtection[$title->mNamespace];
if ( !is_array( $nsProt ) ) {
$nsProt = array( $nsProt );
}
foreach( $nsProt as $right ) {
if ( $right != '' && !$user->isAllowed( $right ) ) {
$result = false;
}
}
}
return true;
}
}

View file

@ -20,7 +20,6 @@
// URL to the parsoid instance
$wgVisualEditorParsoidURL = 'http://parsoid.wmflabs.org/';
/* Setup */
$wgExtensionCredits['other'][] = array(
@ -82,12 +81,19 @@ $wgResourceModules += array(
),
'styles' => 'sandbox/sandbox.css',
'dependencies' => array(
'ext.visualEditor.ve',
'ext.visualEditor.core',
),
),
'ext.visualEditor.core' => $wgVisualEditorResourceTemplate + array(
'ext.visualEditor.editPageInit' => $wgVisualEditorResourceTemplate + array(
'scripts' => array(
'core/ve.Core.js',
've2/init/targets/ve.init.EditPageTarget.js',
),
'styles' => array(
've2/init/styles/ve.init.EditPageTarget.css',
),
'dependencies' => array(
'ext.visualEditor.init',
'mediawiki.util'
),
'messages' => array(
'minoredit',
@ -101,19 +107,27 @@ $wgResourceModules += array(
'accesskey-ca-edit',
'tooltip-ca-edit',
'viewsource'
),
'styles' => 'core/ve.Core.css',
'dependencies' => array(
'jquery',
'mediawiki.util'
),
),
'ext.visualEditor.ve' => $wgVisualEditorResourceTemplate + array(
'ext.visualEditor.init' => $wgVisualEditorResourceTemplate + array(
'scripts' => array(
've2/init/ve.init.js',
've2/init/ve.init.Target.js',
),
'dependencies' => array(
'ext.visualEditor.base'
),
),
'ext.visualEditor.base' => $wgVisualEditorResourceTemplate + array(
'scripts' => array(
// ve
'jquery/jquery.json.js',
've2/ve.js',
)
),
'ext.visualEditor.core' => $wgVisualEditorResourceTemplate + array(
'scripts' => array(
// ve
've2/ve.EventEmitter.js',
've2/ve.Factory.js',
've2/ve.Position.js',
@ -224,6 +238,7 @@ $wgResourceModules += array(
'dependencies' => array(
'jquery',
'rangy',
'ext.visualEditor.base'
),
'messages' => array(
'visualeditor-tooltip-wikitext',
@ -237,20 +252,21 @@ $wgResourceModules += array(
)
);
/*
VisualEditor Namespace
Using 2500 range as it appears available in
MW Extension_namespace_registration
/*
* VisualEditor Namespace
* Using 2500 and 2501 as per registration on mediawiki.org
*
* @see http://www.mediawiki.org/wiki/Extension_default_namespaces
*/
define("NS_VISUALEDITOR", 2500);
define("NS_VISUALEDITOR_TALK", 2501);
$wgExtraNamespaces[NS_VISUALEDITOR] = "VisualEditor";
$wgExtraNamespaces[NS_VISUALEDITOR_TALK] = "VisualEditor_talk";
define( 'NS_VISUALEDITOR', 2500 );
define( 'NS_VISUALEDITOR_TALK', 2501 );
$wgExtraNamespaces[NS_VISUALEDITOR] = 'VisualEditor';
$wgExtraNamespaces[NS_VISUALEDITOR_TALK] = 'VisualEditor_talk';
$wgContentNamespaces[] = NS_VISUALEDITOR;
$wgContentNamespaces[] = NS_VISUALEDITOR_TALK;
// VE Namespace protection
$wgNamespaceProtection[NS_VISUALEDITOR] = array('ve-edit');
$wgNamespaceProtection[NS_VISUALEDITOR] = array( 've-edit' );
$wgGroupPermissions['sysop']['ve-edit'] = true;
// Parsoid Wrapper API
@ -259,6 +275,6 @@ $wgAPIModules['ve-parsoid'] = 'ApiVisualEditor';
// Integration Hooks
$wgAutoloadClasses['VisualEditorHooks'] = $dir . 'VisualEditor.hooks.php';
$wgHooks['BeforePageDisplay'][] = 'VisualEditorHooks::onPageDisplay';
$wgHooks['userCan'][] = 'VisualEditorHooks::namespaceProtection';
$wgHooks['MakeGlobalVariablesScript'][] = 'VisualEditorHooks::makeGlobalVariablesScript';
$wgHooks['BeforePageDisplay'][] = 'VisualEditorHooks::onBeforePageDisplay';
$wgHooks['userCan'][] = 'VisualEditorHooks::onUserCan';
$wgHooks['MakeGlobalVariablesScript'][] = 'VisualEditorHooks::onMakeGlobalVariablesScript';

View file

@ -1,442 +0,0 @@
/*
VisualEditor Core Module for MediaWiki.org integration
*/
(function( mw, $ ){
veCore = function(){
var _this = this,
pageName = mw.config.get( 'wgPageName' ),
validNamespace = mw.config.get('wgCanonicalNamespace') === 'VisualEditor' ? true: false;
this.mainEditor = null;
// On VisualEditor namespace ?
if ( validNamespace ) {
this.$content = $('#content');
this.setupTabs();
// modify / stash content styles
this.prepareContentStyles();
$('#ca-edit > span > a').click( function( e ){
// hijack the edit link
e.preventDefault();
// Load Editor if not already loaded
if( _this.mainEditor === null ) {
_this.selectTab( 'ca-edit' );
_this.showSpinner();
// async init
mw.loader.using( 'ext.visualEditor.ve', function(){
_this.init();
});
_this.getParsoidHTML( pageName, function( content ){
_this.init( content );
});
}
});
} //valid namespace
};
// called once for each asnyc callback.
// once ve is loaded and page has been parsed into html, continue init
veCore.prototype.init = function( html ) {
var _this = this,
isMobileDevice =
('ontouchstart' in window) ||
window.DocumentTouch && document instanceof DocumentTouch ||
false;
// store the html
if (typeof html !== 'undefined') {
this.html = html;
}
// if html and ve are loaded
if(
typeof ve !== 'undefined' &&
typeof this.html !== 'undefined'
){
$html = $("<div />")
.html( this.html );
// release this.html
delete this.html;
var options = {
toolbars: {
top: {
// If mobile device, float false
'float': !isMobileDevice,
// Toolbar modes
'modes': ['wikitext']
}
}
};
this.$editor = $('<div id="ve-editor"></div>');
this.$spinner.hide();
this.$content.css({
'padding':'0px 0px 0px 1px'
}).append( this.$editor );
this.mainEditor = new ve.Surface( '#ve-editor', $html[0], options );
this.$editor.find('.es-panes')
.css('padding', this.contentStyles.padding );
// Save BTN
this.$editor.find('.es-modes')
.append(
$('<div />')
.attr('class', 've-action-button es-inspector-savebutton')
.text('Save')
.click(function(){
// show/hide dialog
_this.$dialog.toggle();
})
).append(
$('<div />')
.attr('class', 've-action-button ve-closeBtn')
.click(function(){
// back to read mode
_this.mainEditor = null;
_this.cleanup();
})
);
this.initSaveDialog();
}
};
veCore.prototype.setupTabs = function(){
//Non admins shouldn't have an edit button, only ca-viewsource
var $viewsource;
// If no edit button, user not sysop
if ( $('#ca-edit').length === 0 ) {
// Add Edit button
mw.util.addPortletLink(
'p-views',
'#',
mw.msg('edit'),
'ca-edit',
mw.msg('tooltip-ca-edit'),
mw.msg('accesskey-ca-edit'),
'#ca-history'
);
// Create 'View Source' link in sub menu from original.
// Remove original
$viewsource = $('#ca-viewsource');
if ($viewsource.length > 0) {
// if admin, move this to the p-cactions link
mw.util.addPortletLink(
'p-cactions',
$viewsource.find('span > a').attr('href'),
$viewsource.find('span > a').text(),
$viewsource.attr('id')
);
$viewsource.remove();
}
} else {
// admin user, create an edit source link
mw.util.addPortletLink(
'p-cactions',
mw.util.wikiGetlink() + '?action=edit',
'Edit Source', // TODO: i18n
'ca-editsource'
);
}
};
// Reset tabs, select tab with tabID
// #ca-view, #ca-edit, #ca-history
veCore.prototype.selectTab = function( tabID ){
// unselects all tabs, selects sel
$('#p-views')
.find('ul')
.children()
.each(function(){
if( $(this).attr('id') === tabID ) {
$(this).addClass('selected');
} else {
$(this).removeClass('selected');
}
});
};
veCore.prototype.save = function(){
var _this = this;
_this.showSpinner();
// Save
_this.getParsoidWikitextAndSave( function( content ){
// cleanup
_this.$dialog.slideUp();
// load saved page
_this.$content
.find('#mw-content-text').html( content );
_this.cleanup();
});
};
veCore.prototype.initSaveDialog = function(){
var _this = this;
this.$dialog =
$('<div />')
.attr({
'id': 've-saveDialog',
'class': 'es-inspector'
}).append(
$('<div />')
.attr('class', 'es-inspector-title')
.text( mw.msg('tooltip-save') )
).append(
$('<div />')
.attr('class', 'es-inspector-button ve-saveBtn')
.click(function(){
_this.$dialog.toggle();
})
).append(
$('<div />')
.attr('class', 've-dialog-divider')
.append(
$('<div />')
.text("Describe what you changed")
//.text( mw.msg('summary') )
).append(
$('<input />')
.attr({
'id': 'txtEditSummary',
'type':'text'
})
).append(
$('<br />')
).append(
$('<div />')
.attr('class', 've-dialog-left')
.append(
$('<input />')
.attr({
'type': 'checkbox',
'name': 'chkMinorEdit',
'id': 'chkMinorEdit'
})
).append(
$('<label />')
.attr('for', 'chkMinorEdit')
.text( mw.msg('minoredit') )
).append(
$('<br />')
).append(
$('<input />')
.attr({
'type': 'checkbox',
'name': 'chkWatchlist',
'id': 'chkWatchlist'
}).prop(
'checked',
mw.config.get('vePageWatched')
)
).append(
$('<label />')
.attr('for', 'chkWatchlist')
.text( mw.msg('watchthis') )
)
).append(
$('<div />')
.attr('class', 've-action-button es-inspector-savebutton doSaveBtn')
.text( mw.msg('savearticle') )
.click(function(){
_this.save();
})
.append(
$('<div />')
.attr('class', 'doSaveBtnIcon')
)
).append(
$('<br />')
)
).append(
$('<div />')
.attr('class', 've-dialog-divider')
.append(
$("<p />")
// TODO: i18n
.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>"
)
)
);
this.$editor
.find('.es-inspector-savebutton')
.after ( this.$dialog );
};
veCore.prototype.showSpinner = function(){
var _this = this;
this.$spinner = $('<div />')
.attr({
'id': 've-loader-spinner',
'class': 'mw-ajax-loader'
}).css({
'height': this.$content.height() + 'px',
'width': (this.$content.width() -20 ) + 'px'
});
_this.$spinner.remove();
//hide all of the #content element children
_this.$content
.children()
.each(function(){
$(this).hide();
});
_this.$content.append(
_this.$spinner.show()
);
};
/* Make a backup of the #content div styles
In order to tuck the toolbar close under the tabs,
We need to change the padding before inserting the toolbar
Additionally, the transitions must be removed so that
when adjusting the padding, there is no animated transition.
*/
veCore.prototype.prepareContentStyles = function(){
// Store Padding and transitions
this.contentStyles = {
'padding': this.$content.css('padding'),
'transition': this.$content.css('transition'),
'transition-property': this.$content.css('transition-property'),
'-moz-transition': this.$content.css('-moz-transition'),
'-webkit-transition': this.$content.css('-webkit-transition'),
'-o-transition': this.$content.css('-o-transition')
};
// Squash transitions
this.$content.css({
'transition': 'none',
'transition-property': 'none',
'-moz-transition': 'none',
'-webkit-transition': 'none',
'-o-transition': 'color 0 ease-in'
});
};
//back to view
veCore.prototype.cleanup = function(){
this.mainEditor = null;
this.selectTab( 'ca-view' );
this.$content
.find('#mw-content-text, #bodyContent, #firstHeading').show()
.end()
.find('#ve-editor, #ve-loader-spinner').remove()
.end()
//.css( this.contentStyles );
// only put back padding for now, ideally restore transition css though
.css('padding', this.contentStyles.padding);
};
veCore.prototype.getParsoidHTML = function (title, callback) {
$.ajax({
url: mw.util.wikiScript( 'api' ),
data: {
'action': 've-parsoid',
'paction': 'parse',
'page': mw.config.get( 'wgPageName' ),
'format': 'json'
},
dataType: 'json',
type: 'GET',
cache: 'false',
timeout: 9000, //wait up to 9 seconds
success: function( data ) {
if (data && data['ve-parsoid'] && data['ve-parsoid'].parsed ) {
if( typeof callback === 'function') {
callback( data['ve-parsoid'].parsed );
}
}
},
error: function( error ) {}
});
};
/*
Posts HTML to Parsoid Wrapper API
API Posts HTML to Parsoid Service, receives Wikitext,
API Saves Wikitext to page.
Returns new page content
*/
veCore.prototype.getParsoidWikitextAndSave = function( callback ) {
var data = this.mainEditor.documentModel.getData(),
html = ve.dm.converter.getDomFromData( data ),
summary = $('#txtEditSummary').val(),
minor = $('#chkMinorEdit').prop('checked'),
watch = $('#chkWatchlist').prop('checked');
$.ajax({
url: mw.util.wikiScript( 'api' ),
data: {
'action': 've-parsoid',
'paction': 'save',
'page': mw.config.get( 'wgPageName' ),
'html': $(html).html(),
'token': mw.user.tokens.get('editToken'),
'summary': summary,
'minor': minor,
'watch': watch,
'format': 'json'
},
dataType: 'json',
type: 'POST',
success: function( data ) {
if (
data &&
data['ve-parsoid'] &&
data['ve-parsoid'].result &&
data['ve-parsoid'].result === 'success' &&
data['ve-parsoid'].content
) {
//saved
callback( data['ve-parsoid'].content );
}
},
error: function( error ) {}
});
};
/* Gets HTML for a page from MW API action parse */
veCore.prototype.getParsedPage = function( title, callback ) {
//currently using mw api to get
$.ajax({
url: mw.util.wikiScript( 'api' ),
data: {
'action': 'parse',
'format': 'json',
'page': title
},
dataType: 'json',
type: 'GET',
cache: 'false',
success: function( data ) {
if ( data && data.parse && data.parse.text ) {
// return pages to callback
if( typeof callback === 'function') {
callback( data.parse.text['*'] );
}
}
},
error: function() {
if( typeof callback === 'function') {
callback();
}
}
});
};
var core = new veCore();
})( window.mw, jQuery );

View file

@ -1,8 +1,21 @@
#ve-loader-spinner {
background-color: #FFF;
opacity:.5;
.es-toolbar-wrapper {
margin: -1.25em -1.5em -1.5em -1.5em;
}
.ve-action-button {
.es-toolbar {
margin-left: 1px;
}
.ve-init-editPageTarget-loadingSpinner {
background-color: #ffffff;
opacity: 0.75;
position: absolute;
top: 0;
left: 0;
background-position: center 2em !important;
}
.ve-init-editPageTarget-button {
display: inline-block;
border: solid 1px
transparent;
@ -17,72 +30,33 @@
margin-right: 0.25em;
border: solid 1px transparent;
}
.ve-action-button:before {
.ve-init-editPageTarget-button:before {
content: " ";
position: absolute;
display: block;
height: 22px;
}
.ve-action-button:hover {
.ve-init-editPageTarget-button:hover {
border-color: #eeeeee;
}
.ve-action-button:active,
.ve-action-button-down {
.ve-init-editPageTarget-button:active,
.ve-init-editPageTarget-button-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);
}
/* Save dialog styles */
#ve-saveDialog {
top: -1px;
right: 2.5em;
width: 29em;
}
#ve-saveDialog > .ve-dialog-divider {
border-top:1px solid #dddddd;
padding: 10px 0;
font-size: 12px;
}
#ve-saveDialog br {
clear: both;
}
.ve-dialog-left {
float: left;
}
#ve-saveDialog input[type='text'] {
width: 96%;
font-size: 12px;
padding: 4px;
margin: 10px 0px;
}
#ve-saveDialog input[type='checkbox'] {
margin-right: 5px;
}
.ve-closeBtn {
.ve-init-editPageTarget-closeButton {
width: 22px;
background-image: url(../ve2/ui/styles/images/close.png);
background-position: center center;
background-repeat: no-repeat;
}
/* mini save button */
.ve-saveBtn {
right: .025em;
width: 22px;
background-image: url(../ve2/ui/styles/images/close.png);
background-image: url(../../ui/styles/images/close.png);
background-position: center center;
background-repeat: no-repeat;
}
/* inspector styles */
.es-inspector-savebutton {
padding-right: 26px;
.ve-init-editPageTarget-saveButton {
padding-right: 32px;
border:1px solid transparent;
border-radius: 0.125em;
-webkit-border-radius: 0.125em;
@ -90,18 +64,34 @@
-o-border-radius: 0.125em;
/* need new button */
/* @embed */
background-image: url(../ve2/ui/styles/images/close.png);
background-position: center right;
background-image: url(../../ui/styles/images/accept.png);
background-position: right center;
background-repeat: no-repeat;
}
.doSaveBtn {
.ve-init-editPageTarget-saveButton span {
font-size: 0.8em;
}
/* mini save button */
.ve-init-editPageTarget-saveDialog-closeButton {
right: .025em;
width: 22px;
background-image: url(../../ui/styles/images/close.png);
background-position: center center;
background-repeat: no-repeat;
}
.ve-init-editPageTarget-saveDialog-saveButton {
position: absolute;
border: 1px solid rgb(196,229,154);
margin-top: 10px;
right: 10px;
font-size: 12px;
padding: 5px 10px;
background-image: url(../ve2/ui/styles/images/close.png);
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 */
@ -118,12 +108,48 @@
color-stop(1, rgb(240,251,225))
);
}
.doSaveBtn > div.doSaveBtnIcon {
height:2em;
width:2em;
float:right;
.ve-init-editPageTarget-saveDialog-saveButton-icon {
display: inline-block;
vertical-align: middle;
height: 2em;
width: 28px;
background: transparent;
background-image: url(../ve2/ui/styles/images/accept.png);
background-position: center right;
background-image: url(../../ui/styles/images/accept-clear.png);
background-position: top right;
background-repeat: no-repeat;
}
}
/* Save dialog styles */
.ve-init-editPageTarget-saveDialog {
display: block;
top: -1px;
right: 2.5em;
width: 29em;
}
.ve-init-editPageTarget-saveDialog-body {
border-top:1px solid #dddddd;
padding: 10px 0;
font-size: 12px;
}
.ve-init-editPageTarget-saveDialog-license {
font-size: 0.7em;
color: #999999;
}
.ve-init-editPageTarget-saveDialog-options {
float: left;
}
.ve-init-editPageTarget-saveDialog input[type='text'] {
width: 96%;
font-size: 12px;
padding: 4px;
margin: 10px 0;
}
.ve-init-editPageTarget-saveDialog input[type='checkbox'] {
margin-right: 5px;
}

View file

@ -0,0 +1,308 @@
/**
* Edit page target.
*
* @class
* @constructor
* @extends {ve.init.Target}
* @param {String} title Page title of target
*/
ve.init.EditPageTarget = function() {
// Inheritance
ve.init.Target.call( this, mw.config.get( 'wgPageName' ) );
// Properties
this.$content = $( '#content' );
this.$view = this.$content.find( '#mw-content-text, #bodyContent' );
this.$heading = $( '#firstHeading' );
this.$surface = $( '<div class="ve-surface"></div>' );
this.$toolbar = null;
this.surface = null;
this.active = false;
this.surfaceOptions = {
'toolbars': {
'top': {
// If mobile device, float false
'float': !this.isMobileDevice,
// Toolbar modes
'modes': ['wikitext']
}
}
};
// 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();
}
};
/* Static Members */
/*jshint multistr: true*/
ve.init.EditPageTarget.saveDialogTemplate = '\
<div class="es-inspector ve-init-editPageTarget-saveDialog">\
<div class="es-inspector-title ve-init-editPageTarget-saveDialog-title"></div>\
<div class="es-inspector-button ve-init-editPageTarget-saveDialog-closeButton"></div>\
<div class="ve-init-editPageTarget-saveDialog-body">\
<div class="ve-init-editPageTarget-saveDialog-editSummary-label"></div>\
<input name="editSummary" id="ve-init-editPageTarget-saveDialog-editSummary" type="text">\
<div class="clear:both"></div>\
<div class="ve-init-editPageTarget-saveDialog-options">\
<input type="checkbox" name="minorEdit" \
id="ve-init-editPageTarget-saveDialog-minorEdit">\
<label class="ve-init-editPageTarget-saveDialog-minorEdit-label" \
for="ve-init-editPageTarget-saveDialog-minorEdit">This is a minor edit</label>\
<div style="clear:both"></div>\
<input type="checkbox" name="watchList" \
id="ve-init-editPageTarget-saveDialog-watchList">\
<label class="ve-init-editPageTarget-saveDialog-watchList-label" \
for="ve-init-editPageTarget-saveDialog-watchList">Watch this page</label>\
</div>\
<div class="ve-init-editPageTarget-button ve-init-editPageTarget-saveDialog-saveButton">\
<span class="ve-init-editPageTarget-saveDialog-saveButton-label"></span>\
<div class="ve-init-editPageTarget-saveDialog-saveButton-icon"></div>\
</div>\
<div style="clear:both"></div>\
</div>\
<div class="ve-init-editPageTarget-saveDialog-foot">\
<p class="ve-init-editPageTarget-saveDialog-license"></p>\
</div>\
</div>';
/* Methods */
/**
* ...
*
* @method
*/
ve.init.EditPageTarget.prototype.onEditTabClick = function( e ) {
// Ignore multiple clicks while editor is active
if ( !this.active ) {
// UI updates
this.setSelectedTab( 'ca-edit' );
this.showSpinner();
// Asynchronous initialization - load ve modules at the same time as the content
this.load( ve.proxy( this.onLoad, this ) );
}
// Prevent the edit tab's normal behavior
e.preventDefault();
return false;
};
/**
* ...
*
* @method
*/
ve.init.EditPageTarget.prototype.onSaveDialogSaveButtonClick = function( e ) {
var _this = this;
this.showSpinner();
// Save
this.save(
ve.dm.converter.getDomFromData( this.surface.getDocumentModel().getData() ),
{
'summary': $( '#ve-init-editPageTarget-saveDialog-editSummary' ).val(),
'minor': $( '#ve-init-editPageTarget-saveDialog-minorEdit' ).prop( 'checked' ),
'watch': $( '#ve-init-editPageTarget-saveDialog-watchList' ).prop( 'checked' )
},
ve.proxy( this.onSave, this )
);
};
/**
* ...
*
* @method
*/
ve.init.EditPageTarget.prototype.onLoad = function( error, dom ) {
if ( error ) {
// TODO: Error handling in the UI
} else {
this.setUpSurface( dom );
}
};
/**
* ...
*
* @method
*/
ve.init.EditPageTarget.prototype.onSave = function( error, content ) {
if ( error ) {
// TODO: Handle error in UI
} else {
// Hide the save dialog
this.$dialog.slideUp();
// Refresh page with changed content
this.$content.find( '#mw-content-text' ).html( content );
// Restore the page to how it used to be
this.tearDownSurface();
}
};
/**
* ...
*
* @method
*/
ve.init.EditPageTarget.prototype.setUpSurface = function( dom ) {
// Initialize surface
this.$surface.appendTo( this.$content );
this.surface = new ve.Surface( this.$surface, dom, this.surfaceOptions );
// Transplant the toolbar
this.$toolbar = this.$surface.find( '.es-toolbar-wrapper' ).insertBefore( this.$heading );
this.$heading.css( 'margin-top', this.$toolbar.outerHeight() );
// Update UI
this.$spinner.remove();
this.$view.hide();
this.$spinner.hide();
this.$dialog = $( ve.init.EditPageTarget.saveDialogTemplate );
// Add save and close buttons
this.$toolbar
.find( '.es-modes' )
.append(
$( '<div></div>' )
.addClass( 've-init-editPageTarget-button ve-init-editPageTarget-saveButton' )
.append(
$( '<span></span>' )
.text( mw.msg( 'savearticle' ) )
)
.click( ve.proxy( this.$dialog.show, this.$dialog ) )
)
.append(
$( '<div></div>' )
.addClass( 've-init-editPageTarget-button ve-init-editPageTarget-closeButton' )
.click( ve.proxy( this.tearDownSurface, this ) )
);
// Set up save dialog
this.$dialog
.find( '.ve-init-editPageTarget-saveDialog-title' )
.text( mw.msg( 'tooltip-save' ) )
.end()
.find( '.ve-init-editPageTarget-saveDialog-closeButton' )
.click( ve.proxy( this.$dialog.hide, this.$dialog ) )
.end()
.find( '.ve-init-editPageTarget-saveDialog-editSummary-label' )
.text( mw.msg( 'summary' ) )
.end()
.find( '.ve-init-editPageTarget-saveDialog-minorEdit-label' )
.text( mw.msg( 'minoredit' ) )
.end()
.find( '.ve-init-editPageTarget-saveDialog-watchList' )
.prop( 'checked', ve.config.isPageWatched )
.end()
.find( '.ve-init-editPageTarget-saveDialog-watchList-label' )
.text( mw.msg( 'watchthis' ) )
.end()
.find( '.ve-init-editPageTarget-saveDialog-saveButton' )
.click( ve.proxy( this.onSaveDialogSaveButtonClick, this ) )
.end()
.find( '.ve-init-editPageTarget-saveDialog-saveButton-label' )
.text( mw.msg( 'savearticle' ) )
.end()
.find( '.ve-init-editPageTarget-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-editPageTarget-saveButton' ) );
};
ve.init.EditPageTarget.prototype.tearDownSurface = function( content ) {
// Reset tabs
this.setSelectedTab( 'ca-view' );
// Update UI
this.$view.show().fadeTo( 1 );
this.$surface.remove();
this.$toolbar.remove();
this.$spinner.remove();
this.$heading.css( 'margin-top', 0 );
// Destroy editor
this.surface = null;
};
ve.init.EditPageTarget.prototype.setupTabs = 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
mw.util.addPortletLink(
'p-views',
'#',
mw.msg( 'edit' ),
'ca-edit',
mw.msg( 'tooltip-ca-edit' ),
mw.msg( 'accesskey-ca-edit' ),
'#ca-history'
);
// If there isn't an edit tab, there's a view source tab we need to replace with edit source
var $viewSource = $( '#ca-viewsource' );
if ( $viewSource.length > 0 ) {
// "Move" the view source link to p-actions
mw.util.addPortletLink(
'p-cactions',
$viewSource.find( 'a' ).attr( 'href' ),
$viewSource.find( 'a' ).text(),
$viewSource.attr( 'id' )
);
// Remove the view original source link
$viewSource.remove();
}
} else {
// Sysop users will need a new edit source tab since we are highjacking the edit tab
mw.util.addPortletLink(
'p-cactions',
mw.util.wikiGetlink() + '?action=edit',
'Edit Source', // TODO: i18n
'ca-editsource'
);
}
$( '#ca-edit > span > a' ).click( ve.proxy( this.onEditTabClick, this ) );
};
/**
* Shows a loading spinner.
*
* @method
*/
ve.init.EditPageTarget.prototype.showSpinner = function() {
var $bodyContent = $( '#bodyContent' );
this.$spinner = $( '<div></div>' )
.addClass( 've-init-editPageTarget-loadingSpinner mw-ajax-loader' )
.css( {
'height': $bodyContent.height() + 'px',
'width': ( $bodyContent.width() -20 ) + 'px'
} )
.appendTo( $bodyContent );
};
/**
* 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.
*
* @method
* @param {String} id HTML ID of tab to select
*/
ve.init.EditPageTarget.prototype.setSelectedTab = function( id ) {
$( '#p-views' ).find( 'li.selected' ).removeClass( 'selected' );
$( '#' + id ).addClass( 'selected' );
};
/* Inheritance */
ve.extendClass( ve.init.EditPageTarget, ve.init.Target );
/* Initialization */
// TODO: Clean this stuff up
ve.config = mw.config.get( 'wgVisualEditor' );
ve.init.current = new ve.init.EditPageTarget();

View file

@ -0,0 +1,144 @@
/**
* Generic target.
*
* @class
* @constructor
* @param {String} title Page title of target
*/
ve.init.Target = function( title ) {
// 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.isMobileDevice = (
'ontouchstart' in window ||
( window.DocumentTouch && document instanceof DocumentTouch )
);
};
/* Methods */
/**
* Gets DOM from Parsoid API.
*
* This method performs an asynchronous action and uses a callback function to handle the result.
*
* @example
* target.loadDom(
* function( error, dom ) {
* // Handle errors and do something with the loaded DOM
* }
* );
*
* @method
* @param {Function} callback Function to call when complete, accepts error and dom arguments
* @returns {Boolean} Loading is now in progress
*/
ve.init.Target.prototype.load = function( callback ) {
// Prevent duplicate requests
if ( this.isDomLoading ) {
return false;
}
// Start loading the module immediately
var modules = this.modules;
mw.loader.load( modules );
// Load DOM
this.isDomLoading = true;
$.ajax( {
'url': this.apiUrl,
'data': {
'action': 've-parsoid',
'paction': 'parse',
'page': this.title,
'format': 'json'
},
'dataType': 'json',
'type': 'GET',
'cache': 'false',
// Wait up to 9 seconds
'timeout': 9000,
'error': callback,
'success': 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( modules, function() {
callback( null, $( '<div></div>' ).html( data['ve-parsoid'].parsed )[0] );
} );
}
}
} );
return true;
};
/**
* Posts DOM to Parsoid API.
*
* This method performs an asynchronous action and uses a callback function to handle the result.
*
* @example
* target.saveDom(
* dom,
* { 'summary': 'test', 'minor': true, 'watch': false },
* function( error, html ) {
* // Handle errors and do something with the rendered HTML
* }
* );
*
* @method
* @param {HTMLElement} dom DOM to save
* @param {Object} options Saving options
* @param {String} options.summary Edit summary
* @param {Boolean} options.minor Edit is a minor edit
* @param {Boolean} options.watch Watch this page
* @param {Function} callback Function to call when complete, accepts error and html arguments
* @returns {Boolean} Saving is now in progress
*/
ve.init.Target.prototype.save = function( dom, options, callback ) {
// Prevent duplicate requests
if ( this.isDomSaving ) {
return false;
}
// Save DOM
this.isDomSaving = true;
$.ajax( {
'url': this.apiUrl,
'data': {
'format': 'json',
'action': 've-parsoid',
'paction': 'save',
'page': this.title,
'html': $( dom ).html(),
'token': this.editToken,
'summary': options.summary,
'minor': options.minor,
'watch': options.watch
},
'dataType': 'json',
'type': 'POST',
'error': callback,
'success': 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 );
}
}
} );
return true;
};

View file

@ -0,0 +1,8 @@
/**
* Initialization namespace.
*
* All classes and functions will be attached to this object to keep the global namespace clean.
*/
ve.init = {
//
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,4 +1,5 @@
.es-inspector {
font-family: sans-serif;
display: none;
position: absolute;
border: solid 1px #cccccc;

View file

@ -203,27 +203,20 @@ ve.ui.Context.prototype.addInspector = function( name, inspector ) {
inspectorDoc.write( inspectorContent );
inspectorDoc.close();
$('head', inspectorDoc).append( $styleLink );
$('#ve-inspector-wrapper', inspectorDoc ).append( inspector.$ );
$( 'head', inspectorDoc).append( $styleLink );
$( '#ve-inspector-wrapper', inspectorDoc ).append( inspector.$ );
$( 'body', inspectorDoc ).css({
$( 'body', inspectorDoc ).css( {
'padding': '0px 5px 10px 5px',
'margin': 0
});
var asdf = {
'width': inspector.$.outerWidth( true ) + 10,
'height': inspector.$.outerHeight( true ) + 10
};
console.log (asdf);
console.log (inspector.$);
} );
// apply the dimensions of the inspector to the iframe, may need to be moved to open inspector
function tweakIframeDimensions() {
_this.$inspectors.css({
_this.$inspectors.css( {
'width': inspector.$.outerWidth( true ) + 10,
'height': inspector.$.outerHeight( true ) + 10
});
} );
}
};

View file

@ -1,6 +1,6 @@
/**
* VisualEditor User Interface namespace.
*
*
* All classes and functions will be attached to this object to keep the global namespace clean.
*/
ve.ui = {
@ -14,5 +14,5 @@ ve.ui = {
ve.ui.getStylesheetPath = function() {
// gets the path to a UI
// TODO: look for mw.util and rewrite
return "/extensions/VisualEditor/modules/ve2/ui/styles/";
};
return mw.config.get( 'wgExtensionAssetsPath' ) + '/VisualEditor/modules/ve2/ui/styles/';
};

View file

@ -4,9 +4,7 @@
* All classes and functions will be attached to this object to keep the global namespace clean.
*/
window.ve = {
/**
* List of instances of visual editors
*/
// List of instances of visual editors
'instances': []
};
@ -53,6 +51,8 @@ ve.isEmptyObject = $.isEmptyObject;
ve.isArray = $.isArray;
ve.proxy = $.proxy;
/**
* Wrapper for Array.prototype.indexOf
*