WikiEditor: Linting and conventions

* Code clean up in preparation for enabling linting in the future

* Update code to use latest code conventions and best practices:
 - Make use of jQuery.Event (e.g. no need to check both e.keyCode
   and e.which)
 - jQuery: .size() -> .length
 - jQuery: (where appropiate) .attr() -> .prop()
   Setting properties like 'checked' via attr() has been deprecated
   in jQuery.
 - Whitespace
 - Single quotes instead of double quotes
 - Use literal keys in object literals instead of strings
 - Pass mediaWiki to closure, use mw. locally instead of "mediaWiki"
   global directly.
 - Fix indentation
 - Brackets around if, else and for bodies
 - Strict comparison to 0, null, false, true etc.
 - Fix missing radix parameter in parseInt
 - Use local $ instead of global $
 - Use `foo || bar` instead of `foo ? foo : bar`
 - Variable scope hoisting
 - Double/redundant variable declarations
 - ['foo'] is better written in dot notation
 - New line at EOF
 - Consistency in jQuery construction:
   Tag name for element creation $( '<div>' )
   Valid html for html parsing $( '<div foo="bar"></div>' )
 - Fix regex escape warnings per JSLint/JSHint.
   Do escape ][, don't escape ><
 - ..

* Add .jshintrc / .jshintignore

* Updated most files, but not all. Too much at once.

Change-Id: I445639b25a9688b3cdf9e5449e3d31cbcfa9c7ae
This commit is contained in:
Timo Tijhof 2012-07-03 07:52:27 +02:00
parent 955ee768ca
commit 7721909f9f
19 changed files with 2106 additions and 1948 deletions

2
.jshintignore Normal file
View file

@ -0,0 +1,2 @@
# upstream lib from Google
modules/contentCollector.js

9
.jshintrc Normal file
View file

@ -0,0 +1,9 @@
{
"predef": [
"mediaWiki",
"jQuery"
],
"browser": true,
"smarttabs": true,
"multistr": true
}

View file

@ -1,4 +1,4 @@
/*
/**
* CSS for WikiEditor
*/
@ -7,10 +7,13 @@ form#editform {
margin: 0;
padding: 0;
}
/* These IDs (#wpSummaryLabel and #wpSummary) could change in MediaWiki */
#wpSummary, #wpSummaryLabel {
#wpSummary,
#wpSummaryLabel {
margin-bottom: 1em;
}
/* This ID (#wpTextbox1) could change in MediaWiki */
.wikiEditor-ui textarea#wpTextbox1 {
border: none;
@ -19,6 +22,7 @@ form#editform {
line-height: 1.5em;
resize: vertical;
}
.wikiEditor-ui .wikiEditor-ui-text > textarea#wpTextbox1 {
margin: 0;
}

View file

@ -12,4 +12,4 @@ $( document ).ready( function() {
// Add dialogs module
$( '#wpTextbox1' ).wikiEditor( 'addModule', $.wikiEditor.modules.dialogs.config.getDefaultConfig() );
} );
} );

View file

