r59987 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r59986‎ | r59987 | r59988 >
Date:23:50, 11 December 2009
Author:tparscal
Status:deferred
Tags:
Comment:
Fixed allot of wonkyness in jquery.wikiEditor.js, but there's still some cleanup to do. One important aspect of these changes was repairing the strangely complex calling of API calls and initialization. Also, there's now lots of accurate documentation.
Modified paths:
  • /trunk/extensions/UsabilityInitiative/js/plugins.combined.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.toolbar.js (modified) (history)

Diff [purge]

Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html
@@ -4,6 +4,7 @@
55 <title>WikiEditor</title>
66 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
77 <script type="text/javascript">
 8+ /*
89 parent.inherit( window )( function() {
910 function get( name ) {
1011 // Extracts the value of a given URL parameter from the current window location
@@ -16,6 +17,7 @@
1718 context.fn.trigger( "change", event )
1819 } );
1920 } );
 21+ */
2022 </script>
2123 </head>
2224 <body style="margin:0;padding:0;width:100%;height:100%;white-space:pre-wrap;font-family:monospace;font-size:9.5pt;"></body>
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js
@@ -1,71 +1,93 @@
22 /**
3 - * This plugin provides a way to build a user interface around a textarea. You
4 - * can build the UI from a confguration..
5 - * $j( 'div#edittoolbar' ).wikiEditor(
6 - * { 'modules': { 'toolbar': { ... config ... } } }
7 - * );
8 - * ...and add modules after it's already been initialized...
9 - * $j( 'textarea#wpTextbox1' ).wikiEditor(
10 - * 'addModule', 'toc', { ... config ... }
11 - * );
12 - * ...using the API, which is still be finished.
 3+ * This plugin provides a way to build a wiki-text editing user interface around a textarea.
 4+ *
 5+ * @example To intialize without any modules:
 6+ * $j( 'div#edittoolbar' ).wikiEditor();
 7+ *
 8+ * @example To initialize with one or more modules, or to add modules after it's already been initialized:
 9+ * $j( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'toolbar', { ... config ... } );
 10+ *
1311 */
1412 ( function( $ ) {
1513
 14+/**
 15+ * Global static object for wikiEditor that provides generally useful functionality to all modules and contexts.
 16+ */
1617 $.wikiEditor = {
 18+ /**
 19+ * For each module that is loaded, static code shared by all instances is loaded into this object organized by
 20+ * module name. The existance of a module in this object only indicates the module is available. To check if a
 21+ * module is in use by a specific context check the context.modules object.
 22+ */
1723 'modules': {},
 24+ /**
 25+ * In some cases like with the iframe's HTML file, it's convienent to have a lookup table of all instances of the
 26+ * WikiEditor. Each context contains an instance field which contains a key that corrosponds to a reference to the
 27+ * textarea which the WikiEditor was build around. This way, by passing a simple integer you can provide a way back
 28+ * to a specific context.
 29+ */
1830 'instances': [],
1931 /**
20 - * For each browser name, an array of conditions that must be met are supplied in [operaton, value] form where
 32+ * For each browser name, an array of conditions that must be met are supplied in [operaton, value]-form where
2133 * operation is a string containing a JavaScript compatible binary operator and value is either a number to be
22 - * compared with $.browser.versionNumber or a string to be compared with $.browser.version
 34+ * compared with $.browser.versionNumber or a string to be compared with $.browser.version. If a browser is not
 35+ * specifically mentioned, we just assume things will work.
2336 */
2437 'browsers': {
 38+ // Left-to-right languages
2539 'ltr': {
 40+ // The toolbar layout is IE6
2641 'msie': [['>=', 7]],
 42+ // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
2743 'firefox': [
28 - ['>=', 2],
29 - ['!=', '2.0'],
30 - ['!=', '2.0.0.1'],
31 - ['!=', '2.0.0.2'],
32 - ['!=', '2.0.0.3'],
33 - ['!=', '2.0.0.4']
 44+ ['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
3445 ],
 46+ // Text selection bugs galore - this may be a different situation with the new iframe-based solution
3547 'opera': [['>=', 9.6]],
 48+ // This should be checked again, but the usage of Safari 3.0 and lower is so small it's not a priority
3649 'safari': [['>=', 3.1]]
3750 },
 51+ // Right-to-left languages
3852 'rtl': {
 53+ // The toolbar layout is broken in IE 7 in RTL mode, and IE6 in any mode
3954 'msie': [['>=', 8]],
 55+ // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
4056 'firefox': [
41 - ['>=', 2],
42 - ['!=', '2.0'],
43 - ['!=', '2.0.0.1'],
44 - ['!=', '2.0.0.2'],
45 - ['!=', '2.0.0.3'],
46 - ['!=', '2.0.0.4']
 57+ ['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
4758 ],
 59+ // Text selection bugs galore - this may be a different situation with the new iframe-based solution
4860 'opera': [['>=', 9.6]],
 61+ // This should be checked again, but the usage of Safari 3.0 and lower is so small it's not a priority
4962 'safari': [['>=', 3.1]]
5063 }
5164 },
5265 /**
53 - * Path to images - this is a bit messy, and it would need to change if
54 - * this code (and images) gets moved into the core - or anywhere for
55 - * that matter...
 66+ * Path to images - this is a bit messy, and it would need to change if this code (and images) gets moved into the
 67+ * core - or anywhere for that matter...
5668 */
5769 'imgPath' : wgScriptPath + '/extensions/UsabilityInitiative/images/wikiEditor/',
58 - 'isSupportKnown': function() {
59 - return $.browser.name in $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'];
60 - },
 70+ /**
 71+ * Checks the current browser against the browsers object to determine if the browser has been black-listed or not.
 72+ * Because these rules are often very complex, the object contains configurable operators and can check against
 73+ * either the browser version number or string. This process also involves checking if the current browser is amung
 74+ * those which we have configured as compatible or not. If the browser was not configured as comptible we just go on
 75+ * assuming things will work - the argument here is to prevent the need to update the code when a new browser comes
 76+ * to market. The assumption here is that any new browser will be built on an existing engine or be otherwise so
 77+ * similar to another existing browser that things actually do work as expected. The merrits of this argument, which
 78+ * is essentially to blacklist rather than whitelist are debateable, but at this point we've decided it's the more
 79+ * "open-web" way to go.
 80+ */
6181 'isSupported': function() {
62 - // Cache the return value
63 - if ( $.wikiEditor.supported != undefined )
 82+ // Check for and make use of a cached return value
 83+ if ( $.wikiEditor.supported != undefined ) {
6484 return $.wikiEditor.supported;
65 -
66 - if ( !$.wikiEditor.isSupportKnown ) {
67 - // Assume good faith :)
 85+ }
 86+ // Check if we have any compatiblity information on-hand for the current browser
 87+ if ( !( $.browser.name in $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'] ) ) {
 88+ // Assume good faith :)
6889 return $.wikiEditor.supported = true;
6990 }
 91+ // Check over each browser condition to determine if we are running in a compatible client
7092 var browser = $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'][$.browser.name];
7193 for ( condition in browser ) {
7294 var op = browser[condition][0];
@@ -80,9 +102,20 @@
81103 }
82104 }
83105 }
 106+ // Return and also cache the return value - this will be checked somewhat often
84107 return $.wikiEditor.supported = true;
85108 },
86 - // Wraps gM from js2, but allows raw text to supercede
 109+ /**
 110+ * Provides a way to extract messages from objects. Wraps the gM function from js2stopgap.js, which will be changing
 111+ * in the very near future, so let's keep and eye on this. It's also possible that this function will just be moved
 112+ * to the global mw object all together.
 113+ *
 114+ * @param object Object to extract messages from
 115+ * @param property String of name of property which contains the message. This should be the base name of the
 116+ * property, which means that in the case of the object { this: 'that', fooMsg: 'bar' }, passing property as 'this'
 117+ * would return the raw text 'that', while passing property as 'foo' would return the internationalized message
 118+ * with the key 'bar'.
 119+ */
87120 'autoMsg': function( object, property ) {
88121 // Accept array of possible properties, of which the first one found will be used
89122 if ( typeof property == 'object' ) {
@@ -101,122 +134,113 @@
102135 return '';
103136 }
104137 },
105 - // Get an icon in a certain language
106 - // @param icon Icon object from e.g. toolbar config
107 - // @param path Default icon path, defaults to $.wikiEditor.imgPath
108 - // @param lang Language code, defaults to wgUserLanguage
109 - 'getIcon': function( icon, path, lang ) {
110 - lang = lang || wgUserLanguage;
 138+ /**
 139+ * Provieds a way to extract a property of an object in a certain language, falling back on the property keyed as
 140+ * 'default'. If such key doesn't exist, the object itself is considered the actual value, which should ideally
 141+ * be the case so that you may use a string or object of any number of strings keyed by language with a default.
 142+ *
 143+ * @param object Object to extract property from
 144+ * @param lang Language code, defaults to wgUserLanguage
 145+ */
 146+ 'autoLang': function( object, lang ) {
 147+ return object[lang || wgUserLanguage] || object['default'] || object;
 148+ },
 149+ /**
 150+ * Provieds a way to extract the path of an icon in a certain language, automatically appending a version number for
 151+ * caching purposes and prepending an image path when icon paths are relative.
 152+ *
 153+ * @param icon Icon object from e.g. toolbar config
 154+ * @param path Default icon path, defaults to $.wikiEditor.imgPath
 155+ * @param lang Language code, defaults to wgUserLanguage
 156+ */
 157+ 'autoIcon': function( icon, path, lang ) {
 158+ var src = $.wikiEditor.autoLang( icon, lang );
111159 path = path || $.wikiEditor.imgPath;
112 - var src = icon[lang] || icon['default'] || icon;
113160 // Prepend path if src is not absolute
114 - if ( src.substr( 0, 7 ) != 'http://' && src.substr( 0, 8 ) != 'https://' &&
115 - src[0] != '/' )
 161+ if ( src.substr( 0, 7 ) != 'http://' && src.substr( 0, 8 ) != 'https://' && src[0] != '/' ) {
116162 src = path + src;
 163+ }
117164 return src + '?' + wgWikiEditorIconVersion;
118 - },
119 - 'fixOperaBrokenness': function( s ) {
120 - /*
121 - // This function works around Opera's
122 - // broken newline handling in textareas.
123 - // .val() has \n while selection functions
124 - // treat newlines as \r\n
125 -
126 - if ( typeof $.isOperaBroken == 'undefined' && $.wikiEditor.instances.length > 0 ) {
127 - // Create a textarea inside a div
128 - // with zero area, to hide it properly
129 - var div = $( '<div />' )
130 - .height( 0 )
131 - .width( 0 )
132 - .insertBefore( $.wikiEditor.instances[0] );
133 - var textarea = $( '<textarea></textarea>' )
134 - .height( 0 )
135 - .appendTo( div )
136 - .val( "foo\r\nbar" );
137 - // Try to search&replace bar --> BAR
138 - var index = textarea.val().indexOf( 'bar' );
139 - textarea.select();
140 - textarea.setSelection( index, index + 3 );
141 - textarea.encapsulateSelection( '', 'BAR', '', false, true );
142 - if ( textarea.val().substr( -4 ) != 'BARr' )
143 - $.isOperaBroken = false;
144 - else
145 - $.isOperaBroken = true;
146 - div.remove();
147 - }
148 - if ( $.isOperaBroken )
149 - s = s.replace( /\n/g, "\r\n" );
150 - */
151 - return s;
152165 }
153166 };
154167
 168+/**
 169+ * jQuery plugin that provides a way to initialize a wikiEditor instance on a textarea.
 170+ */
155171 $.fn.wikiEditor = function() {
156172
157 -// Skip any further work on browsers that are unsupported
158 -if ( $j.wikiEditor.isSupportKnown() && !$j.wikiEditor.isSupported() ) {
 173+// Skip any further work when running in browsers that are unsupported
 174+if ( !$j.wikiEditor.isSupported() ) {
159175 return $(this);
160176 }
161177
162178 /* Initialization */
163179
164 -// The wikiEditor context is stored in the element, so when this function
165 -// gets called again we can pick up where we left off
 180+// The wikiEditor context is stored in the element's data, so when this function gets called again we can pick up right
 181+// where we left off
166182 var context = $(this).data( 'wikiEditor-context' );
167183
168 -// This only gets run on the first call
 184+// On first call, we need to set things up, but on all following calls we can skip right to the API handling
169185 if ( typeof context == 'undefined' ) {
170186
171 - var instance = $.wikiEditor.instances.length;
172 - context = { '$textarea': $(this), 'views': {}, 'modules': {}, 'data': {}, 'instance': instance };
173 - $.wikiEditor.instances[instance] = $(this);
 187+ // Star filling the context with useful data - any jQuery selections, as usual should be named with a preceding $
 188+ context = {
 189+ // Reference to the textarea element which the wikiEditor is being built around
 190+ '$textarea': $(this),
 191+ // Container for any number of mutually exclusive views that are accessible by tabs
 192+ 'views': {},
 193+ // Container for any number of module-specific data - only including data for modules in use on this context
 194+ 'modules': {},
 195+ // General place to shouve bits of data into
 196+ 'data': {},
 197+ // Unique numeric ID of this instance used both for looking up and differentiating instances of wikiEditor
 198+ 'instance': $.wikiEditor.instances.push( $(this) )
 199+ };
174200
175 - /* Externally Accessible API */
 201+ /*
 202+ * Externally Accessible API
 203+ *
 204+ * These are available using calls to $j(selection).wikiEditor( call, data ) where selection is a jQuery selection
 205+ * of the textarea that the wikiEditor instance was built around.
 206+ */
176207
177208 context.api = {
178209 /**
179 - * Accepts either a string of the name of a module to add without any
180 - * additional configuration parameters, or an object with members keyed with
181 - * module names and valued with configuration objects
 210+ * Activates a module on a specific context with optional configuration data.
 211+ *
 212+ * @param data Either a string of the name of a module to add without any additional configuration parameters,
 213+ * or an object with members keyed with module names and valued with configuration objects.
182214 */
183215 'addModule': function( context, data ) {
184 - // A safe way of calling an API function on a module
185 - function callModuleApi( module, call, data ) {
186 - if (
187 - module in $.wikiEditor.modules &&
188 - 'fn' in $.wikiEditor.modules[module] &&
189 - call in $.wikiEditor.modules[module].fn
190 - ) {
191 - // Add a place for the module to put it's own stuff
192 - context.modules[module] = {};
193 - // Tell the module to create itself
194 - $.wikiEditor.modules[module].fn[call]( context, data );
195 - }
196 - }
 216+ var modules = {};
197217 if ( typeof data == 'string' ) {
198 - callModuleApi( data, 'create', {} );
 218+ modules[data] = {};
199219 } else if ( typeof data == 'object' ) {
200 - for ( module in data ) {
201 - if ( typeof module == 'string' ) {
202 - callModuleApi( module, 'create', data[module] );
 220+ modules = data;
 221+ }
 222+ for ( module in modules ) {
 223+ // Check for the existance of an available module with a matching name and a create function
 224+ if ( typeof module == 'string' && module in $.wikiEditor.modules ) {
 225+ // Extend the context's core API with this module's own API calls
 226+ if ( 'api' in $.wikiEditor.modules[module] ) {
 227+ for ( call in $.wikiEditor.modules[module].api ) {
 228+ // Modules may not overwrite existing API functions - first come, first serve
 229+ if ( !( call in context.api ) ) {
 230+ context.api[call] = $.wikiEditor.modules[module].api[call];
 231+ }
 232+ }
203233 }
 234+ // Activate the module on this context
 235+ if ( 'fn' in $.wikiEditor.modules[module] && 'create' in $.wikiEditor.modules[module].fn ) {
 236+ // Add a place for the module to put it's own stuff
 237+ context.modules[module] = {};
 238+ // Tell the module to create itself on the context
 239+ $.wikiEditor.modules[module].fn.create( context, modules[module] );
 240+ }
204241 }
205242 }
206243 }
207244 };
208 - // Allow modules to extend the API
209 - if($.wikiEditor.modules){
210 - for ( module in $.wikiEditor.modules ) {
211 - if ( 'api' in $.wikiEditor.modules[module] ) {
212 - for ( call in $.wikiEditor.modules[module].api ) {
213 - // Modules may not overwrite existing API functions - first come,
214 - // first serve
215 - if ( !( call in context.api ) ) {
216 - context.api[call] = $.wikiEditor.modules[module].api[call];
217 - }
218 - }
219 - }
220 - }}
221245
222246 /*
223247 * Event Handlers
@@ -226,6 +250,11 @@
227251 */
228252
229253 context.evt = {
 254+ /**
 255+ * Filters change events, which occur when the user interacts with the contents of the iframe. The goal of this
 256+ * function is to both classify the scope of changes as 'division' or 'character' and to prevent further
 257+ * processing of events which did not actually change the content of the iframe.
 258+ */
230259 'change': function( event ) {
231260 // Event filtering
232261 switch ( event.type ) {
@@ -253,8 +282,10 @@
254283
255284 /* Internal Functions */
256285
257 - //$(this).data( 'wikiEditor-context', context );
258286 context.fn = {
 287+ /**
 288+ * Executes core event filters as well as event handlers provided by modules.
 289+ */
259290 'trigger': function( name, event ) {
260291 // Event is an optional argument, but from here on out, at least the type field should be dependable
261292 if ( typeof event == 'undefined' ) {
@@ -270,8 +301,8 @@
271302 return false;
272303 }
273304 }
 305+ // Pass the event around to all modules activated on this context
274306 for ( module in context.modules ) {
275 - // Pass the event around to all modules activated on this context
276307 if (
277308 module in $.wikiEditor.modules &&
278309 'evt' in $.wikiEditor.modules[module] &&
@@ -281,6 +312,9 @@
282313 }
283314 }
284315 },
 316+ /**
 317+ * Adds a button to the UI
 318+ */
285319 'addButton': function( options ) {
286320 // Ensure that buttons and tabs are visible
287321 context.$controls.show();
@@ -290,6 +324,10 @@
291325 .click( options.action )
292326 .appendTo( context.$buttons );
293327 },
 328+ /**
 329+ * Adds a view to the UI, which is accessed using a set of tabs. Views are mutually exclusive and by default a
 330+ * wikitext view will be present. Only when more than one view exists will the tabs will be visible.
 331+ */
294332 'addView': function( options ) {
295333 // Adds a tab
296334 function addTab( options ) {
@@ -330,43 +368,17 @@
331369 .hide()
332370 .appendTo( context.$ui );
333371 },
 372+
334373 /**
335 - * Set up the magic iframe
 374+ * FIXME: This section is a bit of a "wonky" section given it's supposed to keep compatibility with the
 375+ * textSelection plugin, which works on character-based manipulations as opposed to the node-based manipulations
 376+ * we use for the iframe. It's debatable whether compatibility with this plugin is even being done well, or for
 377+ * that matter should be done at all.
336378 */
337 - 'setup': function() {
338 - // Turn the document's design mode on
339 - context.$iframe[0].contentWindow.document.designMode = 'on';
340 - // Get a reference to the content area of the iframe
341 - context.$content = $( context.$iframe[0].contentWindow.document.body );
342 - // We need to properly escape any HTML entities like &amp;, &lt; and &gt; so they end up as visible
343 - // characters rather than actual HTML tags in the code editor container.
344 -
345 - context.$content.append(
346 - context.$textarea.val().replace( /</g, '&lt;' ).replace( />/g, '&gt;' )
347 - );
348 - // Reflect direction of parent frame into child
349 - if ( $( 'body' ).is( '.rtl' ) ) {
350 - context.$content.addClass( 'rtl' ).attr( 'dir', 'rtl' );
351 - }
352 -
353 - /* Magic IFRAME Activation */
354 -
355 - // Activate the iframe, encoding the content of the textarea and copying it to the content of the iframe
356 - context.$textarea.attr( 'disabled', true );
357 - context.$textarea.hide();
358 - context.$iframe.show();
359 - // Let modules know we're ready to start working with the content
360 - context.fn.trigger( 'ready' );
361 - },
 379+
362380 /**
363 - * Checks whether the magic iframe is properly set up
 381+ * Gets the complete contents of the iframe (in plain text, not HTML)
364382 */
365 - 'isSetup': function() {
366 - return context.$content != undefined && context.$content[0].innerHTML != undefined;
367 - },
368 - /**
369 - * Gets the complete contents of the iframe (text, not HTML)
370 - */
371383 'getContents': function() {
372384 // FIXME: Evil ua-sniffing action!
373385 if ( $.browser.name == 'msie' ) {
@@ -377,7 +389,9 @@
378390 return $( '<div />' ).html( context.$content.html().replace( /\<br\>/g, "\n" ) ).text();
379391 },
380392 /**
381 - * Sets the complete contents of the iframe (text, not HTML; HTML passed will be converted to entities)
 393+ * Sets the complete contents of the iframe (in plain text, not HTML; HTML passed will be converted to entities)
 394+ * FIXME: Passing in options like this is sort of akward - it appears to be a way to make this compatible with
 395+ * the textSelection plugin - is this needed?
382396 */
383397 'setContents': function( options ) {
384398 context.$content.text( options.contents );
@@ -426,11 +440,13 @@
427441 var range = context.$iframe[0].contentWindow.getSelection().getRangeAt( 0 );
428442 if ( options.ownline ) {
429443 // TODO: This'll probably break with syntax highlighting
430 - if ( range.startOffset != 0 )
 444+ if ( range.startOffset != 0 ) {
431445 pre = "\n" + options.pre;
 446+ }
432447 // TODO: Will this still work with syntax highlighting?
433 - if ( range.endContainer == range.commonAncestorContainer )
 448+ if ( range.endContainer == range.commonAncestorContainer ) {
434449 post += "\n";
 450+ }
435451 }
436452 var insertText = pre + selText + post;
437453 var insertLines = insertText.split( "\n" );
@@ -449,8 +465,9 @@
450466 context.fn.scrollToTop( lastNode );
451467 }
452468 // Trigger the encapsulateSelection event (this might need to get named something else/done differently)
453 - context.$content.trigger( 'encapsulateSelection', [ pre, options.peri, post,
454 - options.ownline, options.replace ] );
 469+ context.$content.trigger(
 470+ 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ]
 471+ );
455472 return context.$textarea;
456473 },
457474 /**
@@ -477,18 +494,20 @@
478495 var sc = options.startContainer, ec = options.endContainer;
479496 sc = sc.jquery ? sc[0] : sc;
480497 ec = ec.jquery ? ec[0] : ec;
481 - while ( sc.firstChild && sc.nodeName != '#text' )
 498+ while ( sc.firstChild && sc.nodeName != '#text' ) {
482499 sc = sc.firstChild;
483 - while ( ec.firstChild && ec.nodeName != '#text' )
 500+ }
 501+ while ( ec.firstChild && ec.nodeName != '#text' ) {
484502 ec = ec.firstChild;
 503+ }
485504 // TODO: Can this be done in one call? sel.addRange()?
486505 //sel.removeAllRanges();
487506 sel.extend( sc, options.start );
488507 //if ( sel.
489508 sel.collapseToStart();
490 - if ( options.end != options.start || sc != ec )
 509+ if ( options.end != options.start || sc != ec ) {
491510 sel.extend( ec, options.end );
492 -
 511+ }
493512 },
494513 /**
495514 * Scroll a textarea to the current cursor position. You can set the cursor position with setSelection()
@@ -526,9 +545,20 @@
527546 var e = range.startContainer;
528547 //TODO continue
529548 }
 549+
 550+ /**
 551+ * End of "wonky" textSelection "compatible" section that needs attention.
 552+ */
 553+
530554 };
531555
532 - /* Base UI Construction */
 556+ /*
 557+ * Base UI Construction
 558+ *
 559+ * The UI is built from several containers, the outer-most being a div classed as "wikiEditor-ui". These containers
 560+ * provide a certain amount of "free" layout, but in some situations procedural layout is needed, which is performed
 561+ * as a response to the "resize" event.
 562+ */
533563
534564 // Encapsulate the textarea with some containers for layout
535565 context.$textarea
@@ -537,7 +567,7 @@
538568 .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-left' ) )
539569 .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-bottom' ) )
540570 .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-text' ) );
541 -
 571+ // Get references to some of the newly created containers
542572 context.$ui = context.$textarea.parent().parent().parent().parent().parent();
543573 context.$wikitext = context.$textarea.parent().parent().parent().parent();
544574 // Add in tab and button containers
@@ -548,6 +578,7 @@
549579 .append( $( '<div></div>' ).addClass( 'wikiEditor-ui-buttons' ) )
550580 )
551581 .before( $( '<div style="clear:both;"></div>' ) );
 582+ // Get references to some of the newly created containers
552583 context.$controls = context.$ui.find( '.wikiEditor-ui-buttons' ).hide();
553584 context.$buttons = context.$ui.find( '.wikiEditor-ui-buttons' );
554585 context.$tabs = context.$ui.find( '.wikiEditor-ui-tabs' );
@@ -559,21 +590,14 @@
560591 context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div></div>' ).addClass( 'wikiEditor-ui-top' ) );
561592 // Setup the intial view
562593 context.view = 'wikitext';
563 -
564 - /* Core Event Handlers */
565 -
 594+ // Trigger the "resize" event anytime the window is resized
566595 $( window ).resize( function( event ) { context.fn.trigger( 'resize', event ) } );
567 -
568 - /* Magic IFRAME Construction */
569 -
570596 // Create an iframe in place of the text area
571 - var ts = ( new Date() ).getTime();
572 - var instance = context.instance;
573597 context.$iframe = $( '<iframe></iframe>' )
574598 .attr( {
575599 'frameborder': 0,
576600 'src': wgScriptPath + '/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?' +
577 - 'instance=' + context.instance + '&ts=' + ts,
 601+ 'instance=' + context.instance + '&ts=' + ( new Date() ).getTime(),
578602 'id': 'wikiEditor-iframe-' + context.instance
579603 } )
580604 .css( {
@@ -585,8 +609,27 @@
586610 'overflow-x': 'hidden'
587611 } )
588612 .insertAfter( context.$textarea )
589 - .load( context.fn.setup );
590 -
 613+ .load( function() {
 614+ // Turn the document's design mode on
 615+ context.$iframe[0].contentWindow.document.designMode = 'on';
 616+ // Get a reference to the content area of the iframe
 617+ context.$content = $( context.$iframe[0].contentWindow.document.body );
 618+ // We need to properly escape any HTML entities like &amp;, &lt; and &gt; so they end up as visible
 619+ // characters rather than actual HTML tags in the code editor container.
 620+ context.$content.append(
 621+ context.$textarea.val().replace( /</g, '&lt;' ).replace( />/g, '&gt;' )
 622+ );
 623+ // Reflect direction of parent frame into child
 624+ if ( $( 'body' ).is( '.rtl' ) ) {
 625+ context.$content.addClass( 'rtl' ).attr( 'dir', 'rtl' );
 626+ }
 627+ // Activate the iframe, encoding the content of the textarea and copying it to the content of the iframe
 628+ context.$textarea.attr( 'disabled', true );
 629+ context.$textarea.hide();
 630+ context.$iframe.show();
 631+ // Let modules know we're ready to start working with the content
 632+ context.fn.trigger( 'ready' );
 633+ } );
591634 // Attach a submit handler to the form so that when the form is submitted the content of the iframe gets decoded and
592635 // copied over to the textarea
593636 context.$textarea.closest( 'form' ).submit( function() {
@@ -595,35 +638,17 @@
596639 } );
597640 }
598641
599 -// If there was a configuration passed, it's assumed to be for the addModule API call
600 -if ( arguments.length > 0 && typeof arguments[0] == 'object' ) {
601 - // If the iframe construction isn't ready yet, defer the call
602 - if ( context.fn.isSetup() )
603 - context.api.addModule( context, arguments[0] );
604 - else {
605 - var args = arguments;
606 - setTimeout( function() {
607 - context.api.addModule( context, args[0] );
608 - }, 2 );
 642+/* API Execution */
 643+
 644+// Since javascript gives arguments as an object, we need to convert them so they can be used more easily
 645+arguments = $.makeArray( arguments );
 646+// There would need to be some arguments if the API is being called
 647+if ( arguments.length > 0 ) {
 648+ // Handle API calls
 649+ var call = arguments.shift();
 650+ if ( call in context.api ) {
 651+ context.api[call]( context, typeof arguments[0] == 'undefined' ? {} : arguments[0] );
609652 }
610 -} else {
611 - // Since javascript gives arguments as an object, we need to convert them so they can be used more easily
612 - arguments = $.makeArray( arguments );
613 - if ( arguments.length > 0 ) {
614 - // Handle API calls
615 - var call = arguments.shift();
616 - if ( call in context.api ) {
617 - // If the iframe construction isn't ready yet, defer the call
618 - if ( context.fn.isSetup() )
619 - context.api[call]( context, arguments[0] == undefined ? {} : arguments[0] );
620 - else {
621 - var args = arguments;
622 - setTimeout( function() {
623 - context.api[call]( context, args[0] == undefined ? {} : args[0] );
624 - }, 2 );
625 - }
626 - }
627 - }
628653 }
629654
630655 // Store the context for next time, and support chaining
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.toolbar.js
@@ -282,7 +282,7 @@
283283 var label = $.wikiEditor.autoMsg( tool, 'label' );
284284 switch ( tool.type ) {
285285 case 'button':
286 - var src = $.wikiEditor.getIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' );
 286+ var src = $.wikiEditor.autoIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' );
287287 $button = $( '<img />' ).attr( {
288288 'src' : src,
289289 'width' : 22,
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js
@@ -1309,73 +1309,95 @@
13101310 };
13111311
13121312 } )( jQuery );/**
1313 - * This plugin provides a way to build a user interface around a textarea. You
1314 - * can build the UI from a confguration..
1315 - * $j( 'div#edittoolbar' ).wikiEditor(
1316 - * { 'modules': { 'toolbar': { ... config ... } } }
1317 - * );
1318 - * ...and add modules after it's already been initialized...
1319 - * $j( 'textarea#wpTextbox1' ).wikiEditor(
1320 - * 'addModule', 'toc', { ... config ... }
1321 - * );
1322 - * ...using the API, which is still be finished.
 1313+ * This plugin provides a way to build a wiki-text editing user interface around a textarea.
 1314+ *
 1315+ * @example To intialize without any modules:
 1316+ * $j( 'div#edittoolbar' ).wikiEditor();
 1317+ *
 1318+ * @example To initialize with one or more modules, or to add modules after it's already been initialized:
 1319+ * $j( 'textarea#wpTextbox1' ).wikiEditor( 'addModule', 'toolbar', { ... config ... } );
 1320+ *
13231321 */
13241322 ( function( $ ) {
13251323
 1324+/**
 1325+ * Global static object for wikiEditor that provides generally useful functionality to all modules and contexts.
 1326+ */
13261327 $.wikiEditor = {
 1328+ /**
 1329+ * For each module that is loaded, static code shared by all instances is loaded into this object organized by
 1330+ * module name. The existance of a module in this object only indicates the module is available. To check if a
 1331+ * module is in use by a specific context check the context.modules object.
 1332+ */
13271333 'modules': {},
 1334+ /**
 1335+ * In some cases like with the iframe's HTML file, it's convienent to have a lookup table of all instances of the
 1336+ * WikiEditor. Each context contains an instance field which contains a key that corrosponds to a reference to the
 1337+ * textarea which the WikiEditor was build around. This way, by passing a simple integer you can provide a way back
 1338+ * to a specific context.
 1339+ */
13281340 'instances': [],
13291341 /**
1330 - * For each browser name, an array of conditions that must be met are supplied in [operaton, value] form where
 1342+ * For each browser name, an array of conditions that must be met are supplied in [operaton, value]-form where
13311343 * operation is a string containing a JavaScript compatible binary operator and value is either a number to be
1332 - * compared with $.browser.versionNumber or a string to be compared with $.browser.version
 1344+ * compared with $.browser.versionNumber or a string to be compared with $.browser.version. If a browser is not
 1345+ * specifically mentioned, we just assume things will work.
13331346 */
13341347 'browsers': {
 1348+ // Left-to-right languages
13351349 'ltr': {
 1350+ // The toolbar layout is IE6
13361351 'msie': [['>=', 7]],
 1352+ // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
13371353 'firefox': [
1338 - ['>=', 2],
1339 - ['!=', '2.0'],
1340 - ['!=', '2.0.0.1'],
1341 - ['!=', '2.0.0.2'],
1342 - ['!=', '2.0.0.3'],
1343 - ['!=', '2.0.0.4']
 1354+ ['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
13441355 ],
 1356+ // Text selection bugs galore - this may be a different situation with the new iframe-based solution
13451357 'opera': [['>=', 9.6]],
 1358+ // This should be checked again, but the usage of Safari 3.0 and lower is so small it's not a priority
13461359 'safari': [['>=', 3.1]]
13471360 },
 1361+ // Right-to-left languages
13481362 'rtl': {
 1363+ // The toolbar layout is broken in IE 7 in RTL mode, and IE6 in any mode
13491364 'msie': [['>=', 8]],
 1365+ // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4
13501366 'firefox': [
1351 - ['>=', 2],
1352 - ['!=', '2.0'],
1353 - ['!=', '2.0.0.1'],
1354 - ['!=', '2.0.0.2'],
1355 - ['!=', '2.0.0.3'],
1356 - ['!=', '2.0.0.4']
 1367+ ['>=', 2], ['!=', '2.0'], ['!=', '2.0.0.1'], ['!=', '2.0.0.2'], ['!=', '2.0.0.3'], ['!=', '2.0.0.4']
13571368 ],
 1369+ // Text selection bugs galore - this may be a different situation with the new iframe-based solution
13581370 'opera': [['>=', 9.6]],
 1371+ // This should be checked again, but the usage of Safari 3.0 and lower is so small it's not a priority
13591372 'safari': [['>=', 3.1]]
13601373 }
13611374 },
13621375 /**
1363 - * Path to images - this is a bit messy, and it would need to change if
1364 - * this code (and images) gets moved into the core - or anywhere for
1365 - * that matter...
 1376+ * Path to images - this is a bit messy, and it would need to change if this code (and images) gets moved into the
 1377+ * core - or anywhere for that matter...
13661378 */
13671379 'imgPath' : wgScriptPath + '/extensions/UsabilityInitiative/images/wikiEditor/',
1368 - 'isSupportKnown': function() {
1369 - return $.browser.name in $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'];
1370 - },
 1380+ /**
 1381+ * Checks the current browser against the browsers object to determine if the browser has been black-listed or not.
 1382+ * Because these rules are often very complex, the object contains configurable operators and can check against
 1383+ * either the browser version number or string. This process also involves checking if the current browser is amung
 1384+ * those which we have configured as compatible or not. If the browser was not configured as comptible we just go on
 1385+ * assuming things will work - the argument here is to prevent the need to update the code when a new browser comes
 1386+ * to market. The assumption here is that any new browser will be built on an existing engine or be otherwise so
 1387+ * similar to another existing browser that things actually do work as expected. The merrits of this argument, which
 1388+ * is essentially to blacklist rather than whitelist are debateable, but at this point we've decided it's the more
 1389+ * "open-web" way to go.
 1390+ */
13711391 'isSupported': function() {
1372 - // Cache the return value
1373 - if ( $.wikiEditor.supported != undefined )
 1392+ // Check for and make use of a cached return value
 1393+ if ( $.wikiEditor.supported != undefined ) {
13741394 return $.wikiEditor.supported;
1375 -
1376 - if ( !$.wikiEditor.isSupportKnown ) {
1377 - // Assume good faith :)
 1395+ }
 1396+ // Check if we have any compatiblity information on-hand for the current browser
 1397+ if ( !( $.browser.name in $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'] ) ) {
 1398+ // Assume good faith :)
13781399 return $.wikiEditor.supported = true;
13791400 }
 1401+ // Check over each browser condition to determine if we are running in a compatible client
13801402 var browser = $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'][$.browser.name];
13811403 for ( condition in browser ) {
13821404 var op = browser[condition][0];
@@ -1390,9 +1412,20 @@
13911413 }
13921414 }
13931415 }
 1416+ // Return and also cache the return value - this will be checked somewhat often
13941417 return $.wikiEditor.supported = true;
13951418 },
1396 - // Wraps gM from js2, but allows raw text to supercede
 1419+ /**
 1420+ * Provides a way to extract messages from objects. Wraps the gM function from js2stopgap.js, which will be changing
 1421+ * in the very near future, so let's keep and eye on this. It's also possible that this function will just be moved
 1422+ * to the global mw object all together.
 1423+ *
 1424+ * @param object Object to extract messages from
 1425+ * @param property String of name of property which contains the message. This should be the base name of the
 1426+ * property, which means that in the case of the object { this: 'that', fooMsg: 'bar' }, passing property as 'this'
 1427+ * would return the raw text 'that', while passing property as 'foo' would return the internationalized message
 1428+ * with the key 'bar'.
 1429+ */
13971430 'autoMsg': function( object, property ) {
13981431 // Accept array of possible properties, of which the first one found will be used
13991432 if ( typeof property == 'object' ) {
@@ -1411,122 +1444,113 @@
14121445 return '';
14131446 }
14141447 },
1415 - // Get an icon in a certain language
1416 - // @param icon Icon object from e.g. toolbar config
1417 - // @param path Default icon path, defaults to $.wikiEditor.imgPath
1418 - // @param lang Language code, defaults to wgUserLanguage
1419 - 'getIcon': function( icon, path, lang ) {
1420 - lang = lang || wgUserLanguage;
 1448+ /**
 1449+ * Provieds a way to extract a property of an object in a certain language, falling back on the property keyed as
 1450+ * 'default'. If such key doesn't exist, the object itself is considered the actual value, which should ideally
 1451+ * be the case so that you may use a string or object of any number of strings keyed by language with a default.
 1452+ *
 1453+ * @param object Object to extract property from
 1454+ * @param lang Language code, defaults to wgUserLanguage
 1455+ */
 1456+ 'autoLang': function( object, lang ) {
 1457+ return object[lang || wgUserLanguage] || object['default'] || object;
 1458+ },
 1459+ /**
 1460+ * Provieds a way to extract the path of an icon in a certain language, automatically appending a version number for
 1461+ * caching purposes and prepending an image path when icon paths are relative.
 1462+ *
 1463+ * @param icon Icon object from e.g. toolbar config
 1464+ * @param path Default icon path, defaults to $.wikiEditor.imgPath
 1465+ * @param lang Language code, defaults to wgUserLanguage
 1466+ */
 1467+ 'autoIcon': function( icon, path, lang ) {
 1468+ var src = $.wikiEditor.autoLang( icon, lang );
14211469 path = path || $.wikiEditor.imgPath;
1422 - var src = icon[lang] || icon['default'] || icon;
14231470 // Prepend path if src is not absolute
1424 - if ( src.substr( 0, 7 ) != 'http://' && src.substr( 0, 8 ) != 'https://' &&
1425 - src[0] != '/' )
 1471+ if ( src.substr( 0, 7 ) != 'http://' && src.substr( 0, 8 ) != 'https://' && src[0] != '/' ) {
14261472 src = path + src;
 1473+ }
14271474 return src + '?' + wgWikiEditorIconVersion;
1428 - },
1429 - 'fixOperaBrokenness': function( s ) {
1430 - /*
1431 - // This function works around Opera's
1432 - // broken newline handling in textareas.
1433 - // .val() has \n while selection functions
1434 - // treat newlines as \r\n
1435 -
1436 - if ( typeof $.isOperaBroken == 'undefined' && $.wikiEditor.instances.length > 0 ) {
1437 - // Create a textarea inside a div
1438 - // with zero area, to hide it properly
1439 - var div = $( '<div />' )
1440 - .height( 0 )
1441 - .width( 0 )
1442 - .insertBefore( $.wikiEditor.instances[0] );
1443 - var textarea = $( '<textarea></textarea>' )
1444 - .height( 0 )
1445 - .appendTo( div )
1446 - .val( "foo\r\nbar" );
1447 - // Try to search&replace bar --> BAR
1448 - var index = textarea.val().indexOf( 'bar' );
1449 - textarea.select();
1450 - textarea.setSelection( index, index + 3 );
1451 - textarea.encapsulateSelection( '', 'BAR', '', false, true );
1452 - if ( textarea.val().substr( -4 ) != 'BARr' )
1453 - $.isOperaBroken = false;
1454 - else
1455 - $.isOperaBroken = true;
1456 - div.remove();
1457 - }
1458 - if ( $.isOperaBroken )
1459 - s = s.replace( /\n/g, "\r\n" );
1460 - */
1461 - return s;
14621475 }
14631476 };
14641477
 1478+/**
 1479+ * jQuery plugin that provides a way to initialize a wikiEditor instance on a textarea.
 1480+ */
14651481 $.fn.wikiEditor = function() {
14661482
1467 -// Skip any further work on browsers that are unsupported
1468 -if ( $j.wikiEditor.isSupportKnown() && !$j.wikiEditor.isSupported() ) {
 1483+// Skip any further work when running in browsers that are unsupported
 1484+if ( !$j.wikiEditor.isSupported() ) {
14691485 return $(this);
14701486 }
14711487
14721488 /* Initialization */
14731489
1474 -// The wikiEditor context is stored in the element, so when this function
1475 -// gets called again we can pick up where we left off
 1490+// The wikiEditor context is stored in the element's data, so when this function gets called again we can pick up right
 1491+// where we left off
14761492 var context = $(this).data( 'wikiEditor-context' );
14771493
1478 -// This only gets run on the first call
 1494+// On first call, we need to set things up, but on all following calls we can skip right to the API handling
14791495 if ( typeof context == 'undefined' ) {
14801496
1481 - var instance = $.wikiEditor.instances.length;
1482 - context = { '$textarea': $(this), 'views': {}, 'modules': {}, 'data': {}, 'instance': instance };
1483 - $.wikiEditor.instances[instance] = $(this);
 1497+ // Star filling the context with useful data - any jQuery selections, as usual should be named with a preceding $
 1498+ context = {
 1499+ // Reference to the textarea element which the wikiEditor is being built around
 1500+ '$textarea': $(this),
 1501+ // Container for any number of mutually exclusive views that are accessible by tabs
 1502+ 'views': {},
 1503+ // Container for any number of module-specific data - only including data for modules in use on this context
 1504+ 'modules': {},
 1505+ // General place to shouve bits of data into
 1506+ 'data': {},
 1507+ // Unique numeric ID of this instance used both for looking up and differentiating instances of wikiEditor
 1508+ 'instance': $.wikiEditor.instances.push( $(this) )
 1509+ };
14841510
1485 - /* Externally Accessible API */
 1511+ /*
 1512+ * Externally Accessible API
 1513+ *
 1514+ * These are available using calls to $j(selection).wikiEditor( call, data ) where selection is a jQuery selection
 1515+ * of the textarea that the wikiEditor instance was built around.
 1516+ */
14861517
14871518 context.api = {
14881519 /**
1489 - * Accepts either a string of the name of a module to add without any
1490 - * additional configuration parameters, or an object with members keyed with
1491 - * module names and valued with configuration objects
 1520+ * Activates a module on a specific context with optional configuration data.
 1521+ *
 1522+ * @param data Either a string of the name of a module to add without any additional configuration parameters,
 1523+ * or an object with members keyed with module names and valued with configuration objects.
14921524 */
14931525 'addModule': function( context, data ) {
1494 - // A safe way of calling an API function on a module
1495 - function callModuleApi( module, call, data ) {
1496 - if (
1497 - module in $.wikiEditor.modules &&
1498 - 'fn' in $.wikiEditor.modules[module] &&
1499 - call in $.wikiEditor.modules[module].fn
1500 - ) {
1501 - // Add a place for the module to put it's own stuff
1502 - context.modules[module] = {};
1503 - // Tell the module to create itself
1504 - $.wikiEditor.modules[module].fn[call]( context, data );
1505 - }
1506 - }
 1526+ var modules = {};
15071527 if ( typeof data == 'string' ) {
1508 - callModuleApi( data, 'create', {} );
 1528+ modules[data] = {};
15091529 } else if ( typeof data == 'object' ) {
1510 - for ( module in data ) {
1511 - if ( typeof module == 'string' ) {
1512 - callModuleApi( module, 'create', data[module] );
 1530+ modules = data;
 1531+ }
 1532+ for ( module in modules ) {
 1533+ // Check for the existance of an available module with a matching name and a create function
 1534+ if ( typeof module == 'string' && module in $.wikiEditor.modules ) {
 1535+ // Extend the context's core API with this module's own API calls
 1536+ if ( 'api' in $.wikiEditor.modules[module] ) {
 1537+ for ( call in $.wikiEditor.modules[module].api ) {
 1538+ // Modules may not overwrite existing API functions - first come, first serve
 1539+ if ( !( call in context.api ) ) {
 1540+ context.api[call] = $.wikiEditor.modules[module].api[call];
 1541+ }
 1542+ }
15131543 }
 1544+ // Activate the module on this context
 1545+ if ( 'fn' in $.wikiEditor.modules[module] && 'create' in $.wikiEditor.modules[module].fn ) {
 1546+ // Add a place for the module to put it's own stuff
 1547+ context.modules[module] = {};
 1548+ // Tell the module to create itself on the context
 1549+ $.wikiEditor.modules[module].fn.create( context, modules[module] );
 1550+ }
15141551 }
15151552 }
15161553 }
15171554 };
1518 - // Allow modules to extend the API
1519 - if($.wikiEditor.modules){
1520 - for ( module in $.wikiEditor.modules ) {
1521 - if ( 'api' in $.wikiEditor.modules[module] ) {
1522 - for ( call in $.wikiEditor.modules[module].api ) {
1523 - // Modules may not overwrite existing API functions - first come,
1524 - // first serve
1525 - if ( !( call in context.api ) ) {
1526 - context.api[call] = $.wikiEditor.modules[module].api[call];
1527 - }
1528 - }
1529 - }
1530 - }}
15311555
15321556 /*
15331557 * Event Handlers
@@ -1536,6 +1560,11 @@
15371561 */
15381562
15391563 context.evt = {
 1564+ /**
 1565+ * Filters change events, which occur when the user interacts with the contents of the iframe. The goal of this
 1566+ * function is to both classify the scope of changes as 'division' or 'character' and to prevent further
 1567+ * processing of events which did not actually change the content of the iframe.
 1568+ */
15401569 'change': function( event ) {
15411570 // Event filtering
15421571 switch ( event.type ) {
@@ -1563,8 +1592,10 @@
15641593
15651594 /* Internal Functions */
15661595
1567 - //$(this).data( 'wikiEditor-context', context );
15681596 context.fn = {
 1597+ /**
 1598+ * Executes core event filters as well as event handlers provided by modules.
 1599+ */
15691600 'trigger': function( name, event ) {
15701601 // Event is an optional argument, but from here on out, at least the type field should be dependable
15711602 if ( typeof event == 'undefined' ) {
@@ -1580,8 +1611,8 @@
15811612 return false;
15821613 }
15831614 }
 1615+ // Pass the event around to all modules activated on this context
15841616 for ( module in context.modules ) {
1585 - // Pass the event around to all modules activated on this context
15861617 if (
15871618 module in $.wikiEditor.modules &&
15881619 'evt' in $.wikiEditor.modules[module] &&
@@ -1591,6 +1622,9 @@
15921623 }
15931624 }
15941625 },
 1626+ /**
 1627+ * Adds a button to the UI
 1628+ */
15951629 'addButton': function( options ) {
15961630 // Ensure that buttons and tabs are visible
15971631 context.$controls.show();
@@ -1600,6 +1634,10 @@
16011635 .click( options.action )
16021636 .appendTo( context.$buttons );
16031637 },
 1638+ /**
 1639+ * Adds a view to the UI, which is accessed using a set of tabs. Views are mutually exclusive and by default a
 1640+ * wikitext view will be present. Only when more than one view exists will the tabs will be visible.
 1641+ */
16041642 'addView': function( options ) {
16051643 // Adds a tab
16061644 function addTab( options ) {
@@ -1640,43 +1678,17 @@
16411679 .hide()
16421680 .appendTo( context.$ui );
16431681 },
 1682+
16441683 /**
1645 - * Set up the magic iframe
 1684+ * FIXME: This section is a bit of a "wonky" section given it's supposed to keep compatibility with the
 1685+ * textSelection plugin, which works on character-based manipulations as opposed to the node-based manipulations
 1686+ * we use for the iframe. It's debatable whether compatibility with this plugin is even being done well, or for
 1687+ * that matter should be done at all.
16461688 */
1647 - 'setup': function() {
1648 - // Turn the document's design mode on
1649 - context.$iframe[0].contentWindow.document.designMode = 'on';
1650 - // Get a reference to the content area of the iframe
1651 - context.$content = $( context.$iframe[0].contentWindow.document.body );
1652 - // We need to properly escape any HTML entities like &amp;, &lt; and &gt; so they end up as visible
1653 - // characters rather than actual HTML tags in the code editor container.
1654 -
1655 - context.$content.append(
1656 - context.$textarea.val().replace( /</g, '&lt;' ).replace( />/g, '&gt;' )
1657 - );
1658 - // Reflect direction of parent frame into child
1659 - if ( $( 'body' ).is( '.rtl' ) ) {
1660 - context.$content.addClass( 'rtl' ).attr( 'dir', 'rtl' );
1661 - }
1662 -
1663 - /* Magic IFRAME Activation */
1664 -
1665 - // Activate the iframe, encoding the content of the textarea and copying it to the content of the iframe
1666 - context.$textarea.attr( 'disabled', true );
1667 - context.$textarea.hide();
1668 - context.$iframe.show();
1669 - // Let modules know we're ready to start working with the content
1670 - context.fn.trigger( 'ready' );
1671 - },
 1689+
16721690 /**
1673 - * Checks whether the magic iframe is properly set up
 1691+ * Gets the complete contents of the iframe (in plain text, not HTML)
16741692 */
1675 - 'isSetup': function() {
1676 - return context.$content != undefined && context.$content[0].innerHTML != undefined;
1677 - },
1678 - /**
1679 - * Gets the complete contents of the iframe
1680 - */
16811693 'getContents': function() {
16821694 // FIXME: Evil ua-sniffing action!
16831695 if ( $.browser.name == 'msie' ) {
@@ -1686,6 +1698,11 @@
16871699 // Setting the HTML of the textarea doesn't work on all browsers, use a dummy <div> instead
16881700 return $( '<div />' ).html( context.$content.html().replace( /\<br\>/g, "\n" ) ).text();
16891701 },
 1702+ /**
 1703+ * Sets the complete contents of the iframe (in plain text, not HTML; HTML passed will be converted to entities)
 1704+ * FIXME: Passing in options like this is sort of akward - it appears to be a way to make this compatible with
 1705+ * the textSelection plugin - is this needed?
 1706+ */
16901707 'setContents': function( options ) {
16911708 context.$content.text( options.contents );
16921709 return context.$textarea;
@@ -1733,11 +1750,13 @@
17341751 var range = context.$iframe[0].contentWindow.getSelection().getRangeAt( 0 );
17351752 if ( options.ownline ) {
17361753 // TODO: This'll probably break with syntax highlighting
1737 - if ( range.startOffset != 0 )
 1754+ if ( range.startOffset != 0 ) {
17381755 pre = "\n" + options.pre;
 1756+ }
17391757 // TODO: Will this still work with syntax highlighting?
1740 - if ( range.endContainer == range.commonAncestorContainer )
 1758+ if ( range.endContainer == range.commonAncestorContainer ) {
17411759 post += "\n";
 1760+ }
17421761 }
17431762 var insertText = pre + selText + post;
17441763 var insertLines = insertText.split( "\n" );
@@ -1756,8 +1775,9 @@
17571776 context.fn.scrollToTop( lastNode );
17581777 }
17591778 // Trigger the encapsulateSelection event (this might need to get named something else/done differently)
1760 - context.$content.trigger( 'encapsulateSelection', [ pre, options.peri, post,
1761 - options.ownline, options.replace ] );
 1779+ context.$content.trigger(
 1780+ 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ]
 1781+ );
17621782 return context.$textarea;
17631783 },
17641784 /**
@@ -1784,18 +1804,20 @@
17851805 var sc = options.startContainer, ec = options.endContainer;
17861806 sc = sc.jquery ? sc[0] : sc;
17871807 ec = ec.jquery ? ec[0] : ec;
1788 - while ( sc.firstChild && sc.nodeName != '#text' )
 1808+ while ( sc.firstChild && sc.nodeName != '#text' ) {
17891809 sc = sc.firstChild;
1790 - while ( ec.firstChild && ec.nodeName != '#text' )
 1810+ }
 1811+ while ( ec.firstChild && ec.nodeName != '#text' ) {
17911812 ec = ec.firstChild;
 1813+ }
17921814 // TODO: Can this be done in one call? sel.addRange()?
17931815 //sel.removeAllRanges();
17941816 sel.extend( sc, options.start );
17951817 //if ( sel.
17961818 sel.collapseToStart();
1797 - if ( options.end != options.start || sc != ec )
 1819+ if ( options.end != options.start || sc != ec ) {
17981820 sel.extend( ec, options.end );
1799 -
 1821+ }
18001822 },
18011823 /**
18021824 * Scroll a textarea to the current cursor position. You can set the cursor position with setSelection()
@@ -1833,9 +1855,20 @@
18341856 var e = range.startContainer;
18351857 //TODO continue
18361858 }
 1859+
 1860+ /**
 1861+ * End of "wonky" textSelection "compatible" section that needs attention.
 1862+ */
 1863+
18371864 };
18381865
1839 - /* Base UI Construction */
 1866+ /*
 1867+ * Base UI Construction
 1868+ *
 1869+ * The UI is built from several containers, the outer-most being a div classed as "wikiEditor-ui". These containers
 1870+ * provide a certain amount of "free" layout, but in some situations procedural layout is needed, which is performed
 1871+ * as a response to the "resize" event.
 1872+ */
18401873
18411874 // Encapsulate the textarea with some containers for layout
18421875 context.$textarea
@@ -1844,7 +1877,7 @@
18451878 .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-left' ) )
18461879 .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-bottom' ) )
18471880 .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-text' ) );
1848 -
 1881+ // Get references to some of the newly created containers
18491882 context.$ui = context.$textarea.parent().parent().parent().parent().parent();
18501883 context.$wikitext = context.$textarea.parent().parent().parent().parent();
18511884 // Add in tab and button containers
@@ -1855,6 +1888,7 @@
18561889 .append( $( '<div></div>' ).addClass( 'wikiEditor-ui-buttons' ) )
18571890 )
18581891 .before( $( '<div style="clear:both;"></div>' ) );
 1892+ // Get references to some of the newly created containers
18591893 context.$controls = context.$ui.find( '.wikiEditor-ui-buttons' ).hide();
18601894 context.$buttons = context.$ui.find( '.wikiEditor-ui-buttons' );
18611895 context.$tabs = context.$ui.find( '.wikiEditor-ui-tabs' );
@@ -1866,21 +1900,14 @@
18671901 context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div></div>' ).addClass( 'wikiEditor-ui-top' ) );
18681902 // Setup the intial view
18691903 context.view = 'wikitext';
1870 -
1871 - /* Core Event Handlers */
1872 -
 1904+ // Trigger the "resize" event anytime the window is resized
18731905 $( window ).resize( function( event ) { context.fn.trigger( 'resize', event ) } );
1874 -
1875 - /* Magic IFRAME Construction */
1876 -
18771906 // Create an iframe in place of the text area
1878 - var ts = ( new Date() ).getTime();
1879 - var instance = context.instance;
18801907 context.$iframe = $( '<iframe></iframe>' )
18811908 .attr( {
18821909 'frameborder': 0,
18831910 'src': wgScriptPath + '/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?' +
1884 - 'instance=' + context.instance + '&ts=' + ts,
 1911+ 'instance=' + context.instance + '&ts=' + ( new Date() ).getTime(),
18851912 'id': 'wikiEditor-iframe-' + context.instance
18861913 } )
18871914 .css( {
@@ -1892,8 +1919,27 @@
18931920 'overflow-x': 'hidden'
18941921 } )
18951922 .insertAfter( context.$textarea )
1896 - .load( context.fn.setup );
1897 -
 1923+ .load( function() {
 1924+ // Turn the document's design mode on
 1925+ context.$iframe[0].contentWindow.document.designMode = 'on';
 1926+ // Get a reference to the content area of the iframe
 1927+ context.$content = $( context.$iframe[0].contentWindow.document.body );
 1928+ // We need to properly escape any HTML entities like &amp;, &lt; and &gt; so they end up as visible
 1929+ // characters rather than actual HTML tags in the code editor container.
 1930+ context.$content.append(
 1931+ context.$textarea.val().replace( /</g, '&lt;' ).replace( />/g, '&gt;' )
 1932+ );
 1933+ // Reflect direction of parent frame into child
 1934+ if ( $( 'body' ).is( '.rtl' ) ) {
 1935+ context.$content.addClass( 'rtl' ).attr( 'dir', 'rtl' );
 1936+ }
 1937+ // Activate the iframe, encoding the content of the textarea and copying it to the content of the iframe
 1938+ context.$textarea.attr( 'disabled', true );
 1939+ context.$textarea.hide();
 1940+ context.$iframe.show();
 1941+ // Let modules know we're ready to start working with the content
 1942+ context.fn.trigger( 'ready' );
 1943+ } );
18981944 // Attach a submit handler to the form so that when the form is submitted the content of the iframe gets decoded and
18991945 // copied over to the textarea
19001946 context.$textarea.closest( 'form' ).submit( function() {
@@ -1902,35 +1948,17 @@
19031949 } );
19041950 }
19051951
1906 -// If there was a configuration passed, it's assumed to be for the addModule API call
1907 -if ( arguments.length > 0 && typeof arguments[0] == 'object' ) {
1908 - // If the iframe construction isn't ready yet, defer the call
1909 - if ( context.fn.isSetup() )
1910 - context.api.addModule( context, arguments[0] );
1911 - else {
1912 - var args = arguments;
1913 - setTimeout( function() {
1914 - context.api.addModule( context, args[0] );
1915 - }, 2 );
 1952+/* API Execution */
 1953+
 1954+// Since javascript gives arguments as an object, we need to convert them so they can be used more easily
 1955+arguments = $.makeArray( arguments );
 1956+// There would need to be some arguments if the API is being called
 1957+if ( arguments.length > 0 ) {
 1958+ // Handle API calls
 1959+ var call = arguments.shift();
 1960+ if ( call in context.api ) {
 1961+ context.api[call]( context, typeof arguments[0] == 'undefined' ? {} : arguments[0] );
19161962 }
1917 -} else {
1918 - // Since javascript gives arguments as an object, we need to convert them so they can be used more easily
1919 - arguments = $.makeArray( arguments );
1920 - if ( arguments.length > 0 ) {
1921 - // Handle API calls
1922 - var call = arguments.shift();
1923 - if ( call in context.api ) {
1924 - // If the iframe construction isn't ready yet, defer the call
1925 - if ( context.fn.isSetup() )
1926 - context.api[call]( context, arguments[0] == undefined ? {} : arguments[0] );
1927 - else {
1928 - var args = arguments;
1929 - setTimeout( function() {
1930 - context.api[call]( context, args[0] == undefined ? {} : args[0] );
1931 - }, 2 );
1932 - }
1933 - }
1934 - }
19351963 }
19361964
19371965 // Store the context for next time, and support chaining
@@ -2577,7 +2605,8 @@
25782606 */
25792607 create: function( context, config ) {
25802608
2581 - // check if text is selected
 2609+ //initializations
 2610+
25822611 },
25832612
25842613 //template Model
@@ -2604,12 +2633,16 @@
26052634 var paramsByName = [];
26062635 var templateNameIndex = 0;
26072636
 2637+ //takes all template-specific characters, namely {|=} away if they're not particular to the
 2638+ //template we're looking at
26082639 function markOffTemplates() {
26092640 sanatizedStr = wikitext.replace( /{{/, " " ); //get rid of first {{ with whitespace
26102641 endBraces = sanatizedStr.match( /}}\s*$/ ); //replace end
26112642 sanatizedStr = sanatizedStr.substring( 0, endBraces.index ) + " " +
26122643 sanatizedStr.substring( endBraces.index + 2 );
26132644
 2645+ //match the open braces we just found with equivalent closing braces
 2646+ //note, works for any level of braces
26142647 while ( sanatizedStr.indexOf( '{{' ) != -1 ) {
26152648 startIndex = sanatizedStr.indexOf('{{') + 1;
26162649 openBraces = 2;
@@ -2636,6 +2669,7 @@
26372670
26382671 markOffTemplates();
26392672
 2673+ //parse 1 param at a time
26402674 var doneParsing = false;
26412675 oldDivider = 0;
26422676 divider = sanatizedStr.indexOf( '|', oldDivider );
@@ -2656,6 +2690,8 @@
26572691
26582692 currentParamNumber = 0;
26592693 var valueEndIndex;
 2694+
 2695+ //start looping over params
26602696 while ( !doneParsing ) {
26612697 currentParamNumber++;
26622698 oldDivider = divider;
@@ -2795,10 +2831,15 @@
27962832 };
27972833
27982834 //get a list of all param names (numbers for the anonymous ones)
2799 - this.getAllParams = function() {
 2835+ this.getAllParamNames = function() {
28002836 return paramsByName;
28012837 };
28022838
 2839+ //get the initial params
 2840+ this.getAllInitialParams = function(){
 2841+ return params;
 2842+ }
 2843+
28032844 //get original template text
28042845 this.getOriginalText = function() {
28052846 return wikitext;
@@ -3568,7 +3609,7 @@
35693610 var label = $.wikiEditor.autoMsg( tool, 'label' );
35703611 switch ( tool.type ) {
35713612 case 'button':
3572 - var src = $.wikiEditor.getIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' );
 3613+ var src = $.wikiEditor.autoIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' );
35733614 $button = $( '<img />' ).attr( {
35743615 'src' : src,
35753616 'width' : 22,
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js
@@ -82,15 +82,15 @@
8383 $(this).trigger('scrollToPosition');});}};switch(command){case'encapsulateSelection':options=$.extend({'pre':'','peri':'','post':'','ownline':false,'replace':false},options);break;case'getCaretPosition':options=$.extend({'startAndEnd':false,},options);break;case'setSelection':options=$.extend({'start':undefined,'end':undefined,'startContainer':undefined,'endContainer':undefined,},options);if(options.end===undefined)
8484 options.end=options.start;if(options.endContainer==undefined)
8585 options.endContainer=options.startContainer;break;case'scrollToCaretPosition':options=$.extend({'force':false},options);break;}
86 -var context=$(this).data('wikiEditor-context');var hasIframe=context!==undefined&&context.$iframe!==undefined;return(hasIframe?context.fn:fn)[command].call(this,options);};})(jQuery);(function($){$.wikiEditor={'modules':{},'instances':[],'browsers':{'ltr':{'msie':[['>=',7]],'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.1]]},'rtl':{'msie':[['>=',8]],'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.1]]}},'imgPath':wgScriptPath+'/extensions/UsabilityInitiative/images/wikiEditor/','isSupportKnown':function(){return $.browser.name in $.wikiEditor.browsers[$('body').is('.rtl')?'rtl':'ltr'];},'isSupported':function(){if($.wikiEditor.supported!=undefined)
87 -return $.wikiEditor.supported;if(!$.wikiEditor.isSupportKnown){return $.wikiEditor.supported=true;}
 86+var context=$(this).data('wikiEditor-context');var hasIframe=context!==undefined&&context.$iframe!==undefined;return(hasIframe?context.fn:fn)[command].call(this,options);};})(jQuery);(function($){$.wikiEditor={'modules':{},'instances':[],'browsers':{'ltr':{'msie':[['>=',7]],'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.1]]},'rtl':{'msie':[['>=',8]],'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.1]]}},'imgPath':wgScriptPath+'/extensions/UsabilityInitiative/images/wikiEditor/','isSupported':function(){if($.wikiEditor.supported!=undefined){return $.wikiEditor.supported;}
 87+if(!($.browser.name in $.wikiEditor.browsers[$('body').is('.rtl')?'rtl':'ltr'])){return $.wikiEditor.supported=true;}
8888 var browser=$.wikiEditor.browsers[$('body').is('.rtl')?'rtl':'ltr'][$.browser.name];for(condition in browser){var op=browser[condition][0];var val=browser[condition][1];if(typeof val=='string'){if(!(eval('$.browser.version'+op+'"'+val+'"'))){return $.wikiEditor.supported=false;}}else if(typeof val=='number'){if(!(eval('$.browser.versionNumber'+op+val))){return $.wikiEditor.supported=false;}}}
8989 return $.wikiEditor.supported=true;},'autoMsg':function(object,property){if(typeof property=='object'){for(i in property){if(property[i]in object||property[i]+'Msg'in object){property=property[i];break;}}}
90 -if(property in object){return object[property];}else if(property+'Msg'in object){return gM(object[property+'Msg']);}else{return'';}},'getIcon':function(icon,path,lang){lang=lang||wgUserLanguage;path=path||$.wikiEditor.imgPath;var src=icon[lang]||icon['default']||icon;if(src.substr(0,7)!='http://'&&src.substr(0,8)!='https://'&&src[0]!='/')
91 -src=path+src;return src+'?'+wgWikiEditorIconVersion;},'fixOperaBrokenness':function(s){return s;}};$.fn.wikiEditor=function(){if($j.wikiEditor.isSupportKnown()&&!$j.wikiEditor.isSupported()){return $(this);}
92 -var context=$(this).data('wikiEditor-context');if(typeof context=='undefined'){var instance=$.wikiEditor.instances.length;context={'$textarea':$(this),'views':{},'modules':{},'data':{},'instance':instance};$.wikiEditor.instances[instance]=$(this);context.api={'addModule':function(context,data){function callModuleApi(module,call,data){if(module in $.wikiEditor.modules&&'fn'in $.wikiEditor.modules[module]&&call in $.wikiEditor.modules[module].fn){context.modules[module]={};$.wikiEditor.modules[module].fn[call](context,data);}}
93 -if(typeof data=='string'){callModuleApi(data,'create',{});}else if(typeof data=='object'){for(module in data){if(typeof module=='string'){callModuleApi(module,'create',data[module]);}}}}};if($.wikiEditor.modules){for(module in $.wikiEditor.modules){if('api'in $.wikiEditor.modules[module]){for(call in $.wikiEditor.modules[module].api){if(!(call in context.api)){context.api[call]=$.wikiEditor.modules[module].api[call];}}}}}
94 -context.evt={'change':function(event){switch(event.type){case'keypress':if(true){event.data.scope='division';}else{event.data.scope='character';}
 90+if(property in object){return object[property];}else if(property+'Msg'in object){return gM(object[property+'Msg']);}else{return'';}},'autoLang':function(object,lang){return object[lang||wgUserLanguage]||object['default']||object;},'autoIcon':function(icon,path,lang){var src=$.wikiEditor.autoLang(icon,lang);path=path||$.wikiEditor.imgPath;if(src.substr(0,7)!='http://'&&src.substr(0,8)!='https://'&&src[0]!='/'){src=path+src;}
 91+return src+'?'+wgWikiEditorIconVersion;}};$.fn.wikiEditor=function(){if(!$j.wikiEditor.isSupported()){return $(this);}
 92+var context=$(this).data('wikiEditor-context');if(typeof context=='undefined'){context={'$textarea':$(this),'views':{},'modules':{},'data':{},'instance':$.wikiEditor.instances.push($(this))};context.api={'addModule':function(context,data){var modules={};if(typeof data=='string'){modules[data]={};}else if(typeof data=='object'){modules=data;}
 93+for(module in modules){if(typeof module=='string'&&module in $.wikiEditor.modules){if('api'in $.wikiEditor.modules[module]){for(call in $.wikiEditor.modules[module].api){if(!(call in context.api)){context.api[call]=$.wikiEditor.modules[module].api[call];}}}
 94+if('fn'in $.wikiEditor.modules[module]&&'create'in $.wikiEditor.modules[module].fn){context.modules[module]={};$.wikiEditor.modules[module].fn.create(context,modules[module]);}}}}};context.evt={'change':function(event){switch(event.type){case'keypress':if(true){event.data.scope='division';}else{event.data.scope='character';}
9595 break;case'mousedown':if(true){event.data.scope='division';}else{return false;}
9696 break;default:event.data.scope='division';break;}
9797 return true;}};context.fn={'trigger':function(name,event){if(typeof event=='undefined'){event={'type':'custom'};}
@@ -99,25 +99,21 @@
100100 for(module in context.modules){if(module in $.wikiEditor.modules&&'evt'in $.wikiEditor.modules[module]&&name in $.wikiEditor.modules[module].evt){$.wikiEditor.modules[module].evt[name](context,event);}}},'addButton':function(options){context.$controls.show();context.$buttons.show();return $('<button />').text($.wikiEditor.autoMsg(options,'caption')).click(options.action).appendTo(context.$buttons);},'addView':function(options){function addTab(options){context.$controls.show();context.$tabs.show();return $('<div></div>').attr('rel','wikiEditor-ui-view-'+options.name).addClass(context.view==options.name?'current':null).append($('<a></a>').attr('href','#').click(function(event){context.$ui.find('.wikiEditor-ui-view').hide();context.$ui.find('.'+$(this).parent().attr('rel')).show();context.$tabs.find('div').removeClass('current');$(this).parent().addClass('current');$(this).blur();if('init'in options&&typeof options.init=='function'){options.init(context);}
101101 event.preventDefault();return false;}).text($.wikiEditor.autoMsg(options,'title'))).appendTo(context.$tabs);}
102102 if(!context.$tabs.children().size()){addTab({'name':'wikitext','titleMsg':'wikieditor-wikitext-tab'});}
103 -addTab(options);return $('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-'+options.name).hide().appendTo(context.$ui);},'setup':function(){context.$iframe[0].contentWindow.document.designMode='on';context.$content=$(context.$iframe[0].contentWindow.document.body);context.$content.append(context.$textarea.val().replace(/</g,'&lt;').replace(/>/g,'&gt;'));if($('body').is('.rtl')){context.$content.addClass('rtl').attr('dir','rtl');}
104 -context.$textarea.attr('disabled',true);context.$textarea.hide();context.$iframe.show();context.fn.trigger('ready');},'isSetup':function(){return context.$content!=undefined&&context.$content[0].innerHTML!=undefined;},'getContents':function(){if($.browser.name=='msie'){return context.$content.text();}
 103+addTab(options);return $('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-'+options.name).hide().appendTo(context.$ui);},'getContents':function(){if($.browser.name=='msie'){return context.$content.text();}
105104 return $('<div />').html(context.$content.html().replace(/\<br\>/g,"\n")).text();},'setContents':function(options){context.$content.text(options.contents);return context.$textarea;},'getSelection':function(){var retval;if(context.$iframe[0].contentWindow.getSelection){retval=context.$iframe[0].contentWindow.getSelection();}else if(context.$iframe[0].contentWindow.document.selection){retval=context.$iframe[0].contentWindow.document.selection.createRange();}
106105 if(retval.text){retval=retval.text;}else if(retval.toString){retval=retval.toString();}
107106 return retval;},'encapsulateSelection':function(options){var selText=$(this).textSelection('getSelection');var selectAfter=false;var pre=options.pre,post=options.post;if(!selText){selText=options.peri;selectAfter=true;}else if(options.replace){selText=options.peri;}else if(selText.charAt(selText.length-1)==' '){selText=selText.substring(0,selText.length-1);post+=' ';}
108 -var range=context.$iframe[0].contentWindow.getSelection().getRangeAt(0);if(options.ownline){if(range.startOffset!=0)
109 -pre="\n"+options.pre;if(range.endContainer==range.commonAncestorContainer)
110 -post+="\n";}
 107+var range=context.$iframe[0].contentWindow.getSelection().getRangeAt(0);if(options.ownline){if(range.startOffset!=0){pre="\n"+options.pre;}
 108+if(range.endContainer==range.commonAncestorContainer){post+="\n";}}
111109 var insertText=pre+selText+post;var insertLines=insertText.split("\n");range.extractContents();var lastNode;for(var i=insertLines.length-1;i>=0;i--){range.insertNode(document.createTextNode(insertLines[i]));if(i>0){lastNode=range.insertNode(document.createElement('br'));}}
112110 if(lastNode){context.fn.scrollToTop(lastNode);}
113 -context.$content.trigger('encapsulateSelection',[pre,options.peri,post,options.ownline,options.replace]);return context.$textarea;},'getCaretPosition':function(options){},'setSelection':function(options){var sel=context.$iframe[0].contentWindow.getSelection();var sc=options.startContainer,ec=options.endContainer;sc=sc.jquery?sc[0]:sc;ec=ec.jquery?ec[0]:ec;while(sc.firstChild&&sc.nodeName!='#text')
114 -sc=sc.firstChild;while(ec.firstChild&&ec.nodeName!='#text')
115 -ec=ec.firstChild;sel.extend(sc,options.start);sel.collapseToStart();if(options.end!=options.start||sc!=ec)
116 -sel.extend(ec,options.end);},'scrollToCaretPosition':function(options){},'scrollToTop':function($element,force){var body=context.$content.closest('body');var y=$element.offset().top-context.$content.offset().top;if(force||y<body.scrollTop()||y>body.scrollTop()+body.height())
 111+context.$content.trigger('encapsulateSelection',[pre,options.peri,post,options.ownline,options.replace]);return context.$textarea;},'getCaretPosition':function(options){},'setSelection':function(options){var sel=context.$iframe[0].contentWindow.getSelection();var sc=options.startContainer,ec=options.endContainer;sc=sc.jquery?sc[0]:sc;ec=ec.jquery?ec[0]:ec;while(sc.firstChild&&sc.nodeName!='#text'){sc=sc.firstChild;}
 112+while(ec.firstChild&&ec.nodeName!='#text'){ec=ec.firstChild;}
 113+sel.extend(sc,options.start);sel.collapseToStart();if(options.end!=options.start||sc!=ec){sel.extend(ec,options.end);}},'scrollToCaretPosition':function(options){},'scrollToTop':function($element,force){var body=context.$content.closest('body');var y=$element.offset().top-context.$content.offset().top;if(force||y<body.scrollTop()||y>body.scrollTop()+body.height())
117114 body.scrollTop(y);$element.trigger('scrollToTop');},'beforeSelection':function(selector,getAll){if(typeof selector=='undefined')
118 -selector='*';var retval=[];var range=context.$iframe[0].contentWindow.getSelection().getRangeAt(0);var e=range.startContainer;}};context.$textarea.wrap($('<div></div>').addClass('wikiEditor-ui')).wrap($('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-wikitext')).wrap($('<div></div>').addClass('wikiEditor-ui-left')).wrap($('<div></div>').addClass('wikiEditor-ui-bottom')).wrap($('<div></div>').addClass('wikiEditor-ui-text'));context.$ui=context.$textarea.parent().parent().parent().parent().parent();context.$wikitext=context.$textarea.parent().parent().parent().parent();context.$wikitext.before($('<div></div>').addClass('wikiEditor-ui-controls').append($('<div></div>').addClass('wikiEditor-ui-tabs').hide()).append($('<div></div>').addClass('wikiEditor-ui-buttons'))).before($('<div style="clear:both;"></div>'));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');context.$ui.after($('<div style="clear:both;"></div>'));context.$wikitext.append($('<div></div>').addClass('wikiEditor-ui-right'));context.$wikitext.find('.wikiEditor-ui-left').prepend($('<div></div>').addClass('wikiEditor-ui-top'));context.view='wikitext';$(window).resize(function(event){context.fn.trigger('resize',event)});var ts=(new Date()).getTime();var instance=context.instance;context.$iframe=$('<iframe></iframe>').attr({'frameborder':0,'src':wgScriptPath+'/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?'+'instance='+context.instance+'&ts='+ts,'id':'wikiEditor-iframe-'+context.instance}).css({'backgroundColor':'white','width':'100%','height':context.$textarea.height(),'display':'none','overflow-y':'scroll','overflow-x':'hidden'}).insertAfter(context.$textarea).load(context.fn.setup);context.$textarea.closest('form').submit(function(){context.$textarea.attr('disabled',false);context.$textarea.val(context.$textarea.textSelection('getContents'));});}
119 -if(arguments.length>0&&typeof arguments[0]=='object'){if(context.fn.isSetup())
120 -context.api.addModule(context,arguments[0]);else{var args=arguments;setTimeout(function(){context.api.addModule(context,args[0]);},2);}}else{arguments=$.makeArray(arguments);if(arguments.length>0){var call=arguments.shift();if(call in context.api){if(context.fn.isSetup())
121 -context.api[call](context,arguments[0]==undefined?{}:arguments[0]);else{var args=arguments;setTimeout(function(){context.api[call](context,args[0]==undefined?{}:args[0]);},2);}}}}
 115+selector='*';var retval=[];var range=context.$iframe[0].contentWindow.getSelection().getRangeAt(0);var e=range.startContainer;}};context.$textarea.wrap($('<div></div>').addClass('wikiEditor-ui')).wrap($('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-wikitext')).wrap($('<div></div>').addClass('wikiEditor-ui-left')).wrap($('<div></div>').addClass('wikiEditor-ui-bottom')).wrap($('<div></div>').addClass('wikiEditor-ui-text'));context.$ui=context.$textarea.parent().parent().parent().parent().parent();context.$wikitext=context.$textarea.parent().parent().parent().parent();context.$wikitext.before($('<div></div>').addClass('wikiEditor-ui-controls').append($('<div></div>').addClass('wikiEditor-ui-tabs').hide()).append($('<div></div>').addClass('wikiEditor-ui-buttons'))).before($('<div style="clear:both;"></div>'));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');context.$ui.after($('<div style="clear:both;"></div>'));context.$wikitext.append($('<div></div>').addClass('wikiEditor-ui-right'));context.$wikitext.find('.wikiEditor-ui-left').prepend($('<div></div>').addClass('wikiEditor-ui-top'));context.view='wikitext';$(window).resize(function(event){context.fn.trigger('resize',event)});context.$iframe=$('<iframe></iframe>').attr({'frameborder':0,'src':wgScriptPath+'/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?'+'instance='+context.instance+'&ts='+(new Date()).getTime(),'id':'wikiEditor-iframe-'+context.instance}).css({'backgroundColor':'white','width':'100%','height':context.$textarea.height(),'display':'none','overflow-y':'scroll','overflow-x':'hidden'}).insertAfter(context.$textarea).load(function(){context.$iframe[0].contentWindow.document.designMode='on';context.$content=$(context.$iframe[0].contentWindow.document.body);context.$content.append(context.$textarea.val().replace(/</g,'&lt;').replace(/>/g,'&gt;'));if($('body').is('.rtl')){context.$content.addClass('rtl').attr('dir','rtl');}
 116+context.$textarea.attr('disabled',true);context.$textarea.hide();context.$iframe.show();context.fn.trigger('ready');});context.$textarea.closest('form').submit(function(){context.$textarea.attr('disabled',false);context.$textarea.val(context.$textarea.textSelection('getContents'));});}
 117+arguments=$.makeArray(arguments);if(arguments.length>0){var call=arguments.shift();if(call in context.api){context.api[call](context,typeof arguments[0]=='undefined'?{}:arguments[0]);}}
122118 return $(this).data('wikiEditor-context',context);};})(jQuery);RegExp.escape=function(s){return s.replace(/([.*+?^${}()|\/\\[\]])/g,'\\$1');};(function($){$.wikiEditor.modules.dialogs={api:{addDialog:function(context,data){$.wikiEditor.modules.dialogs.fn.create(context,data)},openDialog:function(context,module){if(module in $.wikiEditor.modules.dialogs.modules){$('#'+$.wikiEditor.modules.dialogs.modules[module].id).dialog('open');}},closeDialog:function(context,data){if(module in $.wikiEditor.modules.dialogs.modules){$('#'+$.wikiEditor.modules.dialogs.modules[module].id).dialog('close');}}},fn:{create:function(context,config){for(module in config){$.wikiEditor.modules.dialogs.modules[module]=config[module];}
123119 mw.load(['$j.ui','$j.ui.dialog','$j.ui.draggable','$j.ui.resizable'],function(){for(module in $.wikiEditor.modules.dialogs.modules){var module=$.wikiEditor.modules.dialogs.modules[module];if($('#'+module.id).size()==0){var configuration=module.dialog;configuration.bgiframe=true;configuration.autoOpen=false;configuration.modal=true;configuration.title=$.wikiEditor.autoMsg(module,'title');configuration.newButtons={};for(msg in configuration.buttons)
124120 configuration.newButtons[gM(msg)]=configuration.buttons[msg];configuration.buttons=configuration.newButtons;var dialogDiv=$('<div /> ').attr('id',module.id).html(module.html).data('context',context).appendTo($('body')).each(module.init).dialog(configuration);if(!('resizeme'in module)||module.resizeme)
@@ -183,7 +179,8 @@
184180 if(typeof params[rangeIndex]=='undefined'){return"";}
185181 valueRange=ranges[params[rangeIndex].valueIndex];if(typeof valueRange.newVal=='undefined'||original){retVal=wikitext.substring(valueRange.begin,valueRange.end);}else{retVal=valueRange.newVal;}
186182 if(value!=null){ranges[params[rangeIndex].valueIndex].newVal=value;}
187 -return retVal;};this.getName=function(){if(typeof ranges[templateNameIndex].newVal=='undefined'){return wikitext.substring(ranges[templateNameIndex].begin,ranges[templateNameIndex].end);}else{return ranges[templateNameIndex].newVal;}};this.setName=function(name){ranges[templateNameIndex].newVal=name;};this.setValue=function(name,value){return getSetValue(name,value,false);};this.getValue=function(name){return getSetValue(name,null,false);};this.getOriginalValue=function(name){return getSetValue(name,null,true);};this.getAllParams=function(){return paramsByName;};this.getOriginalText=function(){return wikitext;};this.getText=function(){newText="";for(i=0;i<ranges.length;i++){if(typeof ranges[i].newVal=='undefined'){wikitext.substring(ranges[i].begin,ranges[i].end);}else{newText+=ranges[i].newVal;}}
 183+return retVal;};this.getName=function(){if(typeof ranges[templateNameIndex].newVal=='undefined'){return wikitext.substring(ranges[templateNameIndex].begin,ranges[templateNameIndex].end);}else{return ranges[templateNameIndex].newVal;}};this.setName=function(name){ranges[templateNameIndex].newVal=name;};this.setValue=function(name,value){return getSetValue(name,value,false);};this.getValue=function(name){return getSetValue(name,null,false);};this.getOriginalValue=function(name){return getSetValue(name,null,true);};this.getAllParamNames=function(){return paramsByName;};this.getAllInitialParams=function(){return params;}
 184+this.getOriginalText=function(){return wikitext;};this.getText=function(){newText="";for(i=0;i<ranges.length;i++){if(typeof ranges[i].newVal=='undefined'){wikitext.substring(ranges[i].begin,ranges[i].end);}else{newText+=ranges[i].newVal;}}
188185 return newText;};}}};})(jQuery);(function($){$.wikiEditor.modules.toc={defaultWidth:'166px',minimumWidth:'70px',api:{},evt:{ready:function(context,event){$.wikiEditor.modules.toc.fn.build(context);context.$content.parent().delayedBind(250,'mouseup scrollToTop keyup change',function(){$(this).eachAsync({bulk:0,loop:function(){$.wikiEditor.modules.toc.fn.build(context);$.wikiEditor.modules.toc.fn.update(context);}});}).blur(function(event){var context=event.data.context;context.$textarea.delayedBindCancel(250,'mouseup scrollToTop keyup change');$.wikiEditor.modules.toc.fn.unhighlight(context);});},resize:function(context,event){context.modules.toc.$toc.height(context.$ui.find('.wikiEditor-ui-left').height()-
189186 context.$ui.find('.tab-toc').outerHeight());}},fn:{create:function(context,config){if('$toc'in context.modules.toc){return;}
190187 var height=context.$ui.find('.wikiEditor-ui-left').height();context.modules.toc.$toc=$('<div />').addClass('wikiEditor-ui-toc').data('context',context);context.$ui.find('.wikiEditor-ui-right').css('width',$.wikiEditor.modules.toc.defaultWidth).append(context.modules.toc.$toc);context.modules.toc.$toc.height(context.$ui.find('.wikiEditor-ui-left').height());context.$ui.find('.wikiEditor-ui-left').css('marginRight',"-"+$.wikiEditor.modules.toc.defaultWidth).children().css('marginRight',$.wikiEditor.modules.toc.defaultWidth);},unhighlight:function(context){context.modules.toc.$toc.find('div').removeClass('current');},update:function(context){$.wikiEditor.modules.toc.fn.unhighlight(context);var position=context.$textarea.textSelection('getCaretPosition');var section=0;if(context.data.outline.length>0){if(!(position<context.data.outline[0].position-1)){while(section<context.data.outline.length&&context.data.outline[section].position-1<position){section++;}
@@ -247,7 +244,7 @@
248245 break;case'dialog':context.$textarea.wikiEditor('openDialog',action.module);break;default:break;}},buildGroup:function(context,id,group){var $group=$('<div />').attr({'class':'group group-'+id,'rel':id});var label=$.wikiEditor.autoMsg(group,'label');if(label){$group.append('<div class="label">'+label+'</div>')}
249246 var empty=true;if('tools'in group){for(tool in group.tools){var tool=$.wikiEditor.modules.toolbar.fn.buildTool(context,tool,group.tools[tool]);if(tool){empty=false;$group.append(tool);}}}
250247 return empty?null:$group;},buildTool:function(context,id,tool){if('filters'in tool){for(filter in tool.filters){if($(tool.filters[filter]).size()==0){return null;}}}
251 -var label=$.wikiEditor.autoMsg(tool,'label');switch(tool.type){case'button':var src=$.wikiEditor.getIcon(tool.icon,$.wikiEditor.imgPath+'toolbar/');$button=$('<img />').attr({'src':src,'width':22,'height':22,'alt':label,'title':label,'rel':id,'class':'tool tool-button'});if('action'in tool){$button.data('action',tool.action).data('context',context).click(function(){$.wikiEditor.modules.toolbar.fn.doAction($(this).data('context'),$(this).data('action'),$(this));return false;});}
 248+var label=$.wikiEditor.autoMsg(tool,'label');switch(tool.type){case'button':var src=$.wikiEditor.autoIcon(tool.icon,$.wikiEditor.imgPath+'toolbar/');$button=$('<img />').attr({'src':src,'width':22,'height':22,'alt':label,'title':label,'rel':id,'class':'tool tool-button'});if('action'in tool){$button.data('action',tool.action).data('context',context).click(function(){$.wikiEditor.modules.toolbar.fn.doAction($(this).data('context'),$(this).data('action'),$(this));return false;});}
252249 return $button;case'select':var $select=$('<div />').attr({'rel':id,'class':'tool tool-select'}).click(function(){var $options=$(this).find('.options');$options.animate({'opacity':'toggle'},'fast');});$options=$('<div />').addClass('options');if('list'in tool){for(option in tool.list){var optionLabel=$.wikiEditor.autoMsg(tool.list[option],'label');$options.append($('<a />').data('action',tool.list[option].action).data('context',context).click(function(){$.wikiEditor.modules.toolbar.fn.doAction($(this).data('context'),$(this).data('action'),$(this));}).text(optionLabel).addClass('option').attr('rel',option));}}
253250 $select.append($('<div />').addClass('menu').append($options));$select.append($('<div />').addClass('label').text(label));return $select;default:return null;}},buildBookmark:function(context,id,page){var label=$.wikiEditor.autoMsg(page,'label');return $('<div />').text(label).attr('rel',id).data('context',context).bind('mousedown',function(){$(this).parent().parent().find('.page').hide();$(this).parent().parent().find('.page-'+$(this).attr('rel')).show();$(this).siblings().removeClass('current');$(this).addClass('current');var section=$(this).parent().parent().attr('rel');if($.trackAction!=undefined){$.trackAction(section+'.'+$(this).attr('rel'));}
254251 $.cookie('wikiEditor-'+$(this).data('context').instance+'-booklet-'+section+'-page',$(this).attr('rel'));});},buildPage:function(context,id,page){var $page=$('<div />').attr({'class':'page page-'+id,'rel':id});switch(page.layout){case'table':$page.addClass('page-table');var html='<table cellpadding=0 cellspacing=0 '+'border=0 width="100%" class="table table-"'+id+'">';if('headings'in page){html+=$.wikiEditor.modules.toolbar.fn.buildHeading(context,page.headings)}

Follow-up revisions

RevisionCommit summaryAuthorDate
r59988Bumped versions for r59987.tparscal23:57, 11 December 2009

Status & tagging log