Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html |
— | — | @@ -4,6 +4,7 @@ |
5 | 5 | <title>WikiEditor</title> |
6 | 6 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
7 | 7 | <script type="text/javascript"> |
| 8 | + /* |
8 | 9 | parent.inherit( window )( function() { |
9 | 10 | function get( name ) { |
10 | 11 | // Extracts the value of a given URL parameter from the current window location |
— | — | @@ -16,6 +17,7 @@ |
17 | 18 | context.fn.trigger( "change", event ) |
18 | 19 | } ); |
19 | 20 | } ); |
| 21 | + */ |
20 | 22 | </script> |
21 | 23 | </head> |
22 | 24 | <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 @@ |
2 | 2 | /** |
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 | + * |
13 | 11 | */ |
14 | 12 | ( function( $ ) { |
15 | 13 | |
| 14 | +/** |
| 15 | + * Global static object for wikiEditor that provides generally useful functionality to all modules and contexts. |
| 16 | + */ |
16 | 17 | $.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 | + */ |
17 | 23 | '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 | + */ |
18 | 30 | 'instances': [], |
19 | 31 | /** |
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 |
21 | 33 | * 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. |
23 | 36 | */ |
24 | 37 | 'browsers': { |
| 38 | + // Left-to-right languages |
25 | 39 | 'ltr': { |
| 40 | + // The toolbar layout is IE6 |
26 | 41 | 'msie': [['>=', 7]], |
| 42 | + // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4 |
27 | 43 | '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'] |
34 | 45 | ], |
| 46 | + // Text selection bugs galore - this may be a different situation with the new iframe-based solution |
35 | 47 | '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 |
36 | 49 | 'safari': [['>=', 3.1]] |
37 | 50 | }, |
| 51 | + // Right-to-left languages |
38 | 52 | 'rtl': { |
| 53 | + // The toolbar layout is broken in IE 7 in RTL mode, and IE6 in any mode |
39 | 54 | 'msie': [['>=', 8]], |
| 55 | + // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4 |
40 | 56 | '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'] |
47 | 58 | ], |
| 59 | + // Text selection bugs galore - this may be a different situation with the new iframe-based solution |
48 | 60 | '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 |
49 | 62 | 'safari': [['>=', 3.1]] |
50 | 63 | } |
51 | 64 | }, |
52 | 65 | /** |
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... |
56 | 68 | */ |
57 | 69 | '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 | + */ |
61 | 81 | '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 ) { |
64 | 84 | 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 :) |
68 | 89 | return $.wikiEditor.supported = true; |
69 | 90 | } |
| 91 | + // Check over each browser condition to determine if we are running in a compatible client |
70 | 92 | var browser = $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'][$.browser.name]; |
71 | 93 | for ( condition in browser ) { |
72 | 94 | var op = browser[condition][0]; |
— | — | @@ -80,9 +102,20 @@ |
81 | 103 | } |
82 | 104 | } |
83 | 105 | } |
| 106 | + // Return and also cache the return value - this will be checked somewhat often |
84 | 107 | return $.wikiEditor.supported = true; |
85 | 108 | }, |
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 | + */ |
87 | 120 | 'autoMsg': function( object, property ) { |
88 | 121 | // Accept array of possible properties, of which the first one found will be used |
89 | 122 | if ( typeof property == 'object' ) { |
— | — | @@ -101,122 +134,113 @@ |
102 | 135 | return ''; |
103 | 136 | } |
104 | 137 | }, |
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 ); |
111 | 159 | path = path || $.wikiEditor.imgPath; |
112 | | - var src = icon[lang] || icon['default'] || icon; |
113 | 160 | // 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] != '/' ) { |
116 | 162 | src = path + src; |
| 163 | + } |
117 | 164 | 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; |
152 | 165 | } |
153 | 166 | }; |
154 | 167 | |
| 168 | +/** |
| 169 | + * jQuery plugin that provides a way to initialize a wikiEditor instance on a textarea. |
| 170 | + */ |
155 | 171 | $.fn.wikiEditor = function() { |
156 | 172 | |
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() ) { |
159 | 175 | return $(this); |
160 | 176 | } |
161 | 177 | |
162 | 178 | /* Initialization */ |
163 | 179 | |
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 |
166 | 182 | var context = $(this).data( 'wikiEditor-context' ); |
167 | 183 | |
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 |
169 | 185 | if ( typeof context == 'undefined' ) { |
170 | 186 | |
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 | + }; |
174 | 200 | |
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 | + */ |
176 | 207 | |
177 | 208 | context.api = { |
178 | 209 | /** |
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. |
182 | 214 | */ |
183 | 215 | '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 = {}; |
197 | 217 | if ( typeof data == 'string' ) { |
198 | | - callModuleApi( data, 'create', {} ); |
| 218 | + modules[data] = {}; |
199 | 219 | } 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 | + } |
203 | 233 | } |
| 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 | + } |
204 | 241 | } |
205 | 242 | } |
206 | 243 | } |
207 | 244 | }; |
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 | | - }} |
221 | 245 | |
222 | 246 | /* |
223 | 247 | * Event Handlers |
— | — | @@ -226,6 +250,11 @@ |
227 | 251 | */ |
228 | 252 | |
229 | 253 | 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 | + */ |
230 | 259 | 'change': function( event ) { |
231 | 260 | // Event filtering |
232 | 261 | switch ( event.type ) { |
— | — | @@ -253,8 +282,10 @@ |
254 | 283 | |
255 | 284 | /* Internal Functions */ |
256 | 285 | |
257 | | - //$(this).data( 'wikiEditor-context', context ); |
258 | 286 | context.fn = { |
| 287 | + /** |
| 288 | + * Executes core event filters as well as event handlers provided by modules. |
| 289 | + */ |
259 | 290 | 'trigger': function( name, event ) { |
260 | 291 | // Event is an optional argument, but from here on out, at least the type field should be dependable |
261 | 292 | if ( typeof event == 'undefined' ) { |
— | — | @@ -270,8 +301,8 @@ |
271 | 302 | return false; |
272 | 303 | } |
273 | 304 | } |
| 305 | + // Pass the event around to all modules activated on this context |
274 | 306 | for ( module in context.modules ) { |
275 | | - // Pass the event around to all modules activated on this context |
276 | 307 | if ( |
277 | 308 | module in $.wikiEditor.modules && |
278 | 309 | 'evt' in $.wikiEditor.modules[module] && |
— | — | @@ -281,6 +312,9 @@ |
282 | 313 | } |
283 | 314 | } |
284 | 315 | }, |
| 316 | + /** |
| 317 | + * Adds a button to the UI |
| 318 | + */ |
285 | 319 | 'addButton': function( options ) { |
286 | 320 | // Ensure that buttons and tabs are visible |
287 | 321 | context.$controls.show(); |
— | — | @@ -290,6 +324,10 @@ |
291 | 325 | .click( options.action ) |
292 | 326 | .appendTo( context.$buttons ); |
293 | 327 | }, |
| 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 | + */ |
294 | 332 | 'addView': function( options ) { |
295 | 333 | // Adds a tab |
296 | 334 | function addTab( options ) { |
— | — | @@ -330,43 +368,17 @@ |
331 | 369 | .hide() |
332 | 370 | .appendTo( context.$ui ); |
333 | 371 | }, |
| 372 | + |
334 | 373 | /** |
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. |
336 | 378 | */ |
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 &, < and > 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, '<' ).replace( />/g, '>' ) |
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 | + |
362 | 380 | /** |
363 | | - * Checks whether the magic iframe is properly set up |
| 381 | + * Gets the complete contents of the iframe (in plain text, not HTML) |
364 | 382 | */ |
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 | | - */ |
371 | 383 | 'getContents': function() { |
372 | 384 | // FIXME: Evil ua-sniffing action! |
373 | 385 | if ( $.browser.name == 'msie' ) { |
— | — | @@ -377,7 +389,9 @@ |
378 | 390 | return $( '<div />' ).html( context.$content.html().replace( /\<br\>/g, "\n" ) ).text(); |
379 | 391 | }, |
380 | 392 | /** |
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? |
382 | 396 | */ |
383 | 397 | 'setContents': function( options ) { |
384 | 398 | context.$content.text( options.contents ); |
— | — | @@ -426,11 +440,13 @@ |
427 | 441 | var range = context.$iframe[0].contentWindow.getSelection().getRangeAt( 0 ); |
428 | 442 | if ( options.ownline ) { |
429 | 443 | // TODO: This'll probably break with syntax highlighting |
430 | | - if ( range.startOffset != 0 ) |
| 444 | + if ( range.startOffset != 0 ) { |
431 | 445 | pre = "\n" + options.pre; |
| 446 | + } |
432 | 447 | // TODO: Will this still work with syntax highlighting? |
433 | | - if ( range.endContainer == range.commonAncestorContainer ) |
| 448 | + if ( range.endContainer == range.commonAncestorContainer ) { |
434 | 449 | post += "\n"; |
| 450 | + } |
435 | 451 | } |
436 | 452 | var insertText = pre + selText + post; |
437 | 453 | var insertLines = insertText.split( "\n" ); |
— | — | @@ -449,8 +465,9 @@ |
450 | 466 | context.fn.scrollToTop( lastNode ); |
451 | 467 | } |
452 | 468 | // 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 | + ); |
455 | 472 | return context.$textarea; |
456 | 473 | }, |
457 | 474 | /** |
— | — | @@ -477,18 +494,20 @@ |
478 | 495 | var sc = options.startContainer, ec = options.endContainer; |
479 | 496 | sc = sc.jquery ? sc[0] : sc; |
480 | 497 | ec = ec.jquery ? ec[0] : ec; |
481 | | - while ( sc.firstChild && sc.nodeName != '#text' ) |
| 498 | + while ( sc.firstChild && sc.nodeName != '#text' ) { |
482 | 499 | sc = sc.firstChild; |
483 | | - while ( ec.firstChild && ec.nodeName != '#text' ) |
| 500 | + } |
| 501 | + while ( ec.firstChild && ec.nodeName != '#text' ) { |
484 | 502 | ec = ec.firstChild; |
| 503 | + } |
485 | 504 | // TODO: Can this be done in one call? sel.addRange()? |
486 | 505 | //sel.removeAllRanges(); |
487 | 506 | sel.extend( sc, options.start ); |
488 | 507 | //if ( sel. |
489 | 508 | sel.collapseToStart(); |
490 | | - if ( options.end != options.start || sc != ec ) |
| 509 | + if ( options.end != options.start || sc != ec ) { |
491 | 510 | sel.extend( ec, options.end ); |
492 | | - |
| 511 | + } |
493 | 512 | }, |
494 | 513 | /** |
495 | 514 | * Scroll a textarea to the current cursor position. You can set the cursor position with setSelection() |
— | — | @@ -526,9 +545,20 @@ |
527 | 546 | var e = range.startContainer; |
528 | 547 | //TODO continue |
529 | 548 | } |
| 549 | + |
| 550 | + /** |
| 551 | + * End of "wonky" textSelection "compatible" section that needs attention. |
| 552 | + */ |
| 553 | + |
530 | 554 | }; |
531 | 555 | |
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 | + */ |
533 | 563 | |
534 | 564 | // Encapsulate the textarea with some containers for layout |
535 | 565 | context.$textarea |
— | — | @@ -537,7 +567,7 @@ |
538 | 568 | .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-left' ) ) |
539 | 569 | .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-bottom' ) ) |
540 | 570 | .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-text' ) ); |
541 | | - |
| 571 | + // Get references to some of the newly created containers |
542 | 572 | context.$ui = context.$textarea.parent().parent().parent().parent().parent(); |
543 | 573 | context.$wikitext = context.$textarea.parent().parent().parent().parent(); |
544 | 574 | // Add in tab and button containers |
— | — | @@ -548,6 +578,7 @@ |
549 | 579 | .append( $( '<div></div>' ).addClass( 'wikiEditor-ui-buttons' ) ) |
550 | 580 | ) |
551 | 581 | .before( $( '<div style="clear:both;"></div>' ) ); |
| 582 | + // Get references to some of the newly created containers |
552 | 583 | context.$controls = context.$ui.find( '.wikiEditor-ui-buttons' ).hide(); |
553 | 584 | context.$buttons = context.$ui.find( '.wikiEditor-ui-buttons' ); |
554 | 585 | context.$tabs = context.$ui.find( '.wikiEditor-ui-tabs' ); |
— | — | @@ -559,21 +590,14 @@ |
560 | 591 | context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div></div>' ).addClass( 'wikiEditor-ui-top' ) ); |
561 | 592 | // Setup the intial view |
562 | 593 | context.view = 'wikitext'; |
563 | | - |
564 | | - /* Core Event Handlers */ |
565 | | - |
| 594 | + // Trigger the "resize" event anytime the window is resized |
566 | 595 | $( window ).resize( function( event ) { context.fn.trigger( 'resize', event ) } ); |
567 | | - |
568 | | - /* Magic IFRAME Construction */ |
569 | | - |
570 | 596 | // Create an iframe in place of the text area |
571 | | - var ts = ( new Date() ).getTime(); |
572 | | - var instance = context.instance; |
573 | 597 | context.$iframe = $( '<iframe></iframe>' ) |
574 | 598 | .attr( { |
575 | 599 | 'frameborder': 0, |
576 | 600 | 'src': wgScriptPath + '/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?' + |
577 | | - 'instance=' + context.instance + '&ts=' + ts, |
| 601 | + 'instance=' + context.instance + '&ts=' + ( new Date() ).getTime(), |
578 | 602 | 'id': 'wikiEditor-iframe-' + context.instance |
579 | 603 | } ) |
580 | 604 | .css( { |
— | — | @@ -585,8 +609,27 @@ |
586 | 610 | 'overflow-x': 'hidden' |
587 | 611 | } ) |
588 | 612 | .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 &, < and > 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, '<' ).replace( />/g, '>' ) |
| 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 | + } ); |
591 | 634 | // Attach a submit handler to the form so that when the form is submitted the content of the iframe gets decoded and |
592 | 635 | // copied over to the textarea |
593 | 636 | context.$textarea.closest( 'form' ).submit( function() { |
— | — | @@ -595,35 +638,17 @@ |
596 | 639 | } ); |
597 | 640 | } |
598 | 641 | |
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] ); |
609 | 652 | } |
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 | | - } |
628 | 653 | } |
629 | 654 | |
630 | 655 | // Store the context for next time, and support chaining |
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.toolbar.js |
— | — | @@ -282,7 +282,7 @@ |
283 | 283 | var label = $.wikiEditor.autoMsg( tool, 'label' ); |
284 | 284 | switch ( tool.type ) { |
285 | 285 | case 'button': |
286 | | - var src = $.wikiEditor.getIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' ); |
| 286 | + var src = $.wikiEditor.autoIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' ); |
287 | 287 | $button = $( '<img />' ).attr( { |
288 | 288 | 'src' : src, |
289 | 289 | 'width' : 22, |
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js |
— | — | @@ -1309,73 +1309,95 @@ |
1310 | 1310 | }; |
1311 | 1311 | |
1312 | 1312 | } )( 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 | + * |
1323 | 1321 | */ |
1324 | 1322 | ( function( $ ) { |
1325 | 1323 | |
| 1324 | +/** |
| 1325 | + * Global static object for wikiEditor that provides generally useful functionality to all modules and contexts. |
| 1326 | + */ |
1326 | 1327 | $.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 | + */ |
1327 | 1333 | '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 | + */ |
1328 | 1340 | 'instances': [], |
1329 | 1341 | /** |
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 |
1331 | 1343 | * 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. |
1333 | 1346 | */ |
1334 | 1347 | 'browsers': { |
| 1348 | + // Left-to-right languages |
1335 | 1349 | 'ltr': { |
| 1350 | + // The toolbar layout is IE6 |
1336 | 1351 | 'msie': [['>=', 7]], |
| 1352 | + // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4 |
1337 | 1353 | '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'] |
1344 | 1355 | ], |
| 1356 | + // Text selection bugs galore - this may be a different situation with the new iframe-based solution |
1345 | 1357 | '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 |
1346 | 1359 | 'safari': [['>=', 3.1]] |
1347 | 1360 | }, |
| 1361 | + // Right-to-left languages |
1348 | 1362 | 'rtl': { |
| 1363 | + // The toolbar layout is broken in IE 7 in RTL mode, and IE6 in any mode |
1349 | 1364 | 'msie': [['>=', 8]], |
| 1365 | + // jQuery UI appears to be broken in FF 2.0 - 2.0.0.4 |
1350 | 1366 | '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'] |
1357 | 1368 | ], |
| 1369 | + // Text selection bugs galore - this may be a different situation with the new iframe-based solution |
1358 | 1370 | '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 |
1359 | 1372 | 'safari': [['>=', 3.1]] |
1360 | 1373 | } |
1361 | 1374 | }, |
1362 | 1375 | /** |
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... |
1366 | 1378 | */ |
1367 | 1379 | '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 | + */ |
1371 | 1391 | '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 ) { |
1374 | 1394 | 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 :) |
1378 | 1399 | return $.wikiEditor.supported = true; |
1379 | 1400 | } |
| 1401 | + // Check over each browser condition to determine if we are running in a compatible client |
1380 | 1402 | var browser = $.wikiEditor.browsers[$( 'body' ).is( '.rtl' ) ? 'rtl' : 'ltr'][$.browser.name]; |
1381 | 1403 | for ( condition in browser ) { |
1382 | 1404 | var op = browser[condition][0]; |
— | — | @@ -1390,9 +1412,20 @@ |
1391 | 1413 | } |
1392 | 1414 | } |
1393 | 1415 | } |
| 1416 | + // Return and also cache the return value - this will be checked somewhat often |
1394 | 1417 | return $.wikiEditor.supported = true; |
1395 | 1418 | }, |
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 | + */ |
1397 | 1430 | 'autoMsg': function( object, property ) { |
1398 | 1431 | // Accept array of possible properties, of which the first one found will be used |
1399 | 1432 | if ( typeof property == 'object' ) { |
— | — | @@ -1411,122 +1444,113 @@ |
1412 | 1445 | return ''; |
1413 | 1446 | } |
1414 | 1447 | }, |
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 ); |
1421 | 1469 | path = path || $.wikiEditor.imgPath; |
1422 | | - var src = icon[lang] || icon['default'] || icon; |
1423 | 1470 | // 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] != '/' ) { |
1426 | 1472 | src = path + src; |
| 1473 | + } |
1427 | 1474 | 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; |
1462 | 1475 | } |
1463 | 1476 | }; |
1464 | 1477 | |
| 1478 | +/** |
| 1479 | + * jQuery plugin that provides a way to initialize a wikiEditor instance on a textarea. |
| 1480 | + */ |
1465 | 1481 | $.fn.wikiEditor = function() { |
1466 | 1482 | |
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() ) { |
1469 | 1485 | return $(this); |
1470 | 1486 | } |
1471 | 1487 | |
1472 | 1488 | /* Initialization */ |
1473 | 1489 | |
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 |
1476 | 1492 | var context = $(this).data( 'wikiEditor-context' ); |
1477 | 1493 | |
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 |
1479 | 1495 | if ( typeof context == 'undefined' ) { |
1480 | 1496 | |
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 | + }; |
1484 | 1510 | |
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 | + */ |
1486 | 1517 | |
1487 | 1518 | context.api = { |
1488 | 1519 | /** |
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. |
1492 | 1524 | */ |
1493 | 1525 | '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 = {}; |
1507 | 1527 | if ( typeof data == 'string' ) { |
1508 | | - callModuleApi( data, 'create', {} ); |
| 1528 | + modules[data] = {}; |
1509 | 1529 | } 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 | + } |
1513 | 1543 | } |
| 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 | + } |
1514 | 1551 | } |
1515 | 1552 | } |
1516 | 1553 | } |
1517 | 1554 | }; |
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 | | - }} |
1531 | 1555 | |
1532 | 1556 | /* |
1533 | 1557 | * Event Handlers |
— | — | @@ -1536,6 +1560,11 @@ |
1537 | 1561 | */ |
1538 | 1562 | |
1539 | 1563 | 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 | + */ |
1540 | 1569 | 'change': function( event ) { |
1541 | 1570 | // Event filtering |
1542 | 1571 | switch ( event.type ) { |
— | — | @@ -1563,8 +1592,10 @@ |
1564 | 1593 | |
1565 | 1594 | /* Internal Functions */ |
1566 | 1595 | |
1567 | | - //$(this).data( 'wikiEditor-context', context ); |
1568 | 1596 | context.fn = { |
| 1597 | + /** |
| 1598 | + * Executes core event filters as well as event handlers provided by modules. |
| 1599 | + */ |
1569 | 1600 | 'trigger': function( name, event ) { |
1570 | 1601 | // Event is an optional argument, but from here on out, at least the type field should be dependable |
1571 | 1602 | if ( typeof event == 'undefined' ) { |
— | — | @@ -1580,8 +1611,8 @@ |
1581 | 1612 | return false; |
1582 | 1613 | } |
1583 | 1614 | } |
| 1615 | + // Pass the event around to all modules activated on this context |
1584 | 1616 | for ( module in context.modules ) { |
1585 | | - // Pass the event around to all modules activated on this context |
1586 | 1617 | if ( |
1587 | 1618 | module in $.wikiEditor.modules && |
1588 | 1619 | 'evt' in $.wikiEditor.modules[module] && |
— | — | @@ -1591,6 +1622,9 @@ |
1592 | 1623 | } |
1593 | 1624 | } |
1594 | 1625 | }, |
| 1626 | + /** |
| 1627 | + * Adds a button to the UI |
| 1628 | + */ |
1595 | 1629 | 'addButton': function( options ) { |
1596 | 1630 | // Ensure that buttons and tabs are visible |
1597 | 1631 | context.$controls.show(); |
— | — | @@ -1600,6 +1634,10 @@ |
1601 | 1635 | .click( options.action ) |
1602 | 1636 | .appendTo( context.$buttons ); |
1603 | 1637 | }, |
| 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 | + */ |
1604 | 1642 | 'addView': function( options ) { |
1605 | 1643 | // Adds a tab |
1606 | 1644 | function addTab( options ) { |
— | — | @@ -1640,43 +1678,17 @@ |
1641 | 1679 | .hide() |
1642 | 1680 | .appendTo( context.$ui ); |
1643 | 1681 | }, |
| 1682 | + |
1644 | 1683 | /** |
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. |
1646 | 1688 | */ |
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 &, < and > 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, '<' ).replace( />/g, '>' ) |
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 | + |
1672 | 1690 | /** |
1673 | | - * Checks whether the magic iframe is properly set up |
| 1691 | + * Gets the complete contents of the iframe (in plain text, not HTML) |
1674 | 1692 | */ |
1675 | | - 'isSetup': function() { |
1676 | | - return context.$content != undefined && context.$content[0].innerHTML != undefined; |
1677 | | - }, |
1678 | | - /** |
1679 | | - * Gets the complete contents of the iframe |
1680 | | - */ |
1681 | 1693 | 'getContents': function() { |
1682 | 1694 | // FIXME: Evil ua-sniffing action! |
1683 | 1695 | if ( $.browser.name == 'msie' ) { |
— | — | @@ -1686,6 +1698,11 @@ |
1687 | 1699 | // Setting the HTML of the textarea doesn't work on all browsers, use a dummy <div> instead |
1688 | 1700 | return $( '<div />' ).html( context.$content.html().replace( /\<br\>/g, "\n" ) ).text(); |
1689 | 1701 | }, |
| 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 | + */ |
1690 | 1707 | 'setContents': function( options ) { |
1691 | 1708 | context.$content.text( options.contents ); |
1692 | 1709 | return context.$textarea; |
— | — | @@ -1733,11 +1750,13 @@ |
1734 | 1751 | var range = context.$iframe[0].contentWindow.getSelection().getRangeAt( 0 ); |
1735 | 1752 | if ( options.ownline ) { |
1736 | 1753 | // TODO: This'll probably break with syntax highlighting |
1737 | | - if ( range.startOffset != 0 ) |
| 1754 | + if ( range.startOffset != 0 ) { |
1738 | 1755 | pre = "\n" + options.pre; |
| 1756 | + } |
1739 | 1757 | // TODO: Will this still work with syntax highlighting? |
1740 | | - if ( range.endContainer == range.commonAncestorContainer ) |
| 1758 | + if ( range.endContainer == range.commonAncestorContainer ) { |
1741 | 1759 | post += "\n"; |
| 1760 | + } |
1742 | 1761 | } |
1743 | 1762 | var insertText = pre + selText + post; |
1744 | 1763 | var insertLines = insertText.split( "\n" ); |
— | — | @@ -1756,8 +1775,9 @@ |
1757 | 1776 | context.fn.scrollToTop( lastNode ); |
1758 | 1777 | } |
1759 | 1778 | // 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 | + ); |
1762 | 1782 | return context.$textarea; |
1763 | 1783 | }, |
1764 | 1784 | /** |
— | — | @@ -1784,18 +1804,20 @@ |
1785 | 1805 | var sc = options.startContainer, ec = options.endContainer; |
1786 | 1806 | sc = sc.jquery ? sc[0] : sc; |
1787 | 1807 | ec = ec.jquery ? ec[0] : ec; |
1788 | | - while ( sc.firstChild && sc.nodeName != '#text' ) |
| 1808 | + while ( sc.firstChild && sc.nodeName != '#text' ) { |
1789 | 1809 | sc = sc.firstChild; |
1790 | | - while ( ec.firstChild && ec.nodeName != '#text' ) |
| 1810 | + } |
| 1811 | + while ( ec.firstChild && ec.nodeName != '#text' ) { |
1791 | 1812 | ec = ec.firstChild; |
| 1813 | + } |
1792 | 1814 | // TODO: Can this be done in one call? sel.addRange()? |
1793 | 1815 | //sel.removeAllRanges(); |
1794 | 1816 | sel.extend( sc, options.start ); |
1795 | 1817 | //if ( sel. |
1796 | 1818 | sel.collapseToStart(); |
1797 | | - if ( options.end != options.start || sc != ec ) |
| 1819 | + if ( options.end != options.start || sc != ec ) { |
1798 | 1820 | sel.extend( ec, options.end ); |
1799 | | - |
| 1821 | + } |
1800 | 1822 | }, |
1801 | 1823 | /** |
1802 | 1824 | * Scroll a textarea to the current cursor position. You can set the cursor position with setSelection() |
— | — | @@ -1833,9 +1855,20 @@ |
1834 | 1856 | var e = range.startContainer; |
1835 | 1857 | //TODO continue |
1836 | 1858 | } |
| 1859 | + |
| 1860 | + /** |
| 1861 | + * End of "wonky" textSelection "compatible" section that needs attention. |
| 1862 | + */ |
| 1863 | + |
1837 | 1864 | }; |
1838 | 1865 | |
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 | + */ |
1840 | 1873 | |
1841 | 1874 | // Encapsulate the textarea with some containers for layout |
1842 | 1875 | context.$textarea |
— | — | @@ -1844,7 +1877,7 @@ |
1845 | 1878 | .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-left' ) ) |
1846 | 1879 | .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-bottom' ) ) |
1847 | 1880 | .wrap( $( '<div></div>' ).addClass( 'wikiEditor-ui-text' ) ); |
1848 | | - |
| 1881 | + // Get references to some of the newly created containers |
1849 | 1882 | context.$ui = context.$textarea.parent().parent().parent().parent().parent(); |
1850 | 1883 | context.$wikitext = context.$textarea.parent().parent().parent().parent(); |
1851 | 1884 | // Add in tab and button containers |
— | — | @@ -1855,6 +1888,7 @@ |
1856 | 1889 | .append( $( '<div></div>' ).addClass( 'wikiEditor-ui-buttons' ) ) |
1857 | 1890 | ) |
1858 | 1891 | .before( $( '<div style="clear:both;"></div>' ) ); |
| 1892 | + // Get references to some of the newly created containers |
1859 | 1893 | context.$controls = context.$ui.find( '.wikiEditor-ui-buttons' ).hide(); |
1860 | 1894 | context.$buttons = context.$ui.find( '.wikiEditor-ui-buttons' ); |
1861 | 1895 | context.$tabs = context.$ui.find( '.wikiEditor-ui-tabs' ); |
— | — | @@ -1866,21 +1900,14 @@ |
1867 | 1901 | context.$wikitext.find( '.wikiEditor-ui-left' ).prepend( $( '<div></div>' ).addClass( 'wikiEditor-ui-top' ) ); |
1868 | 1902 | // Setup the intial view |
1869 | 1903 | context.view = 'wikitext'; |
1870 | | - |
1871 | | - /* Core Event Handlers */ |
1872 | | - |
| 1904 | + // Trigger the "resize" event anytime the window is resized |
1873 | 1905 | $( window ).resize( function( event ) { context.fn.trigger( 'resize', event ) } ); |
1874 | | - |
1875 | | - /* Magic IFRAME Construction */ |
1876 | | - |
1877 | 1906 | // Create an iframe in place of the text area |
1878 | | - var ts = ( new Date() ).getTime(); |
1879 | | - var instance = context.instance; |
1880 | 1907 | context.$iframe = $( '<iframe></iframe>' ) |
1881 | 1908 | .attr( { |
1882 | 1909 | 'frameborder': 0, |
1883 | 1910 | 'src': wgScriptPath + '/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?' + |
1884 | | - 'instance=' + context.instance + '&ts=' + ts, |
| 1911 | + 'instance=' + context.instance + '&ts=' + ( new Date() ).getTime(), |
1885 | 1912 | 'id': 'wikiEditor-iframe-' + context.instance |
1886 | 1913 | } ) |
1887 | 1914 | .css( { |
— | — | @@ -1892,8 +1919,27 @@ |
1893 | 1920 | 'overflow-x': 'hidden' |
1894 | 1921 | } ) |
1895 | 1922 | .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 &, < and > 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, '<' ).replace( />/g, '>' ) |
| 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 | + } ); |
1898 | 1944 | // Attach a submit handler to the form so that when the form is submitted the content of the iframe gets decoded and |
1899 | 1945 | // copied over to the textarea |
1900 | 1946 | context.$textarea.closest( 'form' ).submit( function() { |
— | — | @@ -1902,35 +1948,17 @@ |
1903 | 1949 | } ); |
1904 | 1950 | } |
1905 | 1951 | |
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] ); |
1916 | 1962 | } |
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 | | - } |
1935 | 1963 | } |
1936 | 1964 | |
1937 | 1965 | // Store the context for next time, and support chaining |
— | — | @@ -2577,7 +2605,8 @@ |
2578 | 2606 | */ |
2579 | 2607 | create: function( context, config ) { |
2580 | 2608 | |
2581 | | - // check if text is selected |
| 2609 | + //initializations |
| 2610 | + |
2582 | 2611 | }, |
2583 | 2612 | |
2584 | 2613 | //template Model |
— | — | @@ -2604,12 +2633,16 @@ |
2605 | 2634 | var paramsByName = []; |
2606 | 2635 | var templateNameIndex = 0; |
2607 | 2636 | |
| 2637 | + //takes all template-specific characters, namely {|=} away if they're not particular to the |
| 2638 | + //template we're looking at |
2608 | 2639 | function markOffTemplates() { |
2609 | 2640 | sanatizedStr = wikitext.replace( /{{/, " " ); //get rid of first {{ with whitespace |
2610 | 2641 | endBraces = sanatizedStr.match( /}}\s*$/ ); //replace end |
2611 | 2642 | sanatizedStr = sanatizedStr.substring( 0, endBraces.index ) + " " + |
2612 | 2643 | sanatizedStr.substring( endBraces.index + 2 ); |
2613 | 2644 | |
| 2645 | + //match the open braces we just found with equivalent closing braces |
| 2646 | + //note, works for any level of braces |
2614 | 2647 | while ( sanatizedStr.indexOf( '{{' ) != -1 ) { |
2615 | 2648 | startIndex = sanatizedStr.indexOf('{{') + 1; |
2616 | 2649 | openBraces = 2; |
— | — | @@ -2636,6 +2669,7 @@ |
2637 | 2670 | |
2638 | 2671 | markOffTemplates(); |
2639 | 2672 | |
| 2673 | + //parse 1 param at a time |
2640 | 2674 | var doneParsing = false; |
2641 | 2675 | oldDivider = 0; |
2642 | 2676 | divider = sanatizedStr.indexOf( '|', oldDivider ); |
— | — | @@ -2656,6 +2690,8 @@ |
2657 | 2691 | |
2658 | 2692 | currentParamNumber = 0; |
2659 | 2693 | var valueEndIndex; |
| 2694 | + |
| 2695 | + //start looping over params |
2660 | 2696 | while ( !doneParsing ) { |
2661 | 2697 | currentParamNumber++; |
2662 | 2698 | oldDivider = divider; |
— | — | @@ -2795,10 +2831,15 @@ |
2796 | 2832 | }; |
2797 | 2833 | |
2798 | 2834 | //get a list of all param names (numbers for the anonymous ones) |
2799 | | - this.getAllParams = function() { |
| 2835 | + this.getAllParamNames = function() { |
2800 | 2836 | return paramsByName; |
2801 | 2837 | }; |
2802 | 2838 | |
| 2839 | + //get the initial params |
| 2840 | + this.getAllInitialParams = function(){ |
| 2841 | + return params; |
| 2842 | + } |
| 2843 | + |
2803 | 2844 | //get original template text |
2804 | 2845 | this.getOriginalText = function() { |
2805 | 2846 | return wikitext; |
— | — | @@ -3568,7 +3609,7 @@ |
3569 | 3610 | var label = $.wikiEditor.autoMsg( tool, 'label' ); |
3570 | 3611 | switch ( tool.type ) { |
3571 | 3612 | case 'button': |
3572 | | - var src = $.wikiEditor.getIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' ); |
| 3613 | + var src = $.wikiEditor.autoIcon( tool.icon, $.wikiEditor.imgPath + 'toolbar/' ); |
3573 | 3614 | $button = $( '<img />' ).attr( { |
3574 | 3615 | 'src' : src, |
3575 | 3616 | 'width' : 22, |
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js |
— | — | @@ -82,15 +82,15 @@ |
83 | 83 | $(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) |
84 | 84 | options.end=options.start;if(options.endContainer==undefined) |
85 | 85 | 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;} |
88 | 88 | 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;}}} |
89 | 89 | 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';} |
95 | 95 | break;case'mousedown':if(true){event.data.scope='division';}else{return false;} |
96 | 96 | break;default:event.data.scope='division';break;} |
97 | 97 | return true;}};context.fn={'trigger':function(name,event){if(typeof event=='undefined'){event={'type':'custom'};} |
— | — | @@ -99,25 +99,21 @@ |
100 | 100 | 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);} |
101 | 101 | event.preventDefault();return false;}).text($.wikiEditor.autoMsg(options,'title'))).appendTo(context.$tabs);} |
102 | 102 | 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,'<').replace(/>/g,'>'));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();} |
105 | 104 | 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();} |
106 | 105 | if(retval.text){retval=retval.text;}else if(retval.toString){retval=retval.toString();} |
107 | 106 | 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";}} |
111 | 109 | 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'));}} |
112 | 110 | 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()) |
117 | 114 | 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,'<').replace(/>/g,'>'));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]);}} |
122 | 118 | 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];} |
123 | 119 | 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) |
124 | 120 | 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 @@ |
184 | 180 | if(typeof params[rangeIndex]=='undefined'){return"";} |
185 | 181 | valueRange=ranges[params[rangeIndex].valueIndex];if(typeof valueRange.newVal=='undefined'||original){retVal=wikitext.substring(valueRange.begin,valueRange.end);}else{retVal=valueRange.newVal;} |
186 | 182 | 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;}} |
188 | 185 | 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()- |
189 | 186 | context.$ui.find('.tab-toc').outerHeight());}},fn:{create:function(context,config){if('$toc'in context.modules.toc){return;} |
190 | 187 | 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 @@ |
248 | 245 | 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>')} |
249 | 246 | 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);}}} |
250 | 247 | 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;});} |
252 | 249 | 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));}} |
253 | 250 | $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'));} |
254 | 251 | $.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)} |