@ -2,9 +2,9 @@
* JavaScript for WikiEditor Template Editor
*/
$( document ).ready( function() {
$( document ).ready( function () {
// Disable in template namespace
if ( mw.config.get( 'wgNamespaceNumber' ) == 10 ) {
if ( mw.config.get( 'wgNamespaceNumber' ) === 10 ) {
return true;
}
// Add template editor module

View file

@ -2,11 +2,11 @@
* JavaScript for WikiEditor Templates
*/
$( document ).ready( function() {
$( document ).ready( function () {
// Disable for template namespace
if ( mw.config.get( 'wgNamespaceNumber' ) == 10 ) {
if ( mw.config.get( 'wgNamespaceNumber' ) === 10 ) {
return true;
}
// Add templates module
$( '#wpTextbox1' ).wikiEditor( 'addModule', 'templates' );
} );
} );

View file

@ -4,9 +4,9 @@
var textareaId = '#wpTextbox1';
var wikiEditorTests = {
// Add emoticons section
'add_sections_toolbar': {
'call': 'addToToolbar',
'data': {
add_sections_toolbar: {
call: 'addToToolbar',
data: {
'sections': {
'emoticons': {
'type': 'toolbar',
@ -14,30 +14,30 @@ var wikiEditorTests = {
}
}
},
'test': '*[rel=emoticons].section',
'pre': 0,
'post': 1
test: '*[rel=emoticons].section',
pre: 0,
post: 1
},
// Add faces group to emoticons section
'add_groups': {
'call': 'addToToolbar',
'data': {
'section': 'emoticons',
call: 'addToToolbar',
data: {
section: 'emoticons',
'groups': {
'faces': {
'label': 'Faces'
}
}
},
'test': '*[rel=emoticons].section *[rel=faces].group',
'pre': 0,
'post': 1
test: '*[rel=emoticons].section *[rel=faces].group',
pre: 0,
post: 1
},
// Add smile tool to faces group of emoticons section
'add_tools': {
'call': 'addToToolbar',
'data': {
'section': 'emoticons',
call: 'addToToolbar',
data: {
section: 'emoticons',
'group': 'faces',
'tools': {
'smile': {
@ -47,20 +47,20 @@ var wikiEditorTests = {
action: {
type: 'encapsulate',
options: {
pre: ":)"
pre: ':)'
}
}
}
}
},
'test': '*[rel=emoticons].section *[rel=faces].group *[rel=smile].tool',
'pre': 0,
'post': 1
test: '*[rel=emoticons].section *[rel=faces].group *[rel=smile].tool',
pre: 0,
post: 1
},
// Add info section
'add_sections_booklet': {
'call': 'addToToolbar',
'data': {
call: 'addToToolbar',
data: {
'sections': {
'info': {
'type': 'booklet',
@ -68,16 +68,16 @@ var wikiEditorTests = {
}
}
},
'test': '*[rel=info].section',
'pre': 0,
'post': 1
test: '*[rel=info].section',
pre: 0,
post: 1
},
// Add info section
'add_pages_table': {
'call': 'addToToolbar',
'data': {
'section': 'info',
'pages': {
call: 'addToToolbar',
data: {
section: 'info',
pages: {
'colors': {
'layout': 'table',
'label': 'Colors',
@ -89,44 +89,44 @@ var wikiEditorTests = {
}
}
},
'test': '*[rel=info].section *[rel=colors].page',
'pre': 0,
'post': 1
test: '*[rel=info].section *[rel=colors].page',
pre: 0,
post: 1
},
// Add colors rows
'add_rows': {
'call': 'addToToolbar',
'data': {
'section': 'info',
'page': 'colors',
call: 'addToToolbar',
data: {
section: 'info',
page: 'colors',
'rows': [
{
'name': { text: 'Red' },
'temp': { text: 'Warm' },
'swatch': { html: '<div style="width:10px;height:10px;background-color:red;">' }
'swatch': { html: '<div style="width: 10px; height: 10px; background-color: red;">' }
},
{
'name': { text: 'Blue' },
'temp': { text: 'Cold' },
'swatch': { html: '<div style="width:10px;height:10px;background-color:blue;">' }
'swatch': { html: '<div style="width: 10px; height: 10px; background-color: blue;">' }
},
{
'name': { text: 'Silver' },
'temp': { text: 'Neutral' },
'swatch': { html: '<div style="width:10px;height:10px;background-color:silver;">' }
'swatch': { html: '<div style="width: 10px; height: 10px; background-color: silver;">' }
}
]
},
'test': '*[rel=info].section *[rel=colors].page tr td',
'pre': 0,
'post': 9
test: '*[rel=info].section *[rel=colors].page tr td',
pre: 0,
post: 9
},
// Add
'add_pages_characters': {
'call': 'addToToolbar',
'data': {
'section': 'info',
'pages': {
call: 'addToToolbar',
data: {
section: 'info',
pages: {
'emoticons': {
'layout': 'characters',
'label': 'Emoticons'
@ -137,110 +137,118 @@ var wikiEditorTests = {
}
}
},
'test': '*[rel=info].section *[rel=emoticons].page',
'pre': 0,
'post': 1
test: '*[rel=info].section *[rel=emoticons].page',
pre: 0,
post: 1
},
// Add
'add_characters': {
'call': 'addToToolbar',
'data': {
'section': 'info',
'page': 'emoticons',
'characters': [ ':)', ':))', ':(', '<3', ';)' ]
call: 'addToToolbar',
data: {
section: 'info',
page: 'emoticons',
characters: [ ':)', ':))', ':(', '<3', ';)' ]
},
'test': '*[rel=info].section *[rel=emoticons].page *[rel=":)"]',
'pre': 0,
'post': 1
test: '*[rel=info].section *[rel=emoticons].page *[rel=":)"]',
pre: 0,
post: 1
},
// Remove page
'remove_page': {
'call': 'removeFromToolbar',
'data': {
'section': 'info',
'page': 'removeme'
call: 'removeFromToolbar',
data: {
section: 'info',
page: 'removeme'
},
'test': '*[rel=info].section *[rel=removeme].page',
'pre': 1,
'post': 0
test: '*[rel=info].section *[rel=removeme].page',
pre: 1,
post: 0
},
// Remove :)) from emoticon characters
'remove_character': {
'call': 'removeFromToolbar',
'data': {
'section': 'info',
'page': 'emoticons',
call: 'removeFromToolbar',
data: {
section: 'info',
page: 'emoticons',
'character': ':))'
},
'test': '*[rel=info].section *[rel=emoticons].page *[rel=":))"]',
'pre': 1,
'post': 0
test: '*[rel=info].section *[rel=emoticons].page *[rel=":))"]',
pre: 1,
post: 0
},
// Remove row from colors table of info section
'remove_row': {
'call': 'removeFromToolbar',
'data': {
'section': 'info',
'page': 'colors',
call: 'removeFromToolbar',
data: {
section: 'info',
page: 'colors',
'row': 0
},
'test': '*[rel=info].section *[rel=colors].page tr td',
'pre': 9,
'post': 6
test: '*[rel=info].section *[rel=colors].page tr td',
pre: 9,
post: 6
}
};
$(document).ready( function() {
var button = $( '<button>Run wikiEditor Tests!</button>' )
jQuery(document).ready( function ( $ ) {
var $button = $( '<button>Run wikiEditor Tests!</button>' )
.css( {
'position': 'fixed',
'bottom': 0,
'right': 0,
'width': '100%',
'backgroundColor': '#333333',
'opacity': 0.75,
'color': '#DDDDDD',
'padding': '0.5em',
'border': 'none',
'display': 'none'
position: 'fixed',
bottom: 0,
right: 0,
width: '100%',
backgroundColor: '#333',
opacity: 0.75,
color: '#DDDDDD',
padding: '0.7em',
border: 'none',
display: 'none'
} )
.click( function() {
if ( $(this).attr( 'enabled' ) == 'false' ) {
.click( function () {
if ( $(this).data( 'testDone' ) ) {
$(this).slideUp( 'fast' );
return false;
}
var messages = [ 'Running tests for wikiEditor API' ];
var $target = $( textareaId );
var $ui = $target.data( 'wikiEditor-context' ).$ui;
var passes = 0;
var tests = 0;
for ( var test in wikiEditorTests ) {
var pre = $ui.find( wikiEditorTests[test].test ).size() ==
wikiEditorTests[test].pre;
var test, pre, post,
messages = [ 'Running tests for wikiEditor API' ],
$target = $( textareaId ),
$ui = $target.data( 'wikiEditor-context' ).$ui,
passes = 0,
tests = 0;
for ( test in wikiEditorTests ) {
pre = $ui.find( wikiEditorTests[test].test ).length === wikiEditorTests[test].pre;
messages.push ( test + '-pre: ' + ( pre ? 'PASS' : 'FAIL' ) );
$target.wikiEditor(
wikiEditorTests[test].call,
wikiEditorTests[test].data
);
var post = $ui.find( wikiEditorTests[test].test ).size() ==
wikiEditorTests[test].post;
post = $ui.find( wikiEditorTests[test].test ).length === wikiEditorTests[test].post;
messages.push ( test + '-post: ' + ( post ? 'PASS' : 'FAIL' ) );
if ( pre && post ) {
passes++;
}
tests++;
}
if ( window.console !== undefined ) {
if ( window.console ) {
for ( var i = 0; i < messages.length; i++ ) {
console.log( messages[i] );
window.console.log( messages[i] );
}
}
$(this)
.attr( 'title', messages.join( " | " ) )
.text( passes + ' / ' + tests + ' were successful' )
.css( 'backgroundColor', passes < tests ? 'red' : 'green' )
.attr( 'enabled', 'false' )
.data( 'testDone', 'true' )
.blur();
} )
.appendTo( $( 'body' ) );
setTimeout( function() { button.slideDown( 'fast' ) }, 2000 );
setTimeout( function () {
$button.slideDown( 'fast' );
}, 1500 );
} );

File diff suppressed because it is too large Load diff

View file

@ -1,56 +1,68 @@
/*
/**
* CSS for WikiEditor Dialogs jQuery plugin
*/
.wikiEditor-toolbar-dialog table {
margin-top: 0.75em;
}
.wikiEditor-toolbar-dialog table td {
padding: 0.5em;
height: 3em;
overflow: visible;
}
/* Put suggestions (default z-index 99) on top of dialogs (z-index 1002) */
div.suggestions {
z-index: 1099;
}
.wikiEditor-toolbar-dialog table td {
padding: 0 !important;
}
.wikiEditor-toolbar-dialog .ui-dialog-content fieldset {
border: none !important;
margin: 0 !important;
padding: 0 !important;
}
.wikiEditor-toolbar-dialog .ui-widget-header {
border-bottom:1px solid #6bc8f3 !important;
}
.wikiEditor-toolbar-dialog .ui-dialog-content input[type=text] {
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-webkit-box-sizing: border-box;
-khtml-box-sizing: border-box;
}
.wikiEditor-toolbar-dialog .ui-dialog-content input[type="radio"],
.wikiEditor-toolbar-dialog .ui-dialog-content input[type="checkbox"] {
margin-left: 0;
}
.wikiEditor-toolbar-dialog .ui-dialog-titlebar-close {
padding: 0;
}
body .wikiEditor-toolbar-dialog .ui-dialog-titlebar-close {
right: 0.9em;
}
.wikieditor-toolbar-field-wrapper {
padding: 0 0 25px 0;
}
.wikieditor-toolbar-floated-field-wrapper {
float: left;
margin-right: 2em;
}
.wikieditor-toolbar-dialog-hint {
color: #999999;
}
.wikiEditor-toolbar-dialog {
border: none;
}

View file

@ -1,217 +1,231 @@
/**
* Dialog Module for wikiEditor
*/
( function( $ ) { $.wikiEditor.modules.dialogs = {
( function ( $, mw ) {
/**
* Compatability map
*/
'browsers': {
// Left-to-right languages
'ltr': {
'msie': [['>=', 7]],
// jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
'firefox': [
['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
],
'opera': [['>=', 9.6]],
'safari': [['>=', 3]],
'chrome': [['>=', 3]]
},
// Right-to-left languages
'rtl': {
'msie': [['>=', 7]],
// jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
'firefox': [
['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
],
'opera': [['>=', 9.6]],
'safari': [['>=', 3]],
'chrome': [['>=', 3]]
}
},
/**
* API accessible functions
*/
api: {
addDialog: function( context, data ) {
$.wikiEditor.modules.dialogs.fn.create( context, data )
},
openDialog: function( context, module ) {
if ( module in $.wikiEditor.modules.dialogs.modules ) {
var mod = $.wikiEditor.modules.dialogs.modules[module];
var $dialog = $( '#' + mod.id );
if ( $dialog.length == 0 ) {
$.wikiEditor.modules.dialogs.fn.reallyCreate( context, mod, module );
$dialog = $( '#' + mod.id );
}
$.wikiEditor.modules.dialogs = {
// Workaround for bug in jQuery UI: close button in top right retains focus
$dialog.closest( '.ui-dialog' )
.find( '.ui-dialog-titlebar-close' )
.removeClass( 'ui-state-focus' );
$dialog.dialog( 'open' );
}
},
closeDialog: function( context, module ) {
if ( module in $.wikiEditor.modules.dialogs.modules ) {
$( '#' + $.wikiEditor.modules.dialogs.modules[module].id ).dialog( 'close' );
}
}
},
/**
* Internally used functions
*/
fn: {
/**
* Creates a dialog module within a wikiEditor
*
* @param {Object} context Context object of editor to create module in
* @param {Object} config Configuration object to create module from
* Compatability map
*/
create: function( context, config ) {
// Defer building of modules, but do check whether they need the iframe rightaway
for ( var mod in config ) {
var module = config[mod];
// Only create the dialog if it's supported, isn't filtered and doesn't exist yet
var filtered = false;
if ( typeof module.filters != 'undefined' ) {
for ( var i = 0; i < module.filters.length; i++ ) {
if ( $( module.filters[i] ).length == 0 ) {
filtered = true;
break;
browsers: {
// Left-to-right languages
ltr: {
msie: [['>=', 7]],
// jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
firefox: [
['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
],
opera: [['>=', 9.6]],
safari: [['>=', 3]],
chrome: [['>=', 3]]
},
// Right-to-left languages
rtl: {
msie: [['>=', 7]],
// jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
firefox: [
['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
],
opera: [['>=', 9.6]],
safari: [['>=', 3]],
chrome: [['>=', 3]]
}
},
/**
* API accessible functions
*/
api: {
addDialog: function ( context, data ) {
$.wikiEditor.modules.dialogs.fn.create( context, data );
},
openDialog: function ( context, module ) {
if ( module in $.wikiEditor.modules.dialogs.modules ) {
var mod = $.wikiEditor.modules.dialogs.modules[module];
var $dialog = $( '#' + mod.id );
if ( $dialog.length === 0 ) {
$.wikiEditor.modules.dialogs.fn.reallyCreate( context, mod, module );
$dialog = $( '#' + mod.id );
}
// Workaround for bug in jQuery UI: close button in top right retains focus
$dialog.closest( '.ui-dialog' )
.find( '.ui-dialog-titlebar-close' )
.removeClass( 'ui-state-focus' );
$dialog.dialog( 'open' );
}
},
closeDialog: function ( context, module ) {
if ( module in $.wikiEditor.modules.dialogs.modules ) {
$( '#' + $.wikiEditor.modules.dialogs.modules[module].id ).dialog( 'close' );
}
}
},
/**
* Internally used functions
*/
fn: {
/**
* Creates a dialog module within a wikiEditor
*
* @param {Object} context Context object of editor to create module in
* @param {Object} config Configuration object to create module from
*/
create: function ( context, config ) {
var mod, module, filtered, i, $existingDialog;
// Defer building of modules, but do check whether they need the iframe rightaway
for ( mod in config ) {
module = config[mod];
// Only create the dialog if it's supported, isn't filtered and doesn't exist yet
filtered = false;
if ( typeof module.filters != 'undefined' ) {
for ( i = 0; i < module.filters.length; i++ ) {
if ( $( module.filters[i] ).length === 0 ) {
filtered = true;
break;
}
}
}
// If the dialog already exists, but for another textarea, simply remove it
$existingDialog = $( '#' + module.id );
if ( $existingDialog.length > 0 && $existingDialog.data( 'context' ).$textarea != context.$textarea ) {
$existingDialog.remove();
}
// Re-select from the DOM, we might have removed the dialog just now
$existingDialog = $( '#' + module.id );
if ( !filtered && $.wikiEditor.isSupported( module ) && $existingDialog.length === 0 ) {
$.wikiEditor.modules.dialogs.modules[mod] = module;
// If this dialog requires the iframe, set it up
if ( typeof context.$iframe === 'undefined' && $.wikiEditor.isRequired( module, 'iframe' ) ) {
context.fn.setupIframe();
}
context.$textarea.trigger( 'wikiEditor-dialogs-setup-' + mod );
// If this dialog requires immediate creation, create it now
if ( typeof module.immediateCreate !== 'undefined' && module.immediateCreate ) {
$.wikiEditor.modules.dialogs.fn.reallyCreate( context, module, mod );
}
}
}
// If the dialog already exists, but for another textarea, simply remove it
var $existingDialog = $( '#' + module.id );
if ( $existingDialog.length > 0 && $existingDialog.data( 'context' ).$textarea != context.$textarea ) {
$existingDialog.remove();
},
/**
* Build the actual dialog. This done on-demand rather than in create()
* @param {Object} context Context object of editor dialog belongs to
* @param {Object} module Dialog module object
* @param {String} name Dialog name (key in $.wikiEditor.modules.dialogs.modules)
*/
reallyCreate: function ( context, module, name ) {
var msg,
configuration = module.dialog;
// Add some stuff to configuration
configuration.bgiframe = true;
configuration.autoOpen = false;
configuration.modal = true;
configuration.title = $.wikiEditor.autoMsg( module, 'title' );
// Transform messages in keys
// Stupid JS won't let us do stuff like
// foo = { mw.msg( 'bar' ): baz }
configuration.newButtons = {};
for ( msg in configuration.buttons ) {
configuration.newButtons[mw.msg( msg )] = configuration.buttons[msg];
}
// Re-select from the DOM, we might have removed the dialog just now
$existingDialog = $( '#' + module.id );
if ( !filtered && $.wikiEditor.isSupported( module ) && $existingDialog.size() === 0 ) {
$.wikiEditor.modules.dialogs.modules[mod] = module;
// If this dialog requires the iframe, set it up
if ( typeof context.$iframe == 'undefined' && $.wikiEditor.isRequired( module, 'iframe' ) ) {
context.fn.setupIframe();
}
context.$textarea.trigger( 'wikiEditor-dialogs-setup-' + mod );
// If this dialog requires immediate creation, create it now
if ( typeof module.immediateCreate !== 'undefined' && module.immediateCreate ) {
$.wikiEditor.modules.dialogs.fn.reallyCreate( context, module, mod );
}
configuration.buttons = configuration.newButtons;
// Create the dialog <div>
var dialogDiv = $( '<div>' )
.attr( 'id', module.id )
.html( module.html )
.data( 'context', context )
.appendTo( $( 'body' ) )
.each( module.init )
.dialog( configuration );
// Set tabindexes on buttons added by .dialog()
$.wikiEditor.modules.dialogs.fn.setTabindexes( dialogDiv.closest( '.ui-dialog' )
.find( 'button' ).not( '[tabindex]' ) );
if ( !( 'resizeme' in module ) || module.resizeme ) {
dialogDiv
.bind( 'dialogopen', $.wikiEditor.modules.dialogs.fn.resize )
.find( '.ui-tabs' ).bind( 'tabsshow', function () {
$(this).closest( '.ui-dialog-content' ).each(
$.wikiEditor.modules.dialogs.fn.resize );
});
}
dialogDiv.bind( 'dialogclose', function () {
context.fn.restoreSelection();
} );
// Let the outside world know we set up this dialog
context.$textarea.trigger( 'wikiEditor-dialogs-loaded-' + name );
},
/**
* Resize a dialog so its contents fit
*
* Usage: dialog.each( resize ); or dialog.bind( 'blah', resize );
* NOTE: This function assumes $.ui.dialog has already been loaded
*/
resize: function () {
var wrapper = $(this).closest( '.ui-dialog' );
var oldWidth = wrapper.width();
// Make sure elements don't wrapped so we get an accurate idea of whether they really fit. Also temporarily show
// hidden elements. Work around jQuery bug where <div style="display: inline;"/> inside a dialog is both
// :visible and :hidden
var oldHidden = $(this).find( '*' ).not( ':visible' );
// Save the style attributes of the hidden elements to restore them later. Calling hide() after show() messes up
// for elements hidden with a class
oldHidden.each( function () {
$(this).data( 'oldstyle', $(this).attr( 'style' ) );
});
oldHidden.show();
var oldWS = $(this).css( 'white-space' );
$(this).css( 'white-space', 'nowrap' );
if ( wrapper.width() <= $(this).get(0).scrollWidth ) {
var thisWidth = $(this).data( 'thisWidth' ) ? $(this).data( 'thisWidth' ) : 0;
thisWidth = Math.max( $(this).get(0).width, thisWidth );
$(this).width( thisWidth );
$(this).data( 'thisWidth', thisWidth );
var wrapperWidth = $(this).data( 'wrapperWidth' ) ? $(this).data( 'wrapperWidth' ) : 0;
wrapperWidth = Math.max( wrapper.get(0).scrollWidth, wrapperWidth );
wrapper.width( wrapperWidth );
$(this).data( 'wrapperWidth', wrapperWidth );
$(this).dialog( { 'width': wrapper.width() } );
wrapper.css( 'left', parseInt( wrapper.css( 'left' ), 10 ) - ( wrapper.width() - oldWidth ) / 2 );
}
$(this).css( 'white-space', oldWS );
oldHidden.each( function () {
$(this).attr( 'style', $(this).data( 'oldstyle' ) );
});
},
/**
* Set the right tabindexes on elements in a dialog
* @param $elements Elements to set tabindexes on. If they already have tabindexes, this function can behave a bit weird
*/
setTabindexes: function ( $elements ) {
// Get the highest tab index
var tabIndex = $( document ).lastTabIndex() + 1;
$elements.each( function () {
$(this).attr( 'tabindex', tabIndex++ );
} );
}
},
/**
* Build the actual dialog. This done on-demand rather than in create()
* @param {Object} context Context object of editor dialog belongs to
* @param {Object} module Dialog module object
* @param {String} name Dialog name (key in $.wikiEditor.modules.dialogs.modules)
*/
reallyCreate: function( context, module, name ) {
var configuration = module.dialog;
// Add some stuff to configuration
configuration.bgiframe = true;
configuration.autoOpen = false;
configuration.modal = true;
configuration.title = $.wikiEditor.autoMsg( module, 'title' );
// Transform messages in keys
// Stupid JS won't let us do stuff like
// foo = { mediaWiki.msg( 'bar' ): baz }
configuration.newButtons = {};
for ( var msg in configuration.buttons )
configuration.newButtons[mediaWiki.msg( msg )] = configuration.buttons[msg];
configuration.buttons = configuration.newButtons;
// Create the dialog <div>
var dialogDiv = $( '<div />' )
.attr( 'id', module.id )
.html( module.html )
.data( 'context', context )
// This stuff is just hanging here, perhaps we could come up with a better home for this stuff
modules: {},
quickDialog: function ( body, settings ) {
$( '<div>' )
.text( body )
.appendTo( $( 'body' ) )
.each( module.init )
.dialog( configuration );
// Set tabindexes on buttons added by .dialog()
$.wikiEditor.modules.dialogs.fn.setTabindexes( dialogDiv.closest( '.ui-dialog' )
.find( 'button' ).not( '[tabindex]' ) );
if ( !( 'resizeme' in module ) || module.resizeme ) {
dialogDiv
.bind( 'dialogopen', $.wikiEditor.modules.dialogs.fn.resize )
.find( '.ui-tabs' ).bind( 'tabsshow', function() {
$(this).closest( '.ui-dialog-content' ).each(
$.wikiEditor.modules.dialogs.fn.resize );
});
}
dialogDiv.bind( 'dialogclose', function() {
context.fn.restoreSelection();
} );
// Let the outside world know we set up this dialog
context.$textarea.trigger( 'wikiEditor-dialogs-loaded-' + name );
},
/**
* Resize a dialog so its contents fit
*
* Usage: dialog.each( resize ); or dialog.bind( 'blah', resize );
* NOTE: This function assumes $.ui.dialog has already been loaded
*/
resize: function() {
var wrapper = $(this).closest( '.ui-dialog' );
var oldWidth = wrapper.width();
// Make sure elements don't wrapped so we get an accurate idea of whether they really fit. Also temporarily show
// hidden elements. Work around jQuery bug where <div style="display:inline;" /> inside a dialog is both
// :visible and :hidden
var oldHidden = $(this).find( '*' ).not( ':visible' );
// Save the style attributes of the hidden elements to restore them later. Calling hide() after show() messes up
// for elements hidden with a class
oldHidden.each( function() {
$(this).data( 'oldstyle', $(this).attr( 'style' ) );
});
oldHidden.show();
var oldWS = $(this).css( 'white-space' );
$(this).css( 'white-space', 'nowrap' );
if ( wrapper.width() <= $(this).get(0).scrollWidth ) {
var thisWidth = $(this).data( 'thisWidth' ) ? $(this).data( 'thisWidth' ) : 0;
thisWidth = Math.max( $(this).get(0).width, thisWidth );
$(this).width( thisWidth );
$(this).data( 'thisWidth', thisWidth );
var wrapperWidth = $(this).data( 'wrapperWidth' ) ? $(this).data( 'wrapperWidth' ) : 0;
wrapperWidth = Math.max( wrapper.get(0).scrollWidth, wrapperWidth );
wrapper.width( wrapperWidth );
$(this).data( 'wrapperWidth', wrapperWidth );
$(this).dialog( { 'width': wrapper.width() } );
wrapper.css( 'left', parseInt( wrapper.css( 'left' ) ) - ( wrapper.width() - oldWidth ) / 2 );
}
$(this).css( 'white-space', oldWS );
oldHidden.each( function() {
$(this).attr( 'style', $(this).data( 'oldstyle' ) );
});
},
/**
* Set the right tabindexes on elements in a dialog
* @param $elements Elements to set tabindexes on. If they already have tabindexes, this function can behave a bit weird
*/
setTabindexes: function( $elements ) {
// Get the highest tab index
var tabIndex = $( document ).lastTabIndex() + 1;
$elements.each( function() {
$(this).attr( 'tabindex', tabIndex++ );
} );
.dialog( $.extend( {
bgiframe: true,
modal: true
}, settings ) )
.dialog( 'open' );
}
},
// This stuff is just hanging here, perhaps we could come up with a better home for this stuff
modules: {},
quickDialog: function( body, settings ) {
$( '<div />' )
.text( body )
.appendTo( $( 'body' ) )
.dialog( $.extend( {
bgiframe: true,
modal: true
}, settings ) )
.dialog( 'open' );
}
}; } ) ( jQuery );
};
}( jQuery, mediaWiki ) );

View file

@ -1,357 +1,373 @@
/* Highlight module for wikiEditor */
( function( $ ) { $.wikiEditor.modules.highlight = {
( function ( $ ) {
/**
* Core Requirements
*/
'req': [ 'iframe' ],
/**
* Configuration
*/
'cfg': {
'styleVersion': 3
},
/**
* Internally used event handlers
*/
'evt': {
'delayedChange': function( context, event ) {
if ( event.data.scope == 'realchange' ) {
$.wikiEditor.modules.highlight = {
/**
* Core Requirements
*/
req: [ 'iframe' ],
/**
* Configuration
*/
cfg: {
styleVersion: 3
},
/**
* Internally used event handlers
*/
evt: {
delayedChange: function ( context, event ) {
if ( event.data.scope == 'realchange' ) {
$.wikiEditor.modules.highlight.fn.scan( context );
$.wikiEditor.modules.highlight.fn.mark( context, event.data.scope );
}
},
ready: function ( context, event ) {
$.wikiEditor.modules.highlight.fn.scan( context );
$.wikiEditor.modules.highlight.fn.mark( context, event.data.scope );
$.wikiEditor.modules.highlight.fn.mark( context, 'ready' );
}
},
'ready': function( context, event ) {
$.wikiEditor.modules.highlight.fn.scan( context );
$.wikiEditor.modules.highlight.fn.mark( context, 'ready' );
}
},
/**
* Internally used functions
*/
'fn': {
/**
* Creates a highlight module within a wikiEditor
*
* @param config Configuration object to create module from
* Internally used functions
*/
'create': function( context, config ) {
context.modules.highlight.markersStr = '';
},
/**
* Scans text division for tokens
*
* @param division
*/
'scan': function( context, division ) {
// Remove all existing tokens
var tokenArray = context.modules.highlight.tokenArray = [];
// Scan text for new tokens
var text = context.fn.getContents();
// Perform a scan for each module which provides any expressions to scan for
// FIXME: This traverses the entire string once for every regex. Investigate
// whether |-concatenating regexes then traversing once is faster.
for ( var module in context.modules ) {
if ( module in $.wikiEditor.modules && 'exp' in $.wikiEditor.modules[module] ) {
for ( var exp in $.wikiEditor.modules[module].exp ) {
// Prepare configuration
var regex = $.wikiEditor.modules[module].exp[exp].regex;
var label = $.wikiEditor.modules[module].exp[exp].label;
var markAfter = $.wikiEditor.modules[module].exp[exp].markAfter || false;
// Search for tokens
var offset = 0, left, right, match;
while ( ( match = text.substr( offset ).match( regex ) ) != null ) {
right = ( left = offset + match.index ) + match[0].length;
tokenArray[tokenArray.length] = {
'offset': markAfter ? right : left,
'label': label,
'tokenStart': left,
'match': match
};
// Move to the right of this match
offset = right;
fn: {
/**
* Creates a highlight module within a wikiEditor
*
* @param config Configuration object to create module from
*/
create: function ( context, config ) {
context.modules.highlight.markersStr = '';
},
/**
* Scans text division for tokens
*
* @param division
*/
scan: function ( context, division ) {
var tokenArray, text, module, exp,
left, right, match;
/*jshint eqnull: true */
// Remove all existing tokens
tokenArray = context.modules.highlight.tokenArray = [];
// Scan text for new tokens
text = context.fn.getContents();
// Perform a scan for each module which provides any expressions to scan for
// FIXME: This traverses the entire string once for every regex. Investigate
// whether |-concatenating regexes then traversing once is faster.
for ( module in context.modules ) {
if ( module in $.wikiEditor.modules && 'exp' in $.wikiEditor.modules[module] ) {
for ( exp in $.wikiEditor.modules[module].exp ) {
// Prepare configuration
var regex = $.wikiEditor.modules[module].exp[exp].regex;
var label = $.wikiEditor.modules[module].exp[exp].label;
var markAfter = $.wikiEditor.modules[module].exp[exp].markAfter || false;
// Search for tokens
var offset = 0;
while ( ( match = text.substr( offset ).match( regex ) ) != null ) {
right = ( left = offset + match.index ) + match[0].length;
tokenArray[tokenArray.length] = {
offset: markAfter ? right : left,
label: label,
tokenStart: left,
match: match
};
// Move to the right of this match
offset = right;
}
}
}
}
}
// Sort by start
tokenArray.sort( function( a, b ) { return a.tokenStart - b.tokenStart; } );
// Let the world know, a scan just happened!
context.fn.trigger( 'scan' );
},
/**
* Marks up text with HTML
*
* @param division
* @param tokens
*/
// FIXME: What do division and tokens do?
// TODO: Document the scan() and mark() APIs somewhere
'mark': function( context, division, tokens ) {
// Reset markers
var markers = [];
// Sort by start
tokenArray.sort( function ( a, b ) {
return a.tokenStart - b.tokenStart;
} );
// Let the world know, a scan just happened!
context.fn.trigger( 'scan' );
},
// Recycle markers that will be skipped in this run
if ( context.modules.highlight.markers && division != '' ) {
for ( var i = 0; i < context.modules.highlight.markers.length; i++ ) {
if ( context.modules.highlight.markers[i].skipDivision == division ) {
markers.push( context.modules.highlight.markers[i] );
}
}
}
context.modules.highlight.markers = markers;
/**
* Marks up text with HTML
*
* @param division
* @param tokens
*/
// FIXME: What do division and tokens do?
// TODO: Document the scan() and mark() APIs somewhere
mark: function ( context, division, tokens ) {
var i, subtracted, oldLength, j, o;
// Get all markers
context.fn.trigger( 'mark' );
markers.sort( function( a, b ) { return a.start - b.start || a.end - b.end; } );
// Reset markers
var markers = [];
// Serialize the markers array to a string and compare it with the one stored in the previous run - if they're
// equal, there's no markers to change
var markersStr = '';
for ( var i = 0; i < markers.length; i++ ) {
markersStr += markers[i].start + ',' + markers[i].end + ',' + markers[i].type + ',';
}
if ( context.modules.highlight.markersStr == markersStr ) {
// No change, bail out
return;
}
context.modules.highlight.markersStr = markersStr;
// Traverse the iframe DOM, inserting markers where they're needed - store visited markers here so we know which
// markers should be removed
var visited = [], v = 0;
for ( var i = 0; i < markers.length; i++ ) {
if ( typeof markers[i].skipDivision !== 'undefined' && ( division == markers[i].skipDivision ) ) {
continue;
}
// We want to isolate each marker, so we may need to split textNodes if a marker starts or ends halfway one.
var start = markers[i].start;
var s = context.fn.getOffset( start );
if ( !s ) {
// This shouldn't happen
continue;
}
var startNode = s.node;
// Don't wrap leading BRs, produces undesirable results
// FIXME: It's also possible that the offset is a bit high because getOffset() has incremented .length to
// fake the newline caused by startNode being in a P. In this case, prevent the textnode splitting below
// from making startNode an empty textnode, IE barfs on that
while ( startNode.nodeName == 'BR' || s.offset == startNode.nodeValue.length ) {
start++;
s = context.fn.getOffset( start );
startNode = s.node;
}
// The next marker starts somewhere in this textNode or at this BR
if ( s.offset > 0 && s.node.nodeName == '#text' ) {
// Split off the prefix - this leaves the prefix in the current node and puts the rest in a new node
// which is our start node
var newStartNode = startNode.splitText( s.offset < s.node.nodeValue.length ?
s.offset : s.node.nodeValue.length - 1
);
var oldStartNode = startNode;
startNode = newStartNode;
// Update offset objects. We don't need purgeOffsets(), simply manipulating the existing offset objects
// will suffice
// FIXME: This manipulates context.offsets directly, which is ugly, but the performance improvement vs.
// purgeOffsets() is worth it - this code doesn't set lastTextNode to newStartNode for offset objects
// with lastTextNode == oldStartNode, but that doesn't really matter
var subtracted = s.offset;
var oldLength = s.length;
var j, o;
// Update offset objects referring to oldStartNode
for ( j = start - subtracted; j < start; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = oldStartNode;
o.length = subtracted;
}
}
// Update offset objects referring to newStartNode
for ( j = start; j < start - subtracted + oldLength; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = newStartNode;
o.offset -= subtracted;
o.length -= subtracted;
o.lastTextNode = oldStartNode;
// Recycle markers that will be skipped in this run
if ( context.modules.highlight.markers && division !== '' ) {
for ( i = 0; i < context.modules.highlight.markers.length; i++ ) {
if ( context.modules.highlight.markers[i].skipDivision == division ) {
markers.push( context.modules.highlight.markers[i] );
}
}
}
var end = markers[i].end;
// To avoid ending up at the first char of the next node, we grab the offset for end - 1 and add one to the
// offset
var e = context.fn.getOffset( end - 1 );
if ( !e ) {
// This shouldn't happen
continue;
context.modules.highlight.markers = markers;
// Get all markers
context.fn.trigger( 'mark' );
markers.sort( function ( a, b ) {
return a.start - b.start || a.end - b.end;
} );
// Serialize the markers array to a string and compare it with the one stored in the previous run - if they're
// equal, there's no markers to change
var markersStr = '';
for ( i = 0; i < markers.length; i++ ) {
markersStr += markers[i].start + ',' + markers[i].end + ',' + markers[i].type + ',';
}
var endNode = e.node;
if ( e.offset + 1 < e.length - 1 && endNode.nodeName == '#text' ) {
// Split off the suffix. This puts the suffix in a new node and leaves the rest in endNode
var oldEndNode = endNode;
var newEndNode = endNode.splitText( e.offset + 1 );
// Update offset objects
var subtracted = e.offset + 1;
var oldLength = e.length;
var j, o;
// Update offset objects referring to oldEndNode
for ( j = end - subtracted; j < end; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = oldEndNode;
o.length = subtracted;
if ( context.modules.highlight.markersStr == markersStr ) {
// No change, bail out
return;
}
context.modules.highlight.markersStr = markersStr;
// Traverse the iframe DOM, inserting markers where they're needed - store visited markers here so we know which
// markers should be removed
var visited = [], v = 0;
for ( i = 0; i < markers.length; i++ ) {
if ( typeof markers[i].skipDivision !== 'undefined' && ( division == markers[i].skipDivision ) ) {
continue;
}
// We want to isolate each marker, so we may need to split textNodes if a marker starts or ends halfway one.
var start = markers[i].start;
var s = context.fn.getOffset( start );
if ( !s ) {
// This shouldn't happen
continue;
}
var startNode = s.node;
// Don't wrap leading BRs, produces undesirable results
// FIXME: It's also possible that the offset is a bit high because getOffset() has incremented .length to
// fake the newline caused by startNode being in a P. In this case, prevent the textnode splitting below
// from making startNode an empty textnode, IE barfs on that
while ( startNode.nodeName === 'BR' || s.offset === startNode.nodeValue.length ) {
start++;
s = context.fn.getOffset( start );
startNode = s.node;
}
// The next marker starts somewhere in this textNode or at this BR
if ( s.offset > 0 && s.node.nodeName == '#text' ) {
// Split off the prefix - this leaves the prefix in the current node and puts the rest in a new node
// which is our start node
var newStartNode = startNode.splitText( s.offset < s.node.nodeValue.length ?
s.offset : s.node.nodeValue.length - 1
);
var oldStartNode = startNode;
startNode = newStartNode;
// Update offset objects. We don't need purgeOffsets(), simply manipulating the existing offset objects
// will suffice
// FIXME: This manipulates context.offsets directly, which is ugly, but the performance improvement vs.
// purgeOffsets() is worth it - this code doesn't set lastTextNode to newStartNode for offset objects
// with lastTextNode == oldStartNode, but that doesn't really matter
subtracted = s.offset;
oldLength = s.length;
// Update offset objects referring to oldStartNode
for ( j = start - subtracted; j < start; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = oldStartNode;
o.length = subtracted;
}
}
// Update offset objects referring to newStartNode
for ( j = start; j < start - subtracted + oldLength; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = newStartNode;
o.offset -= subtracted;
o.length -= subtracted;
o.lastTextNode = oldStartNode;
}
}
}
// We have to insert this one, as it might not exist: we didn't call getOffset( end )
context.offsets[end] = {
'node': newEndNode,
'offset': 0,
'length': oldLength - subtracted,
'lastTextNode': oldEndNode
};
// Update offset objects referring to newEndNode
for ( j = end + 1; j < end - subtracted + oldLength; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = newEndNode;
o.offset -= subtracted;
o.length -= subtracted;
o.lastTextNode = oldEndNode;
var end = markers[i].end;
// To avoid ending up at the first char of the next node, we grab the offset for end - 1 and add one to the
// offset
var e = context.fn.getOffset( end - 1 );
if ( !e ) {
// This shouldn't happen
continue;
}
var endNode = e.node;
if ( e.offset + 1 < e.length - 1 && endNode.nodeName == '#text' ) {
// Split off the suffix. This puts the suffix in a new node and leaves the rest in endNode
var oldEndNode = endNode;
var newEndNode = endNode.splitText( e.offset + 1 );
// Update offset objects
subtracted = e.offset + 1;
oldLength = e.length;
// Update offset objects referring to oldEndNode
for ( j = end - subtracted; j < end; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = oldEndNode;
o.length = subtracted;
}
}
// We have to insert this one, as it might not exist: we didn't call getOffset( end )
context.offsets[end] = {
'node': newEndNode,
'offset': 0,
'length': oldLength - subtracted,
'lastTextNode': oldEndNode
};
// Update offset objects referring to newEndNode
for ( j = end + 1; j < end - subtracted + oldLength; j++ ) {
if ( j in context.offsets ) {
o = context.offsets[j];
o.node = newEndNode;
o.offset -= subtracted;
o.length -= subtracted;
o.lastTextNode = oldEndNode;
}
}
}
}
// Don't wrap trailing BRs, doing that causes weird issues
if ( endNode.nodeName == 'BR' ) {
endNode = e.lastTextNode;
}
// If startNode and endNode have different parents, we need to pull endNode and all textnodes in between
// into startNode's parent and replace </p><p> with <br>
if ( startNode.parentNode != endNode.parentNode ) {
var startP = $( startNode ).closest( 'p' ).get( 0 );
var t = new context.fn.rawTraverser( startNode, startP, context.$content.get( 0 ), false );
var afterStart = startNode.nextSibling;
var lastP = startP;
var nextT = t.next();
while ( nextT && t.node != endNode ) {
t = nextT;
nextT = t.next();
// If t.node has a different parent, merge t.node.parentNode with startNode.parentNode
if ( t.node.parentNode != startNode.parentNode ) {
var oldParent = t.node.parentNode;
if ( afterStart ) {
if ( lastP != t.inP ) {
// We're entering a new <p>, insert a <br>
startNode.parentNode.insertBefore(
startNode.ownerDocument.createElement( 'br' ),
afterStart
);
}
// A <p> with just a <br> in it is an empty line, so let's not bother with unwrapping it
if ( !( oldParent.childNodes.length == 1 && oldParent.firstChild.nodeName == 'BR' ) ) {
// Move all children of oldParent into startNode's parent
while ( oldParent.firstChild ) {
startNode.parentNode.insertBefore( oldParent.firstChild, afterStart );
}
}
} else {
if ( lastP != t.inP ) {
// We're entering a new <p>, insert a <br>
startNode.parentNode.appendChild(
startNode.ownerDocument.createElement( 'br' )
);
}
// A <p> with just a <br> in it is an empty line, so let's not bother with unwrapping it
if ( !( oldParent.childNodes.length == 1 && oldParent.firstChild.nodeName == 'BR' ) ) {
// Move all children of oldParent into startNode's parent
while ( oldParent.firstChild ) {
startNode.parentNode.appendChild( oldParent.firstChild );
// Don't wrap trailing BRs, doing that causes weird issues
if ( endNode.nodeName == 'BR' ) {
endNode = e.lastTextNode;
}
// If startNode and endNode have different parents, we need to pull endNode and all textnodes in between
// into startNode's parent and replace </p><p> with <br>
if ( startNode.parentNode !== endNode.parentNode ) {
var startP = $( startNode ).closest( 'p' ).get( 0 );
var t = new context.fn.rawTraverser( startNode, startP, context.$content.get( 0 ), false );
var afterStart = startNode.nextSibling;
var lastP = startP;
var nextT = t.next();
while ( nextT && t.node !== endNode ) {
t = nextT;
nextT = t.next();
// If t.node has a different parent, merge t.node.parentNode with startNode.parentNode
if ( t.node.parentNode !== startNode.parentNode ) {
var oldParent = t.node.parentNode;
if ( afterStart ) {
if ( lastP !== t.inP ) {
// We're entering a new <p>, insert a <br>
startNode.parentNode.insertBefore(
startNode.ownerDocument.createElement( 'br' ),
afterStart
);
}
// A <p> with just a <br> in it is an empty line, so let's not bother with unwrapping it
if ( !( oldParent.childNodes.length == 1 && oldParent.firstChild.nodeName == 'BR' ) ) {
// Move all children of oldParent into startNode's parent
while ( oldParent.firstChild ) {
startNode.parentNode.insertBefore( oldParent.firstChild, afterStart );
}
}
} else {
if ( lastP !== t.inP ) {
// We're entering a new <p>, insert a <br>
startNode.parentNode.appendChild(
startNode.ownerDocument.createElement( 'br' )
);
}
// A <p> with just a <br> in it is an empty line, so let's not bother with unwrapping it
if ( !( oldParent.childNodes.length == 1 && oldParent.firstChild.nodeName == 'BR' ) ) {
// Move all children of oldParent into startNode's parent
while ( oldParent.firstChild ) {
startNode.parentNode.appendChild( oldParent.firstChild );
}
}
}
// Remove oldParent, which is now empty
oldParent.parentNode.removeChild( oldParent );
}
// Remove oldParent, which is now empty
oldParent.parentNode.removeChild( oldParent );
lastP = t.inP;
}
lastP = t.inP;
// Moving nodes around like this invalidates offset objects
// TODO: Update offset objects ourselves for performance. Requires rewriting this code block to be
// offset-based rather than traverser-based
}
// Moving nodes around like this invalidates offset objects
// TODO: Update offset objects ourselves for performance. Requires rewriting this code block to be
// offset-based rather than traverser-based
}
// Now wrap everything between startNode and endNode (may be equal).
var ca1 = startNode, ca2 = endNode;
if ( ca1 && ca2 && ca1.parentNode ) {
var anchor = markers[i].getAnchor( ca1, ca2 );
if ( !anchor ) {
var commonAncestor = ca1.parentNode;
if ( markers[i].anchor == 'wrap') {
// We have to store things like .parentNode and .nextSibling because appendChild() changes these
var newNode = ca1.ownerDocument.createElement( 'span' );
var nextNode = ca2.nextSibling;
// Append all nodes between ca1 and ca2 (inclusive) to newNode
var n = ca1;
while ( n != nextNode ) {
var ns = n.nextSibling;
newNode.appendChild( n );
n = ns;
// Now wrap everything between startNode and endNode (may be equal).
var ca1 = startNode, ca2 = endNode;
if ( ca1 && ca2 && ca1.parentNode ) {
var anchor = markers[i].getAnchor( ca1, ca2 );
if ( !anchor ) {
var commonAncestor = ca1.parentNode;
if ( markers[i].anchor == 'wrap') {
// We have to store things like .parentNode and .nextSibling because appendChild() changes these
var newNode = ca1.ownerDocument.createElement( 'span' );
var nextNode = ca2.nextSibling;
// Append all nodes between ca1 and ca2 (inclusive) to newNode
var n = ca1;
while ( n !== nextNode ) {
var ns = n.nextSibling;
newNode.appendChild( n );
n = ns;
}
// Insert newNode in the right place
if ( nextNode ) {
commonAncestor.insertBefore( newNode, nextNode );
} else {
commonAncestor.appendChild( newNode );
}
anchor = newNode;
} else if ( markers[i].anchor == 'tag' ) {
anchor = commonAncestor;
}
// Insert newNode in the right place
if ( nextNode ) {
commonAncestor.insertBefore( newNode, nextNode );
} else {
commonAncestor.appendChild( newNode );
}
anchor = newNode;
} else if ( markers[i].anchor == 'tag' ) {
anchor = commonAncestor;
}
$( anchor ).data( 'marker', markers[i] ).addClass( 'wikiEditor-highlight' );
// Allow the module adding this marker to manipulate it
markers[i].afterWrap( anchor, markers[i] );
$( anchor ).data( 'marker', markers[i] ).addClass( 'wikiEditor-highlight' );
// Allow the module adding this marker to manipulate it
markers[i].afterWrap( anchor, markers[i] );
} else {
// Update the marker object
$( anchor ).data( 'marker', markers[i] );
if ( typeof markers[i].onSkip == 'function' ) {
markers[i].onSkip( anchor );
}
}
visited[v++] = anchor;
}
}
// Remove markers that were previously inserted but weren't passed to this function - visited[] contains the
// visited elements in order and find() and each() preserve order
j = 0;
context.$content.find( '.wikiEditor-highlight' ).each( function () {
if ( visited[j] == this ) {
// This marker is legit, leave it in
j++;
return true;
}
// Remove this marker
var marker = $(this).data( 'marker' );
if ( marker && typeof marker.skipDivision !== 'undefined' && ( division === marker.skipDivision ) ) {
// Don't remove these either
return true;
}
if ( marker && typeof marker.beforeUnwrap === 'function' )
marker.beforeUnwrap( this );
if ( ( marker && marker.anchor === 'tag' ) || $(this).is( 'p' ) ) {
// Remove all classes
$(this).removeAttr( 'class' );
} else {
// Update the marker object
$( anchor ).data( 'marker', markers[i] );
if ( typeof markers[i].onSkip == 'function' ) {
markers[i].onSkip( anchor );
}
// Assume anchor == 'wrap'
$(this).replaceWith( this.childNodes );
}
visited[v++] = anchor;
}
context.fn.purgeOffsets();
});
}
// Remove markers that were previously inserted but weren't passed to this function - visited[] contains the
// visited elements in order and find() and each() preserve order
var j = 0;
context.$content.find( '.wikiEditor-highlight' ).each( function() {
if ( visited[j] == this ) {
// This marker is legit, leave it in
j++;
return true;
}
// Remove this marker
var marker = $(this).data( 'marker' );
if ( marker && typeof marker.skipDivision != 'undefined' && ( division == marker.skipDivision ) ) {
// Don't remove these either
return true;
}
if ( marker && typeof marker.beforeUnwrap == 'function' )
marker.beforeUnwrap( this );
if ( ( marker && marker.anchor == 'tag' ) || $(this).is( 'p' ) ) {
// Remove all classes
$(this).removeAttr( 'class' );
} else {
// Assume anchor == 'wrap'
$(this).replaceWith( this.childNodes );
}
context.fn.purgeOffsets();
});
}
}
};
}; })( jQuery );
}( jQuery ) );

View file

@ -2,13 +2,13 @@
* This plugin provides a way to build a wiki-text editing user interface around a textarea.
*
* @example To intialize without any modules:
* $( 'div#edittoolbar' ).wikiEditor();
* $( 'div#edittoolbar' ).wikiEditor();
*
* @example To initialize with one or more modules, or to add modules after it's already been initialized:
* $( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'toolbar', { ... config ... } );
* $( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'toolbar', { ... config ... } );
*
*/
( function( $ ) {
( function ( $ ) {
/**
* Global static object for wikiEditor that provides generally useful functionality to all modules and contexts.
@ -19,63 +19,68 @@ $.wikiEditor = {
* module name. The existance of a module in this object only indicates the module is available. To check if a
* module is in use by a specific context check the context.modules object.
*/
'modules': {},
modules: {},
/**
* A context can be extended, such as adding iframe support, on a per-wikiEditor instance basis.
*/
'extensions': {},
extensions: {},
/**
* In some cases like with the iframe's HTML file, it's convienent to have a lookup table of all instances of the
* WikiEditor. Each context contains an instance field which contains a key that corrosponds to a reference to the
* textarea which the WikiEditor was build around. This way, by passing a simple integer you can provide a way back
* to a specific context.
*/
'instances': [],
instances: [],
/**
* For each browser name, an array of conditions that must be met are supplied in [operaton, value]-form where
* operation is a string containing a JavaScript compatible binary operator and value is either a number to be
* compared with $.browser.versionNumber or a string to be compared with $.browser.version. If a browser is not
* specifically mentioned, we just assume things will work.
*/
'browsers': {
browsers: {
// Left-to-right languages
'ltr': {
ltr: {
// The toolbar layout is broken in IE6
'msie': [['>=', 7]],
msie: [['>=', 7]],
// Layout issues in FF < 2
'firefox': [['>=', 2]],
firefox: [['>=', 2]],
// Text selection bugs galore - this may be a different situation with the new iframe-based solution
'opera': [['>=', 9.6]],
opera: [['>=', 9.6]],
// jQuery minimums
'safari': [['>=', 3]],
'chrome': [['>=', 3]],
'netscape': [['>=', 9]],
'blackberry': false,
'ipod': false,
'iphone': false
safari: [['>=', 3]],
chrome: [['>=', 3]],
netscape: [['>=', 9]],
blackberry: false,
ipod: false,
iphone: false
},
// Right-to-left languages
'rtl': {
rtl: {
// The toolbar layout is broken in IE 7 in RTL mode, and IE6 in any mode
'msie': [['>=', 8]],
msie: [['>=', 8]],
// Layout issues in FF < 2
'firefox': [['>=', 2]],
firefox: [['>=', 2]],
// Text selection bugs galore - this may be a different situation with the new iframe-based solution
'opera': [['>=', 9.6]],
opera: [['>=', 9.6]],
// jQuery minimums
'safari': [['>=', 3]],
'chrome': [['>=', 3]],
'netscape': [['>=', 9]],
'blackberry': false,
'ipod': false,
'iphone': false
safari: [['>=', 3]],
chrome: [['>=', 3]],
netscape: [['>=', 9]],
blackberry: false,
ipod: false,
iphone: false
}
},
/**
* Path to images - this is a bit messy, and it would need to change if this code (and images) gets moved into the
* core - or anywhere for that matter...
*/
'imgPath' : mw.config.get( 'wgExtensionAssetsPath' ) + '/WikiEditor/modules/images/',
imgPath : mw.config.get( 'wgExtensionAssetsPath' ) + '/WikiEditor/modules/images/',
/**
* Checks the current browser against the browsers object to determine if the browser has been black-listed or not.
* Because these rules are often very complex, the object contains configurable operators and can check against
@ -88,7 +93,7 @@ $.wikiEditor = {
* "open-web" way to go.
* @param module Module object, defaults to $.wikiEditor
*/
'isSupported': function( module ) {
isSupported: function ( module ) {
// Fallback to the wikiEditor browser map if no special map is provided in the module
var mod = module && 'browsers' in module ? module : $.wikiEditor;
// Check for and make use of cached value and early opportunities to bail
@ -99,21 +104,23 @@ $.wikiEditor = {
// Run a browser support test and then cache and return the result
return mod.supported = $.client.test( mod.browsers );
},
/**
* Checks if a module has a specific requirement
* @param module Module object
* @param requirement String identifying requirement
*/
'isRequired': function( module, requirement ) {
if ( typeof module['req'] !== 'undefined' ) {
for ( var req in module['req'] ) {
if ( module['req'][req] == requirement ) {
isRequired: function ( module, requirement ) {
if ( typeof module.req !== 'undefined' ) {
for ( var req in module.req ) {
if ( module.req[req] == requirement ) {
return true;
}
}
}
return false;
},
/**
* Provides a way to extract messages from objects. Wraps the mediaWiki.msg() function, which
* may eventually become a wrapper for some kind of core MW functionality.
@ -124,7 +131,7 @@ $.wikiEditor = {
* would return the raw text 'that', while passing property as 'foo' would return the internationalized message
* with the key 'bar'.
*/
'autoMsg': function( object, property ) {
autoMsg: function ( object, property ) {
// Accept array of possible properties, of which the first one found will be used
if ( typeof property == 'object' ) {
for ( var i in property ) {
@ -147,6 +154,7 @@ $.wikiEditor = {
return '';
}
},
/**
* Provides a way to extract a property of an object in a certain language, falling back on the property keyed as
* 'default' or 'default-rtl'. If such key doesn't exist, the object itself is considered the actual value, which
@ -156,10 +164,11 @@ $.wikiEditor = {
* @param object Object to extract property from
* @param lang Language code, defaults to wgUserLanguage
*/
'autoLang': function( object, lang ) {
autoLang: function ( object, lang ) {
var defaultKey = $( 'body' ).hasClass( 'rtl' ) ? 'default-rtl' : 'default';
return object[lang || mw.config.get( 'wgUserLanguage' )] || object[defaultKey] || object['default'] || object;
},
/**
* Provides a way to extract the path of an icon in a certain language, automatically appending a version number for
* caching purposes and prepending an image path when icon paths are relative.
@ -168,7 +177,7 @@ $.wikiEditor = {
* @param path Default icon path, defaults to $.wikiEditor.imgPath
* @param lang Language code, defaults to wgUserLanguage
*/
'autoIcon': function( icon, path, lang ) {
autoIcon: function ( icon, path, lang ) {
var src = $.wikiEditor.autoLang( icon, lang );
path = path || $.wikiEditor.imgPath;
// Prepend path if src is not absolute
@ -177,6 +186,7 @@ $.wikiEditor = {
}
return src + '?' + mw.loader.version( 'jquery.wikiEditor' );
},
/**
* Get the sprite offset for a language if available, icon for a language if available, or the default offset or icon,
* in that order of preference.
@ -185,7 +195,7 @@ $.wikiEditor = {
* @param path Icon path, see autoIcon()
* @param lang Language code, defaults to wgUserLanguage
*/
'autoIconOrOffset': function( icon, offset, path, lang ) {
autoIconOrOffset: function ( icon, offset, path, lang ) {
lang = lang || mw.config.get( 'wgUserLanguage' );
if ( typeof offset == 'object' && lang in offset ) {
return offset[lang];
@ -200,7 +210,7 @@ $.wikiEditor = {
/**
* jQuery plugin that provides a way to initialize a wikiEditor instance on a textarea.
*/
$.fn.wikiEditor = function() {
$.fn.wikiEditor = function () {
// Skip any further work when running in browsers that are unsupported
if ( !$.wikiEditor.isSupported() ) {
@ -263,21 +273,22 @@ if ( !context || typeof context == 'undefined' ) {
* @param data Either a string of the name of a module to add without any additional configuration parameters,
* or an object with members keyed with module names and valued with configuration objects.
*/
'addModule': function( context, data ) {
var modules = {};
'addModule': function ( context, data ) {
var module, call,
modules = {};
if ( typeof data == 'string' ) {
modules[data] = {};
} else if ( typeof data == 'object' ) {
modules = data;
}
for ( var module in modules ) {
for ( module in modules ) {
// Check for the existance of an available / supported module with a matching name and a create function
if ( typeof module == 'string' && typeof $.wikiEditor.modules[module] !== 'undefined' &&
$.wikiEditor.isSupported( $.wikiEditor.modules[module] ) )
{
// Extend the context's core API with this module's own API calls
if ( 'api' in $.wikiEditor.modules[module] ) {
for ( var call in $.wikiEditor.modules[module].api ) {
for ( call in $.wikiEditor.modules[module].api ) {
// Modules may not overwrite existing API functions - first come, first serve
if ( !( call in context.api ) ) {
context.api[call] = $.wikiEditor.modules[module].api[call];
@ -313,7 +324,7 @@ if ( !context || typeof context == 'undefined' ) {
/**
* Executes core event filters as well as event handlers provided by modules.
*/
'trigger': function( name, event ) {
trigger: function ( name, event ) {
// Event is an optional argument, but from here on out, at least the type field should be dependable
if ( typeof event == 'undefined' ) {
event = { 'type': 'custom' };
@ -339,9 +350,9 @@ if ( !context || typeof context == 'undefined' ) {
name in $.wikiEditor.modules[module].evt
) {
var ret = $.wikiEditor.modules[module].evt[name]( context, event );
if (ret != null) {
if (ret !== null) {
//if 1 returns false, the end result is false
if( returnFromModules == null ) {
if( returnFromModules === null ) {
returnFromModules = ret;
} else {
returnFromModules = returnFromModules && ret;
@ -349,45 +360,47 @@ if ( !context || typeof context == 'undefined' ) {
}
}
}
if ( returnFromModules != null ) {
if ( returnFromModules !== null ) {
return returnFromModules;
} else {
return true;
}
},
/**
* Adds a button to the UI
*/
'addButton': function( options ) {
addButton: function ( options ) {
// Ensure that buttons and tabs are visible
context.$controls.show();
context.$buttons.show();
return $( '<button />' )
return $( '<button>' )
.text( $.wikiEditor.autoMsg( options, 'caption' ) )
.click( options.action )
.appendTo( context.$buttons );
},
/**
* Adds a view to the UI, which is accessed using a set of tabs. Views are mutually exclusive and by default a
* wikitext view will be present. Only when more than one view exists will the tabs will be visible.
*/
'addView': function( options ) {
addView: function ( options ) {
// Adds a tab
function addTab( options ) {
// Ensure that buttons and tabs are visible
context.$controls.show();
context.$tabs.show();
// Return the newly appended tab
return $( '<div></div>' )
return $( '<div>' )
.attr( 'rel', 'wikiEditor-ui-view-' + options.name )
.addClass( context.view == options.name ? 'current' : null )
.append( $( '<a></a>' )
.append( $( '<a>' )
.attr( 'href', '#' )
.mousedown( function() {
.mousedown( function () {
// No dragging!
return false;
} )
.click( function( event ) {
.click( function ( event ) {
context.$ui.find( '.wikiEditor-ui-view' ).hide();
context.$ui.find( '.' + $(this).parent().attr( 'rel' ) ).show();
context.$tabs.find( 'div' ).removeClass( 'current' );
@ -404,21 +417,22 @@ if ( !context || typeof context == 'undefined' ) {
.appendTo( context.$tabs );
}
// Automatically add the previously not-needed wikitext tab
if ( !context.$tabs.children().size() ) {
if ( !context.$tabs.children().length ) {
addTab( { 'name': 'wikitext', 'titleMsg': 'wikieditor-wikitext-tab' } );
}
// Add the tab for the view we were actually asked to add
addTab( options );
// Return newly appended view
return $( '<div></div>' )
return $( '<div>' )
.addClass( 'wikiEditor-ui-view wikiEditor-ui-view-' + options.name )
.hide()
.appendTo( context.$ui );
},
/**
* Save scrollTop and cursor position for IE
*/
'saveCursorAndScrollTop': function() {
saveCursorAndScrollTop: function () {
if ( $.client.profile().name === 'msie' ) {
var IHateIE = {
'scrollTop' : context.$textarea.scrollTop(),
@ -427,10 +441,11 @@ if ( !context || typeof context == 'undefined' ) {
context.$textarea.data( 'IHateIE', IHateIE );
}
},
/**
* Restore scrollTo and cursor position for IE
*/
'restoreCursorAndScrollTop': function() {
restoreCursorAndScrollTop: function () {
if ( $.client.profile().name === 'msie' ) {
var IHateIE = context.$textarea.data( 'IHateIE' );
if ( IHateIE ) {
@ -440,19 +455,21 @@ if ( !context || typeof context == 'undefined' ) {
}
}
},
/**
* Save text selection for IE
*/
'saveSelection': function() {
saveSelection: function () {
if ( $.client.profile().name === 'msie' ) {
context.$textarea.focus();
context.savedSelection = document.selection.createRange();
}
},
/**
* Restore text selection for IE
*/
'restoreSelection': function() {
restoreSelection: function () {
if ( $.client.profile().name === 'msie' && context.savedSelection !== null ) {
context.$textarea.focus();
context.savedSelection.select();
@ -471,7 +488,7 @@ if ( !context || typeof context == 'undefined' ) {
// Assemble a temporary div to place over the wikiEditor while it's being constructed
/* Disabling our loading div for now
var $loader = $( '<div></div>' )
var $loader = $( '<div>' )
.addClass( 'wikiEditor-ui-loading' )
.append( $( '<span>' + mediaWiki.msg( 'wikieditor-loading' ) + '</span>' )
.css( 'marginTop', context.$textarea.height() / 2 ) );
@ -482,36 +499,38 @@ if ( !context || typeof context == 'undefined' ) {
.after( $loader )
.add( $loader )
*/
.wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui' ) )
.wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-view wikiEditor-ui-view-wikitext' ) )
.wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-left' ) )
.wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-bottom' ) )
.wrapAll( $( '<div></div>' ).addClass( 'wikiEditor-ui-text' ) );
.wrapAll( $( '<div>' ).addClass( 'wikiEditor-ui' ) )
.wrapAll( $( '<div>' ).addClass( 'wikiEditor-ui-view wikiEditor-ui-view-wikitext' ) )
.wrapAll( $( '<div>' ).addClass( 'wikiEditor-ui-left' ) )
.wrapAll( $( '<div>' ).addClass( 'wikiEditor-ui-bottom' ) )
.wrapAll( $( '<div>' ).addClass( 'wikiEditor-ui-text' ) );
// Get references to some of the newly created containers
context.$ui = context.$textarea.parent().parent().parent().parent().parent();
context.$wikitext = context.$textarea.parent().parent().parent().parent();
// Add in tab and button containers
context.$wikitext
.before(
$( '<div></div>' ).addClass( 'wikiEditor-ui-controls' )
.append( $( '<div></div>' ).addClass( 'wikiEditor-ui-tabs' ).hide() )
.append( $( '<div></div>' ).addClass( 'wikiEditor-ui-buttons' ) )
$( '<div>' ).addClass( 'wikiEditor-ui-controls' )
.append( $( '<div>' ).addClass( 'wikiEditor-ui-tabs' ).hide() )
.append( $( '<div>' ).addClass( 'wikiEditor-ui-buttons' ) )
)
.before( $( '<div style="clear:both;"></div>' ) );
.before( $( '<div style="clear: both;"></div>' ) );
// Get references to some of the newly created containers
context.$controls = context.$ui.find( '.wikiEditor-ui-buttons' ).hide();
context.$buttons = context.$ui.find( '.wikiEditor-ui-buttons' );
context.$tabs = context.$ui.find( '.wikiEditor-ui-tabs' );
// Clear all floating after the UI
context.$ui.after( $( '<div style="clear:both;"></div>' ) );
context.$ui.after( $( '<div style="clear: both;"></div>' ) );
// Attach a right container
context.$wikitext.append( $( '<div></div>' ).addClass( 'wikiEditor-ui-right' ) );
context.$wikitext.append( $( '<div>' ).addClass( 'wikiEditor-ui-right' ) );
// Attach a top container to the left pane
context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div></div>' ).addClass( 'wikiEditor-ui-top' ) );
context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div>' ).addClass( 'wikiEditor-ui-top' ) );
// Setup the intial view
context.view = 'wikitext';
// Trigger the "resize" event anytime the window is resized
$( window ).resize( function( event ) { context.fn.trigger( 'resize', event ); } );
$( window ).resize( function ( event ) {
context.fn.trigger( 'resize', event );
} );
}
/* API Execution */
@ -520,9 +539,9 @@ if ( !context || typeof context == 'undefined' ) {
var args = $.makeArray( arguments );
// Dynamically setup core extensions for modules that are required
if ( args[0] == 'addModule' && typeof args[1] != 'undefined' ) {
if ( args[0] == 'addModule' && typeof args[1] !== 'undefined' ) {
var modules = args[1];
if ( typeof modules != "object" ) {
if ( typeof modules !== "object" ) {
modules = {};
modules[args[1]] = '';
}
@ -536,7 +555,7 @@ if ( args[0] == 'addModule' && typeof args[1] != 'undefined' ) {
$.inArray( e, context.extensions ) === -1
) {
context.extensions[context.extensions.length] = e;
$.wikiEditor.extensions[e]( context );
$.wikiEditor.extensions[e]( context );
}
}
break;
@ -549,11 +568,13 @@ if ( args.length > 0 ) {
// Handle API calls
var call = args.shift();
if ( call in context.api ) {
context.api[call]( context, typeof args[0] == 'undefined' ? {} : args[0] );
context.api[call]( context, typeof args[0] === 'undefined' ? {} : args[0] );
}
}
// Store the context for next time, and support chaining
return $(this).data( 'wikiEditor-context', context );
}; } )( jQuery );
};
}( jQuery ) );

View file

@ -1,4 +1,4 @@
/*
/**
* CSS for WikiEditor Preview jQuery plugin
*/
@ -6,21 +6,26 @@
padding: 1em;
background-color: white;
}
.wikiEditor-preview-loading span {
color: #666666;
}
.wikiEditor-preview-spinner {
padding-right: 1em;
}
.wikiEditor-preview-contents {
padding: 1em;
background-color: white;
}
/* FIXME: This only works for the first wikiEditor on the page! */
#wikiEditor-0-preview-dialog .wikiEditor-ui-loading {
overflow: hidden;
border: none;
}
.ui-dialog .ui-dialog-buttonpane {
margin: 0 !important;
}
}

View file

@ -1,25 +1,28 @@
/* Preview module for wikiEditor */
( function( $ ) { $.wikiEditor.modules.preview = {
( function ( $, mw ) {
$.wikiEditor.modules.preview = {
/**
* Compatability map
*/
'browsers': {
browsers: {
// Left-to-right languages
'ltr': {
'msie': [['>=', 7]],
'firefox': [['>=', 3]],
'opera': [['>=', 9.6]],
'safari': [['>=', 4]]
ltr: {
msie: [['>=', 7]],
firefox: [['>=', 3]],
opera: [['>=', 9.6]],
safari: [['>=', 4]]
},
// Right-to-left languages
'rtl': {
'msie': [['>=', 8]],
'firefox': [['>=', 3]],
'opera': [['>=', 9.6]],
'safari': [['>=', 4]]
rtl: {
msie: [['>=', 8]],
firefox: [['>=', 3]],
opera: [['>=', 9.6]],
safari: [['>=', 4]]
}
},
/**
* Internally used functions
*/
@ -29,7 +32,7 @@ fn: {
* @param context Context object of editor to create module in
* @param config Configuration object to create module from
*/
create: function( context, config ) {
create: function ( context, config ) {
if ( 'initialized' in context.modules.preview ) {
return;
}
@ -41,11 +44,11 @@ fn: {
context.modules.preview.$preview = context.fn.addView( {
'name': 'preview',
'titleMsg': 'wikieditor-preview-tab',
'init': function( context ) {
'init': function ( context ) {
// Gets the latest copy of the wikitext
var wikitext = context.$textarea.textSelection( 'getContents' );
// Aborts when nothing has changed since the last preview
if ( context.modules.preview.previewText == wikitext ) {
if ( context.modules.preview.previewText === wikitext ) {
return;
}
context.modules.preview.$preview.find( '.wikiEditor-preview-contents' ).empty();
@ -53,14 +56,14 @@ fn: {
$.post(
mw.util.wikiScript( 'api' ),
{
'action': 'parse',
'title': mw.config.get( 'wgPageName' ),
'text': wikitext,
'prop': 'text',
'pst': '',
'format': 'json'
format: 'json',
action: 'parse',
title: mw.config.get( 'wgPageName' ),
text: wikitext,
prop: 'text',
pst: ''
},
function( data ) {
function ( data ) {
if (
typeof data.parse == 'undefined' ||
typeof data.parse.text == 'undefined' ||
@ -72,7 +75,7 @@ fn: {
context.modules.preview.$preview.find( '.wikiEditor-preview-loading' ).hide();
context.modules.preview.$preview.find( '.wikiEditor-preview-contents' )
.html( data.parse.text['*'] )
.find( 'a:not([href^=#])' ).click( function() { return false; } );
.find( 'a:not([href^=#])' ).click( false );
},
'json'
);
@ -82,7 +85,7 @@ fn: {
context.$changesTab = context.fn.addView( {
'name': 'changes',
'titleMsg': 'wikieditor-preview-changes-tab',
'init': function( context ) {
'init': function ( context ) {
// Gets the latest copy of the wikitext
var wikitext = context.$textarea.textSelection( 'getContents' );
// Aborts when nothing has changed since the last time
@ -94,28 +97,28 @@ fn: {
// Call the API. First PST the input, then diff it
var postdata = {
'action': 'parse',
'onlypst': '',
'text': wikitext,
'format': 'json'
format: 'json',
action: 'parse',
onlypst: '',
text: wikitext
};
$.post( mw.util.wikiScript( 'api' ), postdata, function( data ) {
$.post( mw.util.wikiScript( 'api' ), postdata, function ( data ) {
try {
var postdata2 = {
'action': 'query',
'indexpageids': '',
'prop': 'revisions',
'titles': mw.config.get( 'wgPageName' ),
'rvdifftotext': data.parse.text['*'],
'rvprop': '',
'format': 'json'
format: 'json',
action: 'query',
indexpageids: '',
prop: 'revisions',
titles: mw.config.get( 'wgPageName' ),
rvdifftotext: data.parse.text['*'],
rvprop: ''
};
var section = $( '[name=wpSection]' ).val();
if ( section != '' )
postdata2['rvsection'] = section;
var section = $( '[name="wpSection"]' ).val();
if ( section !== '' )
postdata2.rvsection = section;
$.post( mw.util.wikiScript( 'api' ), postdata2, function( data ) {
$.post( mw.util.wikiScript( 'api' ), postdata2, function ( data ) {
// Add diff CSS
mw.loader.load( 'mediawiki.action.history.diff' );
try {
@ -129,17 +132,17 @@ fn: {
} catch ( e ) { } // "blah is undefined" error, ignore
}, 'json'
);
} catch( e ) { } // "blah is undefined" error, ignore
} catch ( e ) { } // "blah is undefined" error, ignore
}, 'json' );
}
} );
var loadingMsg = mediaWiki.msg( 'wikieditor-preview-loading' );
var loadingMsg = mw.msg( 'wikieditor-preview-loading' );
context.modules.preview.$preview
.add( context.$changesTab )
.append( $( '<div />' )
.append( $( '<div>' )
.addClass( 'wikiEditor-preview-loading' )
.append( $( '<img />' )
.append( $( '<img>' )
.addClass( 'wikiEditor-preview-spinner' )
.attr( {
'src': $.wikiEditor.imgPath + 'dialogs/loading.gif',
@ -149,16 +152,18 @@ fn: {
} )
)
.append(
$( '<span></span>' ).text( loadingMsg )
$( '<span>' ).text( loadingMsg )
)
)
.append( $( '<div />' )
.append( $( '<div>' )
.addClass( 'wikiEditor-preview-contents' )
);
context.$changesTab.find( '.wikiEditor-preview-contents' )
.html( '<table class="diff"><col class="diff-marker" /><col class="diff-content" />' +
'<col class="diff-marker" /><col class="diff-content" /><tbody /></table>' );
.html( '<table class="diff"><col class="diff-marker"/><col class="diff-content"/>' +
'<col class="diff-marker"/><col class="diff-content"/><tbody/></table>' );
}
}
}; } )( jQuery );
};
}( jQuery, mediaWiki ) );

View file

@ -1,4 +1,4 @@
/*
/**
* CSS for WikiEditor Preview Dialog jQuery plugin
*/
@ -14,6 +14,7 @@
overflow: hidden;
border: none;
}
/* FIXME: This only works for the first wikiEditor on the page! */
#wikiEditor-0-preview-dialog .wikiEditor-ui-loading span {
display: block;
@ -24,12 +25,15 @@
text-indent: -9999px;
margin: 50px auto;
}
.ui-dialog .ui-dialog-buttonpane {
margin: 0 !important;
}
.wikiEditor-preview-dialog-contents {
font-size: 0.9em !important;
}
.wikiEditor-preview-dialog-contents #firstHeading {
font-size: 2.1em;
}
}

View file

@ -1,23 +1,25 @@
/* Publish module for wikiEditor */
( function( $ ) { $.wikiEditor.modules.publish = {
( function ( $ ) {
$.wikiEditor.modules.publish = {
/**
* Compatability map
*/
'browsers': {
browsers: {
// Left-to-right languages
'ltr': {
'msie': [['>=', 7]],
'firefox': [['>=', 3]],
'opera': [['>=', 9.6]],
'safari': [['>=', 4]]
ltr: {
msie: [['>=', 7]],
firefox: [['>=', 3]],
opera: [['>=', 9.6]],
safari: [['>=', 4]]
},
// Right-to-left languages
'rtl': {
'msie': [['>=', 8]],
'firefox': [['>=', 3]],
'opera': [['>=', 9.6]],
'safari': [['>=', 4]]
rtl: {
msie: [['>=', 8]],
firefox: [['>=', 3]],
opera: [['>=', 9.6]],
safari: [['>=', 4]]
}
},
/**
@ -29,7 +31,7 @@ fn: {
* @param context Context object of editor to create module in
* @param config Configuration object to create module from
*/
create: function( context, config ) {
create: function ( context, config ) {
// Build the dialog behind the Publish button
var dialogID = 'wikiEditor-' + context.instance + '-dialog';
$.wikiEditor.modules.dialogs.fn.create(
@ -61,8 +63,10 @@ fn: {
</div>\
</form>\
</div>',
init: function() {
$(this).find( '[rel]' ).each( function() {
init: function () {
var i;
$(this).find( '[rel]' ).each( function () {
$(this).text( mediaWiki.msg( $(this).attr( 'rel' ) ) );
});
@ -72,8 +76,8 @@ fn: {
// TODO: internationalize by splitting on other characters that end statements
var copyWarnStatements = copyWarnHTML.split( '. ' );
var newCopyWarnHTML = '<ul>';
for ( var i = 0; i < copyWarnStatements.length; i++ ) {
if ( copyWarnStatements[i] != '' ) {
for ( i = 0; i < copyWarnStatements.length; i++ ) {
if ( copyWarnStatements[i] !== '' ) {
var copyWarnStatement = $.trim( copyWarnStatements[i] ).replace( /\.*$/, '' );
newCopyWarnHTML += '<li>' + copyWarnStatement + '.</li>';
}
@ -85,42 +89,42 @@ fn: {
);
/* END OF REALLY DIRTY HACK */
if ( $( '#wpMinoredit' ).size() == 0 )
if ( $( '#wpMinoredit' ).length === 0 )
$( '#wikiEditor-' + context.instance + '-dialog-minor' ).hide();
else if ( $( '#wpMinoredit' ).is( ':checked' ) )
$( '#wikiEditor-' + context.instance + '-dialog-minor' )
.attr( 'checked', 'checked' );
if ( $( '#wpWatchthis' ).size() == 0 )
.prop( 'checked', true );
if ( $( '#wpWatchthis' ).length === 0 )
$( '#wikiEditor-' + context.instance + '-dialog-watch' ).hide();
else if ( $( '#wpWatchthis' ).is( ':checked' ) )
$( '#wikiEditor-' + context.instance + '-dialog-watch' )
.attr( 'checked', 'checked' );
.prop( 'checked', true );
$(this).find( 'form' ).submit( function( e ) {
$(this).find( 'form' ).submit( function ( e ) {
$(this).closest( '.ui-dialog' ).find( 'button:first' ).click();
e.preventDefault();
});
},
dialog: {
buttons: {
'wikieditor-publish-dialog-publish': function() {
'wikieditor-publish-dialog-publish': function () {
var minorChecked = $( '#wikiEditor-' + context.instance +
'-dialog-minor' ).is( ':checked' ) ?
'checked' : '';
var watchChecked = $( '#wikiEditor-' + context.instance +
'-dialog-watch' ).is( ':checked' ) ?
'checked' : '';
$( '#wpMinoredit' ).attr( 'checked', minorChecked );
$( '#wpWatchthis' ).attr( 'checked', watchChecked );
$( '#wpMinoredit' ).prop( 'checked', minorChecked );
$( '#wpWatchthis' ).prop( 'checked', watchChecked );
$( '#wpSummary' ).val( $( '#wikiEditor-' + context.instance +
'-dialog-summary' ).val() );
$( '#editform' ).submit();
},
'wikieditor-publish-dialog-goback': function() {
'wikieditor-publish-dialog-goback': function () {
$(this).dialog( 'close' );
}
},
open: function() {
open: function () {
$( '#wikiEditor-' + context.instance + '-dialog-summary' ).focus();
},
width: 500
@ -129,18 +133,22 @@ fn: {
}
}
);
context.fn.addButton( {
'captionMsg': 'wikieditor-publish-button-publish',
'action': function() {
'action': function () {
$( '#' + dialogID ).dialog( 'open' );
return false;
}
} );
context.fn.addButton( {
'captionMsg': 'wikieditor-publish-button-cancel',
'action': function() { }
'action': function () { }
} );
}
}
}; } )( jQuery );
};
}( jQuery ) );

View file

@ -9,9 +9,11 @@
overflow: auto;
overflow-x: hidden;
}
.wikiEditor-ui-toc {
border-left: solid silver 1px;
}
.wikiEditor-ui-toc ul {
padding: 0;
margin: 0;
@ -22,6 +24,7 @@
list-style-type: none;
width: 100%;
}
.tab-toc {
/* Should match the toolbar */
/* @embed */
@ -36,13 +39,16 @@
white-space: nowrap;
overflow: hidden;
}
.tab-toc a {
outline: none;
}
.wikiEditor-ui-toc li {
padding: 0;
margin: 0;
}
.wikiEditor-ui-toc ul ul {
padding: 0;
margin: 0;
@ -51,35 +57,44 @@
list-style: none;
background-image: none;
}
.wikiEditor-ui-toc ul li div {
display: block;
font-size: 0.9em;
cursor: pointer;
color: #0645ad;
}
.wikiEditor-ui-toc ul li div {
padding: 0.125em;
padding-left: 1em;
}
.wikiEditor-ui-toc ul ul li div {
padding-left: 2em;
}
.wikiEditor-ui-toc ul ul ul li div {
padding-left: 3em;
}
.wikiEditor-ui-toc ul ul ul ul li div {
padding-left: 4em;
}
.wikiEditor-ui-toc ul ul ul ul ul li div {
padding-left: 5em;
}
.wikiEditor-ui-toc ul ul ul ul ul ul li div {
padding-left: 6em;
}
.wikiEditor-ui-toc ul li div.current {
background-color: #FAFAFA;
color: #333333;
}
.wikiEditor-ui-toc ul li div.section-0 {
font-size: 1em;
padding-top: 0.5em;
@ -91,13 +106,14 @@
overflow-y: hidden;
position: relative;
}
.wikiEditor-ui-toc ul {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
margin-bottom: 0 !important;
}
.wikiEditor-ui-toc ul ul {
float: none;
height: auto;
@ -109,15 +125,18 @@
top: 0;
left: 0;
}
.wikiEditor-ui-toc-collapse-open {
/* @embed */
background: #f3f3f3 url(images/toc/close.png) 4px 50% no-repeat;
border-left: 1px solid #DDDDDD;
}
.wikiEditor-ui-toc-collapse-closed {
/* @embed */
background: #f3f3f3 url(images/toc/open.png) 4px 50% no-repeat;
}
/* Resizing Changes */
.wikiEditor-ui-toc-resize-vertical,
.ui-resizable-w {
@ -128,13 +147,16 @@
height: 100%;
cursor: ew-resize;
}
.wikiEditor-ui .wikiEditor-ui-right {
overflow: visible;
}
.wikiEditor-ui-right .ui-resizable-w {
left: 0px !important;
z-index: 0;
}
.wikiEditor-ui-right .wikiEditor-ui-toc-resize-grip {
width: 5px;
height: 12px;
@ -147,11 +169,13 @@
background: url(images/toc/grip.png) 50% 50% no-repeat;
z-index: 0;
}
.wikiEditor-ui-toolbar .tab-toc {
float: right;
margin: 3px 16px 3px 3px;
line-height: 26px;
}
.wikiEditor-ui-toc-expandControl {
position: absolute;
z-index: 2;
@ -164,14 +188,17 @@
white-space: nowrap;
overflow: hidden;
}
.wikiEditor-ui-text textarea {
resize: none;
}
.wikiEditor-ui-text textarea:focus {
outline: none;
}
/* Self Clearing for the wikiText view */
.wikiEditor-ui-view-wikiText {
overflow: auto;
width: 100%;
}
}

View file

@ -182,7 +182,7 @@ fn: {
$.wikiEditor.modules.toc.cfg.rtl = $( 'body' ).is( '.rtl' );
$.wikiEditor.modules.toc.cfg.flexProperty = $.wikiEditor.modules.toc.cfg.rtl ? 'marginLeft' : 'marginRight';
var height = context.$ui.find( '.wikiEditor-ui-left' ).height();
context.modules.toc.$toc = $( '<div />' )
context.modules.toc.$toc = $( '<div>' )
.addClass( 'wikiEditor-ui-toc' )
.data( 'context', context )
.data( 'positionMode', 'regular' )
@ -195,7 +195,7 @@ fn: {
$.wikiEditor.modules.toc.fn.redraw( context, $.wikiEditor.modules.toc.cfg.defaultWidth );
},
redraw: function( context, fixedWidth ) {
var fixedWidth = parseFloat( fixedWidth );
fixedWidth = parseFloat( fixedWidth );
if( context.modules.toc.$toc.data( 'positionMode' ) == 'regular' ) {
context.$ui.find( '.wikiEditor-ui-right' )
.css( 'width', fixedWidth + 'px' );
@ -214,8 +214,9 @@ fn: {
switchLayout: function( context ) {
var width,
height = context.$ui.find( '.wikiEditor-ui-right' ).height();
if( context.modules.toc.$toc.data( 'positionMode' ) == 'regular'
&& !context.modules.toc.$toc.data( 'collapsed' ) ) {
if ( context.modules.toc.$toc.data( 'positionMode' ) == 'regular'
&& !context.modules.toc.$toc.data( 'collapsed' )
) {
// store position mode
context.modules.toc.$toc.data( 'positionMode', 'goofy' );
// store the width of the TOC, to ensure we dont allow it to be larger than this when switching back
@ -423,8 +424,12 @@ fn: {
* @param {Object} outline Array of objects with level fields
*/
function buildStructure( outline, offset, level ) {
if ( offset == undefined ) offset = 0;
if ( level == undefined ) level = 1;
if ( offset === undefined ) {
offset = 0;
}
if ( level === undefined ) {
level = 1;
}
var sections = [];
for ( var i = offset; i < outline.length; i++ ) {
if ( outline[i].nLevel == level ) {
@ -445,9 +450,9 @@ fn: {
* @param {Object} structure Structured outline
*/
function buildList( structure ) {
var list = $( '<ul />' );
var list = $( '<ul>' );
for ( var i = 0; i < structure.length; i++ ) {
var div = $( '<div />' )
var div = $( '<div>' )
.addClass( 'section-' + structure[i].index )
.data( 'index', structure[i].index )
.mousedown( function() {
@ -457,7 +462,7 @@ fn: {
.click( function( event ) {
var wrapper = context.$content.find(
'.wikiEditor-toc-section-' + $( this ).data( 'index' ) );
if ( wrapper.size() == 0 )
if ( wrapper.length === 0 )
wrapper = context.$content;
context.fn.scrollToTop( wrapper, true );
context.$textarea.textSelection( 'setSelection', {
@ -471,16 +476,16 @@ fn: {
//$.wikiEditor.modules.toc.fn.unhighlight( context );
$( this ).addClass( 'current' );
//$( this ).removeClass( 'current' );
setTimeout( function() { $.wikiEditor.modules.toc.fn.unhighlight( context ) }, 1000 );
setTimeout( function() { $.wikiEditor.modules.toc.fn.unhighlight( context ); }, 1000 );
if ( typeof $.trackAction != 'undefined' )
$.trackAction( 'ntoc.heading' );
event.preventDefault();
} )
.text( structure[i].text );
if ( structure[i].text == '' )
if ( structure[i].text === '' )
div.html( '&nbsp;' );
var item = $( '<li />' ).append( div );
var item = $( '<li>' ).append( div );
if ( structure[i].sections !== undefined ) {
item.append( buildList( structure[i].sections ) );
}
@ -493,11 +498,11 @@ fn: {
*
*/
function buildCollapseControls( ) {
var $collapseControl = $( '<div />' ), $expandControl = $( '<div />' );
var $collapseControl = $( '<div>' ), $expandControl = $( '<div>' );
$collapseControl
.addClass( 'tab' )
.addClass( 'tab-toc' )
.append( '<a href="#" />' )
.append( '<a href="#"></a>' )
.mousedown( function( e ) {
// No dragging!
e.preventDefault();
@ -513,7 +518,7 @@ fn: {
.text( mediaWiki.msg( 'wikieditor-toc-hide' ) );
$expandControl
.addClass( 'wikiEditor-ui-toc-expandControl' )
.append( '<a href="#" />' )
.append( '<a href="#"></a>' )
.mousedown( function( e ) {
// No dragging!
e.preventDefault();
@ -546,7 +551,7 @@ fn: {
start: function( e, ui ) {
var $this = $( this );
// Toss a transparent cover over our iframe
$( '<div />' )
$( '<div>' )
.addClass( 'wikiEditor-ui-resize-mask' )
.css( {
'position': 'absolute',
@ -636,8 +641,12 @@ fn: {
// Recursively build the structure and add special item for
// section 0, if needed
var structure = buildStructure( outline );
if ( $( 'input[name=wpSection]' ).val() == '' ) {
structure.unshift( { 'text': mw.config.get( 'wgPageName' ).replace( /_/g, ' ' ), 'level': 1, 'index': 0 } );
if ( $( 'input[name="wpSection"]' ).val() === '' ) {
structure.unshift( {
'text': mw.config.get( 'wgPageName' ).replace( /_/g, ' ' ),
'level': 1,
'index': 0
} );
}
context.modules.toc.$toc.html( buildList( structure ) );
@ -645,18 +654,20 @@ fn: {
buildResizeControls();
buildCollapseControls();
}
context.modules.toc.$toc.find( 'div' ).autoEllipsis(
{ 'position': 'right', 'tooltip': true, 'restoreText': true }
);
context.modules.toc.$toc.find( 'div' ).autoEllipsis( {
'position': 'right',
'tooltip': true,
'restoreText': true
} );
}
},
improveUI: function() {
/*
* Extending resizable to allow west resizing without altering the left position attribute
*/
$.ui.plugin.add( "resizable", "preventPositionLeftChange", {
$.ui.plugin.add( 'resizable', 'preventPositionLeftChange', {
resize: function( event, ui ) {
$( this ).data( "resizable" ).position.left = 0;
$( this ).data( 'resizable' ).position.left = 0;
}
} );
}

View file

@ -1,4 +1,4 @@
/*
/**
* CSS for WikiEditor Toolbar jQuery plugin
*/