gollum_editor 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,263 @@
1
+ /**
2
+ * gollum.dialog.js
3
+ *
4
+ * Used for dialogs. Duh.
5
+ *
6
+ */
7
+
8
+ (function($) {
9
+
10
+ var Dialog = {
11
+
12
+ debugOn: false,
13
+ markupCreated: false,
14
+ markup: '',
15
+
16
+ attachEvents: function( evtOK ) {
17
+ $('#gollum-dialog-action-ok').click(function( e ) {
18
+ Dialog.eventOK( e, evtOK );
19
+ });
20
+ $('#gollum-dialog-action-cancel').click( Dialog.eventCancel );
21
+ $('#gollum-dialog-dialog input[type="text"]').keydown(function( e ) {
22
+ if ( e.keyCode == 13 ) {
23
+ Dialog.eventOK( e, evtOK );
24
+ }
25
+ });
26
+ },
27
+
28
+ detachEvents: function() {
29
+ $('#gollum-dialog-action-ok').unbind('click');
30
+ $('#gollum-dialog-action-cancel').unbind('click');
31
+ },
32
+
33
+ createFieldMarkup: function( fieldArray ) {
34
+ var fieldMarkup = '<fieldset>';
35
+ for ( var i=0; i < fieldArray.length; i++ ) {
36
+ if ( typeof fieldArray[i] == 'object' ) {
37
+ fieldMarkup += '<div class="field">';
38
+ switch ( fieldArray[i].type ) {
39
+
40
+ // only text is supported for now
41
+ case 'text':
42
+ fieldMarkup += Dialog.createFieldText( fieldArray[i] );
43
+ break;
44
+
45
+ default:
46
+ break;
47
+
48
+ }
49
+ fieldMarkup += '</div>';
50
+ }
51
+
52
+ }
53
+ fieldMarkup += '</fieldset>';
54
+ return fieldMarkup;
55
+ },
56
+
57
+ createFieldText: function( fieldAttributes ) {
58
+ var html = '';
59
+
60
+ if ( fieldAttributes.name ) {
61
+ html += '<label';
62
+ if ( fieldAttributes.id ) {
63
+ html += ' for="' + fieldAttributes.name + '"';
64
+ }
65
+ html += '>' + fieldAttributes.name + '</label>';
66
+ }
67
+
68
+ html += '<input type="text"';
69
+
70
+ if ( fieldAttributes.id ) {
71
+ html += ' name="' + fieldAttributes.id + '"'
72
+ if ( fieldAttributes.type == 'code' ) {
73
+ html+= ' class="code"';
74
+ }
75
+ html += ' id="gollum-dialog-dialog-generated-field-' +
76
+ fieldAttributes.id + '">';
77
+ }
78
+
79
+ return html;
80
+ },
81
+
82
+ createMarkup: function( title, body ) {
83
+ Dialog.markupCreated = true;
84
+ if ($.facebox) {
85
+ return '<div id="gollum-dialog-dialog">' +
86
+ '<div id="gollum-dialog-dialog-title"><h4>' +
87
+ title +'</h4></div>' +
88
+ '<div id="gollum-dialog-dialog-body">' + body + '</div>' +
89
+ '<div id="gollum-dialog-dialog-buttons">' +
90
+ '<a href="#" title="Cancel" id="gollum-dialog-action-cancel" ' +
91
+ 'class="gollum-minibutton">Cancel</a>' +
92
+ '<a href="#" title="OK" id="gollum-dialog-action-ok" '+
93
+ 'class="gollum-minibutton">OK</a>' +
94
+ '</div>' +
95
+ '</div>';
96
+ } else {
97
+ return '<div id="gollum-dialog-dialog">' +
98
+ '<div id="gollum-dialog-dialog-inner">' +
99
+ '<div id="gollum-dialog-dialog-bg">' +
100
+ '<div id="gollum-dialog-dialog-title"><h4>' +
101
+ title +'</h4></div>' +
102
+ '<div id="gollum-dialog-dialog-body">' + body + '</div>' +
103
+ '<div id="gollum-dialog-dialog-buttons">' +
104
+ '<a href="#" title="Cancel" id="gollum-dialog-action-cancel" ' +
105
+ 'class="minibutton">Cancel</a>' +
106
+ '<a href="#" title="OK" id="gollum-dialog-action-ok" '+
107
+ 'class="minibutton">OK</a>' +
108
+ '</div>' +
109
+ '</div>' +
110
+ '</div>' +
111
+ '</div>';
112
+ }
113
+ },
114
+
115
+ eventCancel: function( e ) {
116
+ e.preventDefault();
117
+ debug('Cancelled dialog.');
118
+ Dialog.hide();
119
+ },
120
+
121
+ eventOK: function( e, evtOK ) {
122
+ e.preventDefault();
123
+
124
+ var results = [];
125
+ // get the results from each field and build them into the object
126
+ $('#gollum-dialog-dialog-body input').each(function() {
127
+ results[$(this).attr('name')] = $(this).val();
128
+ });
129
+
130
+ // pass them to evtOK if it exists (which it should)
131
+ if ( evtOK &&
132
+ typeof evtOK == 'function' ) {
133
+ evtOK( results );
134
+ }
135
+ Dialog.hide();
136
+ },
137
+
138
+ hide: function() {
139
+ if ( $.facebox ) {
140
+ Dialog.markupCreated = false;
141
+ $(document).trigger('close.facebox');
142
+ Dialog.detachEvents();
143
+ } else {
144
+ if ( $.browser.msie ) {
145
+ $('#gollum-dialog-dialog').hide().removeClass('active');
146
+ $('select').css('visibility', 'visible');
147
+ } else {
148
+ $('#gollum-dialog-dialog').animate({ opacity: 0 }, {
149
+ duration: 200,
150
+ complete: function() {
151
+ $('#gollum-dialog-dialog').removeClass('active');
152
+ }
153
+ });
154
+ }
155
+ }
156
+ },
157
+
158
+ init: function( argObject ) {
159
+ var title = '';
160
+ var body = '';
161
+
162
+ // bail out if necessary
163
+ if ( !argObject ||
164
+ typeof argObject != 'object' ) {
165
+ debug('Editor Dialog: Cannot init; invalid init object');
166
+ return;
167
+ }
168
+
169
+ if ( argObject.body && typeof argObject.body == 'string' ) {
170
+ body = '<p>' + argObject.body + '</p>';
171
+ }
172
+
173
+ // alright, build out fields
174
+ if ( argObject.fields && typeof argObject.fields == 'object' ) {
175
+ body += Dialog.createFieldMarkup( argObject.fields );
176
+ }
177
+
178
+ if ( argObject.title && typeof argObject.title == 'string' ) {
179
+ title = argObject.title;
180
+ }
181
+
182
+ if ( Dialog.markupCreated ) {
183
+ if ($.facebox) {
184
+ $(document).trigger('close.facebox');
185
+ } else {
186
+ $('#gollum-dialog-dialog').remove();
187
+ }
188
+ }
189
+
190
+ Dialog.markup = Dialog.createMarkup( title, body );
191
+
192
+ if ($.facebox) {
193
+ $(document).bind('reveal.facebox', function() {
194
+ if ( argObject.OK &&
195
+ typeof argObject.OK == 'function' ) {
196
+ Dialog.attachEvents( argObject.OK );
197
+ $($('#facebox input[type="text"]').get(0)).focus();
198
+ }
199
+ });
200
+ } else {
201
+ $('body').append( Dialog.markup );
202
+ if ( argObject.OK &&
203
+ typeof argObject.OK == 'function' ) {
204
+ Dialog.attachEvents( argObject.OK );
205
+ }
206
+ }
207
+
208
+ Dialog.show();
209
+ },
210
+
211
+ show: function() {
212
+ if ( !Dialog.markupCreated ) {
213
+ debug('Dialog: No markup to show. Please use init first.');
214
+ } else {
215
+ debug('Showing dialog');
216
+ if ($.facebox) {
217
+ $.facebox( Dialog.markup );
218
+ } else {
219
+ if ( $.browser.msie ) {
220
+ $('#gollum-dialog.dialog').addClass('active');
221
+ Dialog.position();
222
+ $('select').css('visibility', 'hidden');
223
+ } else {
224
+ $('#gollum-dialog.dialog').css('display', 'none');
225
+ $('#gollum-dialog-dialog').animate({ opacity: 0 }, {
226
+ duration: 0,
227
+ complete: function() {
228
+ $('#gollum-dialog-dialog').css('display', 'block');
229
+ Dialog.position(); // position this thing
230
+ $('#gollum-dialog-dialog').animate({ opacity: 1 }, {
231
+ duration: 500
232
+ });
233
+ }
234
+ });
235
+ }
236
+ }
237
+ }
238
+ },
239
+
240
+ position: function() {
241
+ var dialogHeight = $('#gollum-dialog-dialog-inner').height();
242
+ $('#gollum-dialog-dialog-inner')
243
+ .css('height', dialogHeight + 'px')
244
+ .css('margin-top', -1 * parseInt( dialogHeight / 2 ));
245
+ }
246
+ };
247
+
248
+ if ($.facebox) {
249
+ $(document).bind('reveal.facebox', function() {
250
+ $('#facebox img.close_image').remove();
251
+ });
252
+ }
253
+
254
+ var debug = function(m) {
255
+ if ( Dialog.debugOn
256
+ && typeof console != 'undefined' ) {
257
+ console.log( m );
258
+ }
259
+ };
260
+
261
+ $.GollumDialog = Dialog;
262
+
263
+ })(jQuery);
@@ -0,0 +1,1072 @@
1
+ /**
2
+ * gollum.editor.js
3
+ * A jQuery plugin that creates the Gollum Editor.
4
+ *
5
+ * Usage:
6
+ * $.GollumEditor(); on DOM ready.
7
+ */
8
+ (function($) {
9
+
10
+ // Editor options
11
+ var DefaultOptions = {
12
+ MarkupType: 'markdown',
13
+ EditorMode: 'code',
14
+ NewFile: false,
15
+ HasFunctionBar: true,
16
+ Debug: false,
17
+ NoDefinitionsFor: []
18
+ };
19
+ var ActiveOptions = {};
20
+
21
+ /**
22
+ * $.GollumEditor
23
+ *
24
+ * You don't need to do anything. Just run this on DOM ready.
25
+ */
26
+ $.GollumEditor = function( IncomingOptions ) {
27
+
28
+ ActiveOptions = $.extend( DefaultOptions, IncomingOptions );
29
+ debug('GollumEditor loading');
30
+
31
+ if ( EditorHas.baseEditorMarkup() ) {
32
+
33
+ if ( EditorHas.titleDisplayed() ) {
34
+ $('#gollum-editor-title-field').addClass('active');
35
+ }
36
+
37
+ if ( EditorHas.editSummaryMarkup() ) {
38
+ $.GollumEditor.Placeholder.add($('#gollum-editor-edit-summary input'));
39
+ $('#gollum-editor form[name="gollum-editor"]').submit(function( e ) {
40
+ e.preventDefault();
41
+ $.GollumEditor.Placeholder.clearAll();
42
+ debug('submitting');
43
+ $(this).unbind('submit');
44
+ $(this).submit();
45
+ });
46
+ }
47
+
48
+ if ( EditorHas.collapsibleInputs() ) {
49
+ $('#gollum-editor .collapsed a.button, ' +
50
+ '#gollum-editor .expanded a.button').click(function( e ) {
51
+ e.preventDefault();
52
+ $(this).parent().toggleClass('expanded');
53
+ $(this).parent().toggleClass('collapsed');
54
+ });
55
+ }
56
+
57
+ if ( EditorHas.previewButton() ) {
58
+ var formAction =
59
+ $('#gollum-editor #gollum-editor-preview').click(function() {
60
+ // make a dummy form, submit to new target window
61
+ // get form fields
62
+ var oldAction = $('#gollum-editor form').attr('action');
63
+ var $form = $($('#gollum-editor form').get(0));
64
+ $form.attr('action', this.href || '/preview');
65
+ $form.attr('target', '_blank');
66
+ $form.submit();
67
+
68
+
69
+ $form.attr('action', oldAction);
70
+ $form.removeAttr('target');
71
+ return false;
72
+ });
73
+ }
74
+
75
+ // Initialize the function bar by loading proper definitions
76
+ if ( EditorHas.functionBar() ) {
77
+
78
+ var htmlSetMarkupLang =
79
+ $('#gollum-editor-body').attr('data-markup-lang');
80
+
81
+ if ( htmlSetMarkupLang ) {
82
+ ActiveOptions.MarkupType = htmlSetMarkupLang;
83
+ }
84
+
85
+ // load language definition
86
+ LanguageDefinition.setActiveLanguage( ActiveOptions.MarkupType );
87
+ if ( EditorHas.formatSelector() ) {
88
+ FormatSelector.init(
89
+ $('#gollum-editor-format-selector select') );
90
+ }
91
+
92
+ if ( EditorHas.help() ) {
93
+ $('#gollum-editor-help').hide();
94
+ $('#gollum-editor-help').removeClass('jaws');
95
+ }
96
+
97
+ }
98
+ // EditorHas.functionBar
99
+ }
100
+ // EditorHas.baseEditorMarkup
101
+ };
102
+
103
+
104
+
105
+ /**
106
+ * $.GollumEditor.defineLanguage
107
+ * Defines a set of language actions that Gollum can use.
108
+ * Used by the definitions in langs/ to register language definitions.
109
+ */
110
+ $.GollumEditor.defineLanguage = function( language_name, languageObject ) {
111
+ if ( typeof languageObject == 'object' ) {
112
+ LanguageDefinition.define( language_name, languageObject );
113
+ } else {
114
+ debug('GollumEditor.defineLanguage: definition for ' + language_name +
115
+ ' is not an object');
116
+ }
117
+ };
118
+
119
+
120
+ /**
121
+ * debug
122
+ * Prints debug information to console.log if debug output is enabled.
123
+ *
124
+ * @param mixed Whatever you want to dump to console.log
125
+ * @return void
126
+ */
127
+ var debug = function(m) {
128
+ if ( ActiveOptions.Debug &&
129
+ typeof console != 'undefined' ) {
130
+ console.log( m );
131
+ }
132
+ };
133
+
134
+
135
+
136
+ /**
137
+ * LanguageDefinition
138
+ * Language definition file handler
139
+ * Loads language definition files as necessary.
140
+ */
141
+ var LanguageDefinition = {
142
+
143
+ _ACTIVE_LANG: '',
144
+ _LOADED_LANGS: [],
145
+ _LANG: {},
146
+
147
+ /**
148
+ * Defines a language
149
+ *
150
+ * @param name string The name of the language
151
+ * @param name object The definition object
152
+ */
153
+ define: function( name, definitionObject ) {
154
+ LanguageDefinition._ACTIVE_LANG = name;
155
+ LanguageDefinition._LOADED_LANGS.push( name );
156
+ if ( typeof $.GollumEditor.WikiLanguage == 'object' ) {
157
+ var definition = {};
158
+ $.extend(definition, $.GollumEditor.WikiLanguage, definitionObject);
159
+ LanguageDefinition._LANG[name] = definition;
160
+ } else {
161
+ LanguageDefinition._LANG[name] = definitionObject;
162
+ }
163
+ },
164
+
165
+ getActiveLanguage: function() {
166
+ return LanguageDefinition._ACTIVE_LANG;
167
+ },
168
+
169
+ setActiveLanguage: function( name ) {
170
+ if ( !LanguageDefinition.isLoadedFor(name) ) {
171
+ LanguageDefinition._ACTIVE_LANG = null;
172
+ LanguageDefinition.loadFor( name, function(x, t) {
173
+ if ( t != 'success' ) {
174
+ debug('Failed to load language definition for ' + name);
175
+ // well, fake it and turn everything off for this one
176
+ LanguageDefinition.define( name, {} );
177
+ }
178
+
179
+ // update features that rely on the language definition
180
+ if ( EditorHas.functionBar() ) {
181
+ FunctionBar.refresh();
182
+ }
183
+
184
+ if ( LanguageDefinition.isValid() && EditorHas.formatSelector() ) {
185
+ FormatSelector.updateSelected();
186
+ }
187
+
188
+ } );
189
+ } else {
190
+ LanguageDefinition._ACTIVE_LANG = name;
191
+ FunctionBar.refresh();
192
+ }
193
+ },
194
+
195
+
196
+ /**
197
+ * gets a definition object for a specified attribute
198
+ *
199
+ * @param string attr The specified attribute.
200
+ * @param string specified_lang The language to pull a definition for.
201
+ * @return object if exists, null otherwise
202
+ */
203
+ getDefinitionFor: function( attr, specified_lang ) {
204
+ if ( !specified_lang ) {
205
+ specified_lang = LanguageDefinition._ACTIVE_LANG;
206
+ }
207
+
208
+ if ( LanguageDefinition.isLoadedFor(specified_lang) &&
209
+ LanguageDefinition._LANG[specified_lang][attr] &&
210
+ typeof LanguageDefinition._LANG[specified_lang][attr] == 'object' ) {
211
+ return LanguageDefinition._LANG[specified_lang][attr];
212
+ }
213
+
214
+ return null;
215
+ },
216
+
217
+
218
+ /**
219
+ * loadFor
220
+ * Asynchronously loads a definition file for the current markup.
221
+ * Definition files are necessary to use the code editor.
222
+ *
223
+ * @param string markup_name The markup name you want to load
224
+ * @return void
225
+ */
226
+ loadFor: function( markup_name, on_complete ) {
227
+ // Keep us from hitting 404s on our site, check the definition blacklist
228
+ if ( ActiveOptions.NoDefinitionsFor.length ) {
229
+ for ( var i=0; i < ActiveOptions.NoDefinitionsFor.length; i++ ) {
230
+ if ( markup_name == ActiveOptions.NoDefinitionsFor[i] ) {
231
+ // we don't have this. get out.
232
+ if ( typeof on_complete == 'function' ) {
233
+ on_complete( null, 'error' );
234
+ return;
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ // attempt to load the definition for this language
241
+ var script_uri = '/assets/langs/' + markup_name + '.js';
242
+ $.ajax({
243
+ url: script_uri,
244
+ dataType: 'script',
245
+ complete: function( xhr, textStatus ) {
246
+ if ( typeof on_complete == 'function' ) {
247
+ on_complete( xhr, textStatus );
248
+ }
249
+ }
250
+ });
251
+ },
252
+
253
+
254
+ /**
255
+ * isLoadedFor
256
+ * Checks to see if a definition file has been loaded for the
257
+ * specified markup language.
258
+ *
259
+ * @param string markup_name The name of the markup.
260
+ * @return boolean
261
+ */
262
+ isLoadedFor: function( markup_name ) {
263
+ if ( LanguageDefinition._LOADED_LANGS.length === 0 ) {
264
+ return false;
265
+ }
266
+
267
+ for ( var i=0; i < LanguageDefinition._LOADED_LANGS.length; i++ ) {
268
+ if ( LanguageDefinition._LOADED_LANGS[i] == markup_name ) {
269
+ return true;
270
+ }
271
+ }
272
+ return false;
273
+ },
274
+
275
+ isValid: function() {
276
+ return ( LanguageDefinition._ACTIVE_LANG &&
277
+ typeof LanguageDefinition._LANG[LanguageDefinition._ACTIVE_LANG] ==
278
+ 'object' );
279
+ }
280
+
281
+ };
282
+
283
+
284
+ /**
285
+ * EditorHas
286
+ * Various conditionals to check what features of the Gollum Editor are
287
+ * active/operational.
288
+ */
289
+ var EditorHas = {
290
+
291
+
292
+ /**
293
+ * EditorHas.baseEditorMarkup
294
+ * True if the basic editor form is in place.
295
+ *
296
+ * @return boolean
297
+ */
298
+ baseEditorMarkup: function() {
299
+ return ( $('#gollum-editor').length &&
300
+ $('#gollum-editor-body').length );
301
+ },
302
+
303
+
304
+ /**
305
+ * EditorHas.collapsibleInputs
306
+ * True if the editor contains collapsible inputs for things like the
307
+ * sidebar or footer, false otherwise.
308
+ *
309
+ * @return boolean
310
+ */
311
+ collapsibleInputs: function() {
312
+ return $('#gollum-editor .collapsed, #gollum-editor .expanded').length;
313
+ },
314
+
315
+
316
+ /**
317
+ * EditorHas.formatSelector
318
+ * True if the editor has a format selector (for switching between
319
+ * language types), false otherwise.
320
+ *
321
+ * @return boolean
322
+ */
323
+ formatSelector: function() {
324
+ return $('#gollum-editor-format-selector select').length;
325
+ },
326
+
327
+
328
+ /**
329
+ * EditorHas.functionBar
330
+ * True if the Function Bar markup exists.
331
+ *
332
+ * @return boolean
333
+ */
334
+ functionBar: function() {
335
+ return ( ActiveOptions.HasFunctionBar &&
336
+ $('#gollum-editor-function-bar').length );
337
+ },
338
+
339
+
340
+ /**
341
+ * EditorHas.ff4Environment
342
+ * True if in a Firefox 4.0 Beta environment.
343
+ *
344
+ * @return boolean
345
+ */
346
+ ff4Environment: function() {
347
+ var ua = new RegExp(/Firefox\/4.0b/);
348
+ return ( ua.test( navigator.userAgent ) );
349
+ },
350
+
351
+
352
+ /**
353
+ * EditorHas.editSummaryMarkup
354
+ * True if the editor has a summary field (Gollum's commit message),
355
+ * false otherwise.
356
+ *
357
+ * @return boolean
358
+ */
359
+ editSummaryMarkup: function() {
360
+ return ( $('input#gollum-editor-message-field').length > 0 );
361
+ },
362
+
363
+
364
+ /**
365
+ * EditorHas.help
366
+ * True if the editor contains the inline help sector, false otherwise.
367
+ *
368
+ * @return boolean
369
+ */
370
+ help: function() {
371
+ return ( $('#gollum-editor #gollum-editor-help').length &&
372
+ $('#gollum-editor #function-help').length );
373
+ },
374
+
375
+
376
+ /**
377
+ * EditorHas.previewButton
378
+ * True if the editor has a preview button, false otherwise.
379
+ *
380
+ * @return boolean
381
+ */
382
+ previewButton: function() {
383
+ return ( $('#gollum-editor #gollum-editor-preview').length );
384
+ },
385
+
386
+
387
+ /**
388
+ * EditorHas.titleDisplayed
389
+ * True if the editor is displaying a title field, false otherwise.
390
+ *
391
+ * @return boolean
392
+ */
393
+ titleDisplayed: function() {
394
+ return ( ActiveOptions.NewFile );
395
+ }
396
+
397
+ };
398
+
399
+
400
+ /**
401
+ * FunctionBar
402
+ *
403
+ * Things the function bar does.
404
+ */
405
+ var FunctionBar = {
406
+
407
+ isActive: false,
408
+
409
+
410
+ /**
411
+ * FunctionBar.activate
412
+ * Activates the function bar, attaching all click events
413
+ * and displaying the bar.
414
+ *
415
+ */
416
+ activate: function() {
417
+ debug('Activating function bar');
418
+
419
+ // check these out
420
+ $('#gollum-editor-function-bar a.function-button').each(function() {
421
+ if ( LanguageDefinition.getDefinitionFor( $(this).attr('id') ) ) {
422
+ $(this).click( FunctionBar.evtFunctionButtonClick );
423
+ $(this).removeClass('disabled');
424
+ }
425
+ else if ( $(this).attr('id') != 'function-help' ) {
426
+ $(this).addClass('disabled');
427
+ }
428
+ });
429
+
430
+ // show bar as active
431
+ $('#gollum-editor-function-bar').addClass( 'active' );
432
+ FunctionBar.isActive = true;
433
+ },
434
+
435
+
436
+ deactivate: function() {
437
+ $('#gollum-editor-function-bar a.function-button').unbind('click');
438
+ $('#gollum-editor-function-bar').removeClass( 'active' );
439
+ FunctionBar.isActive = false;
440
+ },
441
+
442
+
443
+ /**
444
+ * FunctionBar.evtFunctionButtonClick
445
+ * Event handler for the function buttons. Traps the click and
446
+ * executes the proper language action.
447
+ *
448
+ * @param jQuery.Event jQuery event object.
449
+ */
450
+ evtFunctionButtonClick: function(e) {
451
+ e.preventDefault();
452
+ var def = LanguageDefinition.getDefinitionFor( $(this).attr('id') );
453
+ if ( typeof def == 'object' ) {
454
+ FunctionBar.executeAction( def );
455
+ }
456
+ },
457
+
458
+
459
+ /**
460
+ * FunctionBar.executeAction
461
+ * Executes a language-specific defined action for a function button.
462
+ *
463
+ */
464
+ executeAction: function( definitionObject ) {
465
+ // get the selected text from the textarea
466
+ var txt = $('#gollum-editor-body').val();
467
+ // hmm, I'm not sure this will work in a textarea
468
+ var selPos = FunctionBar
469
+ .getFieldSelectionPosition( $('#gollum-editor-body') );
470
+ var selText = FunctionBar.getFieldSelection( $('#gollum-editor-body') );
471
+ var repText = selText;
472
+ var reselect = true;
473
+ var cursor = null;
474
+
475
+ // execute a replacement function if one exists
476
+ if ( definitionObject.exec &&
477
+ typeof definitionObject.exec == 'function' ) {
478
+ definitionObject.exec( txt, selText, $('#gollum-editor-body') );
479
+ return;
480
+ }
481
+
482
+ // execute a search/replace if they exist
483
+ var searchExp = /([^\n]+)/gi;
484
+ if ( definitionObject.search &&
485
+ typeof definitionObject.search == 'object' ) {
486
+ debug('Replacing search Regex');
487
+ searchExp = null;
488
+ searchExp = new RegExp ( definitionObject.search );
489
+ debug( searchExp );
490
+ }
491
+ debug('repText is ' + '"' + repText + '"');
492
+ // replace text
493
+ if ( definitionObject.replace &&
494
+ typeof definitionObject.replace == 'string' ) {
495
+ debug('Running replacement - using ' + definitionObject.replace);
496
+ var rt = definitionObject.replace;
497
+ repText = repText.replace( searchExp, rt );
498
+ // remove backreferences
499
+ repText = repText.replace( /\$[\d]/g, '' );
500
+
501
+ if ( repText === '' ) {
502
+ debug('Search string is empty');
503
+
504
+ // find position of $1 - this is where we will place the cursor
505
+ cursor = rt.indexOf('$1');
506
+
507
+ // we have an empty string, so just remove backreferences
508
+ repText = rt.replace( /\$[\d]/g, '' );
509
+
510
+ // if the position of $1 doesn't exist, stick the cursor in
511
+ // the middle
512
+ if ( cursor == -1 ) {
513
+ cursor = Math.floor( rt.length / 2 );
514
+ }
515
+ }
516
+ }
517
+
518
+ // append if necessary
519
+ if ( definitionObject.append &&
520
+ typeof definitionObject.append == 'string' ) {
521
+ if ( repText == selText ) {
522
+ reselect = false;
523
+ }
524
+ repText += definitionObject.append;
525
+ }
526
+
527
+ if ( repText ) {
528
+ FunctionBar.replaceFieldSelection( $('#gollum-editor-body'),
529
+ repText, reselect, cursor );
530
+ }
531
+
532
+ },
533
+
534
+
535
+ /**
536
+ * getFieldSelectionPosition
537
+ * Retrieves the selection range for the textarea.
538
+ *
539
+ * @return object the .start and .end offsets in the string
540
+ */
541
+ getFieldSelectionPosition: function( $field ) {
542
+ if ($field.length) {
543
+ var start = 0, end = 0;
544
+ var el = $field.get(0);
545
+
546
+ if (typeof el.selectionStart == "number" &&
547
+ typeof el.selectionEnd == "number") {
548
+ start = el.selectionStart;
549
+ end = el.selectionEnd;
550
+ } else {
551
+ var range = document.selection.createRange();
552
+ var stored_range = range.duplicate();
553
+ stored_range.moveToElementText( el );
554
+ stored_range.setEndPoint( 'EndToEnd', range );
555
+ start = stored_range.text.length - range.text.length;
556
+ end = start + range.text.length;
557
+
558
+ // so, uh, we're close, but we need to search for line breaks and
559
+ // adjust the start/end points accordingly since IE counts them as
560
+ // 2 characters in TextRange.
561
+ var s = start;
562
+ var lb = 0;
563
+ var i;
564
+ debug('IE: start position is currently ' + s);
565
+ for ( i=0; i < s; i++ ) {
566
+ if ( el.value.charAt(i).match(/\r/) ) {
567
+ ++lb;
568
+ }
569
+ }
570
+
571
+ if ( lb ) {
572
+ debug('IE start: compensating for ' + lb + ' line breaks');
573
+ start = start - lb;
574
+ lb = 0;
575
+ }
576
+
577
+ var e = end;
578
+ for ( i=0; i < e; i++ ) {
579
+ if ( el.value.charAt(i).match(/\r/) ) {
580
+ ++lb;
581
+ }
582
+ }
583
+
584
+ if ( lb ) {
585
+ debug('IE end: compensating for ' + lb + ' line breaks');
586
+ end = end - lb;
587
+ }
588
+ }
589
+
590
+ return {
591
+ start: start,
592
+ end: end
593
+ };
594
+ } // end if ($field.length)
595
+ },
596
+
597
+
598
+ /**
599
+ * getFieldSelection
600
+ * Returns the currently selected substring of the textarea.
601
+ *
602
+ * @param jQuery A jQuery object for the textarea.
603
+ * @return string Selected string.
604
+ */
605
+ getFieldSelection: function( $field ) {
606
+ var selStr = '';
607
+ var selPos;
608
+
609
+ if ( $field.length ) {
610
+ selPos = FunctionBar.getFieldSelectionPosition( $field );
611
+ selStr = $field.val().substring( selPos.start, selPos.end );
612
+ debug('Selected: ' + selStr + ' (' + selPos.start + ', ' +
613
+ selPos.end + ')');
614
+ return selStr;
615
+ }
616
+ return false;
617
+ },
618
+
619
+
620
+ isShown: function() {
621
+ return ($('#gollum-editor-function-bar').is(':visible'));
622
+ },
623
+
624
+ refresh: function() {
625
+ if ( EditorHas.functionBar() ) {
626
+ debug('Refreshing function bar');
627
+ if ( LanguageDefinition.isValid() ) {
628
+ $('#gollum-editor-function-bar a.function-button').unbind('click');
629
+ FunctionBar.activate();
630
+ if ( Help ) {
631
+ Help.setActiveHelp( LanguageDefinition.getActiveLanguage() );
632
+ }
633
+ } else {
634
+ debug('Language definition is invalid.');
635
+ if ( FunctionBar.isShown() ) {
636
+ // deactivate the function bar; it's not gonna work now
637
+ FunctionBar.deactivate();
638
+ }
639
+ if ( Help.isShown() ) {
640
+ Help.hide();
641
+ }
642
+ }
643
+ }
644
+ },
645
+
646
+
647
+ /**
648
+ * replaceFieldSelection
649
+ * Replaces the currently selected substring of the textarea with
650
+ * a new string.
651
+ *
652
+ * @param jQuery A jQuery object for the textarea.
653
+ * @param string The string to replace the current selection with.
654
+ * @param boolean Reselect the new text range.
655
+ */
656
+ replaceFieldSelection: function( $field, replaceText, reselect, cursorOffset ) {
657
+ var selPos = FunctionBar.getFieldSelectionPosition( $field );
658
+ var fullStr = $field.val();
659
+ var selectNew = true;
660
+ if ( reselect === false) {
661
+ selectNew = false;
662
+ }
663
+
664
+ var scrollTop = null;
665
+ if ( $field[0].scrollTop ) {
666
+ scrollTop = $field[0].scrollTop;
667
+ }
668
+
669
+ $field.val( fullStr.substring(0, selPos.start) + replaceText +
670
+ fullStr.substring(selPos.end) );
671
+ $field[0].focus();
672
+
673
+ if ( selectNew ) {
674
+ if ( $field[0].setSelectionRange ) {
675
+ if ( cursorOffset ) {
676
+ $field[0].setSelectionRange(
677
+ selPos.start + cursorOffset,
678
+ selPos.start + cursorOffset
679
+ );
680
+ } else {
681
+ $field[0].setSelectionRange( selPos.start,
682
+ selPos.start + replaceText.length );
683
+ }
684
+ } else if ( $field[0].createTextRange ) {
685
+ var range = $field[0].createTextRange();
686
+ range.collapse( true );
687
+ if ( cursorOffset ) {
688
+ range.moveEnd( selPos.start + cursorOffset );
689
+ range.moveStart( selPos.start + cursorOffset );
690
+ } else {
691
+ range.moveEnd( 'character', selPos.start + replaceText.length );
692
+ range.moveStart( 'character', selPos.start );
693
+ }
694
+ range.select();
695
+ }
696
+ }
697
+
698
+ if ( scrollTop ) {
699
+ // this jumps sometimes in FF
700
+ $field[0].scrollTop = scrollTop;
701
+ }
702
+ }
703
+ };
704
+
705
+
706
+
707
+ /**
708
+ * FormatSelector
709
+ *
710
+ * Functions relating to the format selector (if it exists)
711
+ */
712
+ var FormatSelector = {
713
+
714
+ $_SELECTOR: null,
715
+
716
+ /**
717
+ * FormatSelector.evtChangeFormat
718
+ * Event handler for when a format has been changed by the format
719
+ * selector. Will automatically load a new language definition
720
+ * via JS if necessary.
721
+ *
722
+ * @return void
723
+ */
724
+ evtChangeFormat: function( e ) {
725
+ var newMarkup = $(this).val();
726
+ LanguageDefinition.setActiveLanguage( newMarkup );
727
+ },
728
+
729
+
730
+ /**
731
+ * FormatSelector.init
732
+ * Initializes the format selector.
733
+ *
734
+ * @return void
735
+ */
736
+ init: function( $sel ) {
737
+ debug('Initializing format selector');
738
+
739
+ // unbind events if init is being called twice for some reason
740
+ if ( FormatSelector.$_SELECTOR &&
741
+ typeof FormatSelector.$_SELECTOR == 'object' ) {
742
+ FormatSelector.$_SELECTOR.unbind( 'change' );
743
+ }
744
+
745
+ FormatSelector.$_SELECTOR = $sel;
746
+
747
+ // set format selector to the current language
748
+ FormatSelector.updateSelected();
749
+ FormatSelector.$_SELECTOR.change( FormatSelector.evtChangeFormat );
750
+ },
751
+
752
+
753
+ /**
754
+ * FormatSelector.update
755
+ */
756
+ updateSelected: function() {
757
+ var currentLang = LanguageDefinition.getActiveLanguage();
758
+ FormatSelector.$_SELECTOR.val( currentLang );
759
+ }
760
+
761
+ };
762
+
763
+
764
+
765
+ /**
766
+ * Help
767
+ *
768
+ * Functions that manage the display and loading of inline help files.
769
+ */
770
+ var Help = {
771
+
772
+ _ACTIVE_HELP: '',
773
+ _LOADED_HELP_LANGS: [],
774
+ _HELP: {},
775
+
776
+ /**
777
+ * Help.define
778
+ *
779
+ * Defines a new help context and enables the help function if it
780
+ * exists in the Gollum Function Bar.
781
+ *
782
+ * @param string name The name you're giving to this help context.
783
+ * Generally, this should match the language name.
784
+ * @param object definitionObject The definition object being loaded from a
785
+ * language / help definition file.
786
+ * @return void
787
+ */
788
+ define: function( name, definitionObject ) {
789
+ if ( Help.isValidHelpFormat( definitionObject ) ) {
790
+ debug('help is a valid format');
791
+
792
+ Help._ACTIVE_HELP_LANG = name;
793
+ Help._LOADED_HELP_LANGS.push( name );
794
+ Help._HELP[name] = definitionObject;
795
+
796
+ if ( $("#function-help").length ) {
797
+ if ( $('#function-help').hasClass('disabled') ) {
798
+ $('#function-help').removeClass('disabled');
799
+ }
800
+ $('#function-help').unbind('click');
801
+ $('#function-help').click( Help.evtHelpButtonClick );
802
+
803
+ // generate help menus
804
+ Help.generateHelpMenuFor( name );
805
+
806
+ if ( $('#gollum-editor-help').length &&
807
+ typeof $('#gollum-editor-help').attr('data-autodisplay') !== 'undefined' &&
808
+ $('#gollum-editor-help').attr('data-autodisplay') === 'true' ) {
809
+ Help.show();
810
+ }
811
+ }
812
+ } else {
813
+ if ( $('#function-help').length ) {
814
+ $('#function-help').addClass('disabled');
815
+ }
816
+ }
817
+ },
818
+
819
+ /**
820
+ * Help.generateHelpMenuFor
821
+ * Generates the markup for the main help menu given a context name.
822
+ *
823
+ * @param string name The context name.
824
+ * @return void
825
+ */
826
+ generateHelpMenuFor: function( name ) {
827
+ if ( !Help._HELP[name] ) {
828
+ debug('Help is not defined for ' + name.toString());
829
+ return false;
830
+ }
831
+ var helpData = Help._HELP[name];
832
+
833
+ // clear this shiz out
834
+ $('#gollum-editor-help-parent').html('');
835
+ $('#gollum-editor-help-list').html('');
836
+ $('#gollum-editor-help-content').html('');
837
+
838
+ // go go inefficient algorithm
839
+ for ( var i=0; i < helpData.length; i++ ) {
840
+ if ( typeof helpData[i] != 'object' ) {
841
+ break;
842
+ }
843
+
844
+ var $newLi = $('<li><a href="#" rel="' + i + '">' +
845
+ helpData[i].menuName + '</a></li>');
846
+ $('#gollum-editor-help-parent').append( $newLi );
847
+ if ( i === 0 ) {
848
+ // select on first run
849
+ $newLi.children('a').addClass('selected');
850
+ }
851
+ $newLi.children('a').click( Help.evtParentMenuClick );
852
+ }
853
+
854
+ // generate parent submenu on first run
855
+ Help.generateSubMenu( helpData[0], 0 );
856
+ $($('#gollum-editor-help-list li a').get(0)).click();
857
+
858
+ },
859
+
860
+ /**
861
+ * Help.generateSubMenu
862
+ * Generates the markup for the inline help sub-menu given the data
863
+ * object for the submenu and the array index to start at.
864
+ *
865
+ * @param object subData The data for the sub-menu.
866
+ * @param integer index The index clicked on (parent menu index).
867
+ * @return void
868
+ */
869
+ generateSubMenu: function( subData, index ) {
870
+ $('#gollum-editor-help-list').html('');
871
+ $('#gollum-editor-help-content').html('');
872
+ for ( var i=0; i < subData.content.length; i++ ) {
873
+ if ( typeof subData.content[i] != 'object' ) {
874
+ break;
875
+ }
876
+
877
+ var $subLi = $('<li><a href="#" rel="' + index + ':' + i + '">' +
878
+ subData.content[i].menuName + '</a></li>');
879
+
880
+
881
+ $('#gollum-editor-help-list').append( $subLi );
882
+ $subLi.children('a').click( Help.evtSubMenuClick );
883
+ }
884
+ },
885
+
886
+ hide: function() {
887
+ if ( $.browser.msie ) {
888
+ $('#gollum-editor-help').css('display', 'none');
889
+ } else {
890
+ $('#gollum-editor-help').animate({
891
+ opacity: 0
892
+ }, 200, function() {
893
+ $('#gollum-editor-help')
894
+ .animate({ height: 'hide' }, 200);
895
+ });
896
+ }
897
+ },
898
+
899
+ show: function() {
900
+ if ( $.browser.msie ) {
901
+ // bypass effects for internet explorer, since it does weird crap
902
+ // to text antialiasing with opacity animations
903
+ $('#gollum-editor-help').css('display', 'block');
904
+ } else {
905
+ $('#gollum-editor-help').animate({
906
+ height: 'show'
907
+ }, 200, function() {
908
+ $('#gollum-editor-help')
909
+ .animate({ opacity: 1 }, 300);
910
+ });
911
+ }
912
+ },
913
+
914
+ /**
915
+ * Help.showHelpFor
916
+ * Displays the actual help content given the two menu indexes, which are
917
+ * rendered in the rel="" attributes of the help menus
918
+ *
919
+ * @param integer index1 parent index
920
+ * @param integer index2 submenu index
921
+ * @return void
922
+ */
923
+ showHelpFor: function( index1, index2 ) {
924
+ var html =
925
+ Help._HELP[Help._ACTIVE_HELP_LANG][index1].content[index2].data;
926
+ $('#gollum-editor-help-content').html(html);
927
+ },
928
+
929
+ /**
930
+ * Help.isLoadedFor
931
+ * Returns true if help is loaded for a specific markup language,
932
+ * false otherwise.
933
+ *
934
+ * @param string name The name of the markup language.
935
+ * @return boolean
936
+ */
937
+ isLoadedFor: function( name ) {
938
+ for ( var i=0; i < Help._LOADED_HELP_LANGS.length; i++ ) {
939
+ if ( name == Help._LOADED_HELP_LANGS[i] ) {
940
+ return true;
941
+ }
942
+ }
943
+ return false;
944
+ },
945
+
946
+ isShown: function() {
947
+ return ($('#gollum-editor-help').is(':visible'));
948
+ },
949
+
950
+ /**
951
+ * Help.isValidHelpFormat
952
+ * Does a quick check to make sure that the help definition isn't in a
953
+ * completely messed-up format.
954
+ *
955
+ * @param object (Array) helpArr The help definition array.
956
+ * @return boolean
957
+ */
958
+ isValidHelpFormat: function( helpArr ) {
959
+ return ( typeof helpArr == 'object' &&
960
+ helpArr.length &&
961
+ typeof helpArr[0].menuName == 'string' &&
962
+ typeof helpArr[0].content == 'object' &&
963
+ helpArr[0].content.length );
964
+ },
965
+
966
+ /**
967
+ * Help.setActiveHelp
968
+ * Sets the active help definition to the one defined in the argument,
969
+ * re-rendering the help menu to match the new definition.
970
+ *
971
+ * @param string name The name of the help definition.
972
+ * @return void
973
+ */
974
+ setActiveHelp: function( name ) {
975
+ if ( !Help.isLoadedFor( name ) ) {
976
+ if ( $('#function-help').length ) {
977
+ $('#function-help').addClass('disabled');
978
+ }
979
+ if ( Help.isShown() ) {
980
+ Help.hide();
981
+ }
982
+ } else {
983
+ Help._ACTIVE_HELP_LANG = name;
984
+ if ( $("#function-help").length ) {
985
+ if ( $('#function-help').hasClass('disabled') ) {
986
+ $('#function-help').removeClass('disabled');
987
+ }
988
+ $('#function-help').unbind('click');
989
+ $('#function-help').click( Help.evtHelpButtonClick );
990
+ Help.generateHelpMenuFor( name );
991
+ }
992
+ }
993
+ },
994
+
995
+ /**
996
+ * Help.evtHelpButtonClick
997
+ * Event handler for clicking the help button in the function bar.
998
+ *
999
+ * @param jQuery.Event e The jQuery event object.
1000
+ * @return void
1001
+ */
1002
+ evtHelpButtonClick: function( e ) {
1003
+ e.preventDefault();
1004
+ if ( Help.isShown() ) {
1005
+ // turn off autodisplay if it's on
1006
+ if ( $('#gollum-editor-help').length &&
1007
+ $('#gollum-editor-help').attr('data-autodisplay') !== 'undefined' &&
1008
+ $('#gollum-editor-help').attr('data-autodisplay') === 'true' ) {
1009
+ $.post('/wiki/help?_method=delete');
1010
+ $('#gollum-editor-help').attr('data-autodisplay', '');
1011
+ }
1012
+ Help.hide(); }
1013
+ else { Help.show(); }
1014
+ },
1015
+
1016
+ /**
1017
+ * Help.evtParentMenuClick
1018
+ * Event handler for clicking on an item in the parent menu. Automatically
1019
+ * renders the submenu for the parent menu as well as the first result for
1020
+ * the actual plain text.
1021
+ *
1022
+ * @param jQuery.Event e The jQuery event object.
1023
+ * @return void
1024
+ */
1025
+ evtParentMenuClick: function( e ) {
1026
+ e.preventDefault();
1027
+ // short circuit if we've selected this already
1028
+ if ( $(this).hasClass('selected') ) { return; }
1029
+
1030
+ // populate from help data for this
1031
+ var helpIndex = $(this).attr('rel');
1032
+ var subData = Help._HELP[Help._ACTIVE_HELP_LANG][helpIndex];
1033
+
1034
+ $('#gollum-editor-help-parent li a').removeClass('selected');
1035
+ $(this).addClass('selected');
1036
+ Help.generateSubMenu( subData, helpIndex );
1037
+ $($('#gollum-editor-help-list li a').get(0)).click();
1038
+ },
1039
+
1040
+ /**
1041
+ * Help.evtSubMenuClick
1042
+ * Event handler for clicking an item in a help submenu. Renders the
1043
+ * appropriate text for the submenu link.
1044
+ *
1045
+ * @param jQuery.Event e The jQuery event object.
1046
+ * @return void
1047
+ */
1048
+ evtSubMenuClick: function( e ) {
1049
+ e.preventDefault();
1050
+ if ( $(this).hasClass('selected') ) { return; }
1051
+
1052
+ // split index rel data
1053
+ var rawIndex = $(this).attr('rel').split(':');
1054
+ $('#gollum-editor-help-list li a').removeClass('selected');
1055
+ $(this).addClass('selected');
1056
+ Help.showHelpFor( rawIndex[0], rawIndex[1] );
1057
+ }
1058
+ };
1059
+
1060
+ // Publicly-accessible function to Help.define
1061
+ $.GollumEditor.defineHelp = Help.define;
1062
+
1063
+ // Dialog exists as its own thing now
1064
+ $.GollumEditor.Dialog = $.GollumDialog;
1065
+ $.GollumEditor.replaceSelection = function( repText ) {
1066
+ FunctionBar.replaceFieldSelection( $('#gollum-editor-body'), repText );
1067
+ };
1068
+
1069
+ // Placeholder exists as its own thing now
1070
+ $.GollumEditor.Placeholder = $.GollumPlaceholder;
1071
+
1072
+ })(jQuery);