/**
* gollum.editor.js
* A jQuery plugin that creates the Gollum Editor.
*
* Usage:
* $.GollumEditor(); on DOM ready.
*/
(function($) {
// Editor options
var DefaultOptions = {
MarkupType: 'markdown',
EditorMode: 'code',
NewFile: false,
HasFunctionBar: true,
Debug: false,
NoDefinitionsFor: []
};
var ActiveOptions = {};
function isRTL(s){
var ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF'+'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
rtlDirCheck = new RegExp('^[^'+ltrChars+']*['+rtlChars+']');
return rtlDirCheck.test(s);
};
function switchRtl(b){
window.ace_editor.session.$bidiHandler.$isRtl = b;
window.ace_editor.session.$bidiHandler.updateBidiMap();
window.ace_editor.renderer.updateFull();
}
/**
* $.GollumEditor
*
* You don't need to do anything. Just run this on DOM ready.
*/
$.GollumEditor = function( IncomingOptions ) {
ActiveOptions = $.extend( DefaultOptions, IncomingOptions );
$('textarea[id="gollum-editor-body"]').each(function () {
var textarea = $(this);
var mode = textarea.attr("data-markup-lang") ? textarea.attr("data-markup-lang") : "markdown"
var editDiv = $('
', {
position: 'absolute',
height: textarea.height(),
'class': textarea.attr('class'),
'id': 'gollum-editor-body-ace'
}).insertAfter(textarea);
textarea.css('display', 'none');
// NOTE: This requires the page to have only one 'gollum-editor-body'.
var editor = ace.edit(editDiv[0], {rtlText: true});
window.ace_editor = editor;
editor.setTheme("ace/theme/tomorrow");
editor.setKeyboardHandler();
editor.renderer.setShowGutter(false);
editor.getSession().setUseWrapMode(true);
editor.getSession().setValue(textarea.val());
editor.getSession().setMode($.getEditorMode(mode));
editor.getSession().on('change', function(){
textarea.val(editor.getSession().getValue());
});
if (isRTL(editor.getSession().getLine(0))) {
switchRtl(true);
}
editor.commands.addCommand({
name: "showKeyboardShortcuts",
bindKey: {win: "Ctrl-Alt-h", mac: "Command-Alt-h"},
exec: function(editor) {
ace.config.loadModule("ace/ext/keybinding_menu", function(module) {
module.init(editor);
editor.showKeyboardShortcuts();
});
}
});
if ( ActiveOptions.commands ) {
$.each(ActiveOptions.commands, function ( index, cmd ) {
editor.commands.addCommand(cmd);
});
}
});
$("#gollum-editor-body-ace").resize(function(){
window.ace_editor.resize();
});
$("#wiki_format").change(function() {
var mode = $(this).val();
var editor = window.ace_editor;
window.ace_editor.getSession().setMode($.getEditorMode(mode));
FormatSelector.updateCommitMessage(mode);
editor.focus();
});
$("#keybinding").change(function() {
var mode = $(this).val();
var editor = window.ace_editor;
if (mode == "default") {
editor.setKeyboardHandler();
} else if (mode == "vim" || mode == "emacs") {
editor.setKeyboardHandler("ace/keyboard/" + mode);
} else {
editor.setKeyboardHandler();
}
editor.focus();
});
debug('GollumEditor loading');
if ( EditorHas.baseEditorMarkup() ) {
if ( EditorHas.titleDisplayed() ) {
$('#gollum-editor-title-field').addClass('active');
}
if ( EditorHas.editSummaryMarkup() ) {
$.GollumEditor.Placeholder.add($('#gollum-editor-edit-summary input'));
$('#gollum-editor form[name="gollum-editor"]').submit(function( e ) {
e.preventDefault();
// Do not clear default place holder text
// Updated home (markdown)
// $.GollumEditor.Placeholder.clearAll();
debug('submitting');
$(this).unbind('submit');
$(this).submit();
});
}
if ( EditorHas.collapsibleInputs() ) {
$('#gollum-editor .collapsed button.collapsable, ' +
'#gollum-editor .expanded button.collapsable').click(function( e ) {
e.preventDefault();
$(this).parent().toggleClass('expanded');
$(this).parent().toggleClass('collapsed');
buttons = $(this).parent().children("button");
hidden_button = buttons.filter(':hidden')[0];
shown_button = buttons.not(':hidden')[0];
hidden_button.hidden = false;
shown_button.hidden = true;
});
}
if ( EditorHas.previewButton() ) {
var formAction =
$('#gollum-editor #gollum-editor-preview').click(function() {
// make a dummy form, submit to new target window
// get form fields
var oldAction = $('#gollum-editor form').attr('action');
var $form = $($('#gollum-editor form').get(0));
$form.attr('action', this.href || routePath('preview'));
$form.attr('target', '_blank');
var paths = window.location.pathname.split('/');
$form.attr('page', paths[ paths.length - 1 ] || '')
$form.submit();
$form.attr('action', oldAction);
$form.removeAttr('target');
return false;
});
}
// Initialize the function bar by loading proper definitions
if ( EditorHas.functionBar() ) {
var htmlSetMarkupLang =
$('#gollum-editor-body').attr('data-markup-lang');
if ( htmlSetMarkupLang ) {
ActiveOptions.MarkupType = htmlSetMarkupLang;
}
// load language definition
LanguageDefinition.setActiveLanguage( ActiveOptions.MarkupType );
if ( EditorHas.formatSelector() ) {
FormatSelector.init(
$('#wiki_format') );
}
if ( EditorHas.dir() ) {
$('#function-text-direction').click(function( e ) {
e.preventDefault();
switchRtl(!window.ace_editor.session.$bidiHandler.$isRtl);
});
}
if ( EditorHas.help() ) {
$('#gollum-editor-help').hide();
$('#gollum-editor-help').removeClass('jaws');
}
} // EditorHas.functionBar
if (ActiveOptions['section'] && $.markupSupportsEditableSections ( ActiveOptions.MarkupType )) {
var sectionLine = $.findSection(ActiveOptions['section'], LanguageDefinition.getDefinitionFor('gollum-helpers'));
if (sectionLine) {
window.ace_editor.gotoLine(sectionLine+1, 0, animate=false);
window.ace_editor.scrollToLine(sectionLine, center = false, animate = false);
window.ace_editor.focus();
}
}
if ( EditorHas.dragDropUpload() ) {
var $editorBody = $('#gollum-editor-body-ace');
var editorBody = $('#gollum-editor-body-ace')[0];
editorBody.ondragover = function(e) {
$editorBody.addClass('dragging');
return false;
};
editorBody.ondragleave = function() {
$editorBody.removeClass('dragging');
return false;
};
editorBody.ondrop = function(e) {
debug("dropped file");
e.preventDefault();
$editorBody.removeClass('dragging').addClass('uploading');
var file = e.dataTransfer.files[0],
formData = new FormData();
formData.append('file', file);
$.ajax({
url: routePath('upload_file'),
data: formData,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(){
$editorBody.removeClass('uploading');
var ext = file.name.split('.').pop().toLowerCase()
var image_ext = ['jpg', 'jpeg', 'tif', 'tiff', 'png', 'gif', 'svg', 'bmp']
// Link directly to image files
if ((image_ext.indexOf(ext) > -1)) {
var text = '[[/' + uploadDest + '/' + file.name + ']]';
} else {
// Add file name to tag for non-image files, to avoid broken image thumbnail
var text = '[[' + file.name + '|/' + uploadDest + '/' + file.name + ']]';
}
window.ace_editor.insert(text);
},
error: function(data, textStatus, errorThrown) {
if (data.status == 409) {
alert('This file already exists.');
} else {
alert('Error uploading file: ' + textStatus + ' ' + errorThrown);
}
$editorBody.removeClass('uploading');
}
});
return false;
};
} // EditorHas.dragDropUpload
} // EditorHas.baseEditorMarkup
};
/**
* $.GollumEditor.defineLanguage
* Defines a set of language actions that Gollum can use.
* Used by the definitions in langs/ to register language definitions.
*/
$.GollumEditor.defineLanguage = function( language_name, languageObject ) {
if ( typeof languageObject == 'object' ) {
LanguageDefinition.define( language_name, languageObject );
} else {
debug('GollumEditor.defineLanguage: definition for ' + language_name +
' is not an object');
}
};
/**
* debug
* Prints debug information to console.log if debug output is enabled.
*
* @param mixed Whatever you want to dump to console.log
* @return void
*/
var debug = function(m) {
if ( ActiveOptions.Debug &&
typeof console != 'undefined' ) {
console.log( m );
}
};
/**
* LanguageDefinition
* Language definition file handler
* Loads language definition files as necessary.
*/
var LanguageDefinition = {
_ACTIVE_LANG: '',
_LOADED_LANGS: [],
_LANG: {},
/**
* Defines a language
*
* @param name string The name of the language
* @param name object The definition object
*/
define: function( name, definitionObject ) {
LanguageDefinition._ACTIVE_LANG = name;
LanguageDefinition._LOADED_LANGS.push( name );
if ( typeof $.GollumEditor.WikiLanguage == 'object' ) {
var definition = {};
$.extend(definition, $.GollumEditor.WikiLanguage, definitionObject);
LanguageDefinition._LANG[name] = definition;
} else {
LanguageDefinition._LANG[name] = definitionObject;
}
},
getActiveLanguage: function() {
return LanguageDefinition._ACTIVE_LANG;
},
setActiveLanguage: function( name ) {
// On first load _ACTIVE_LANG.length is 0 and evtChangeFormat isn't called.
if ( LanguageDefinition._ACTIVE_LANG != null && LanguageDefinition._ACTIVE_LANG.length <= 0 ) {
FormatSelector.updateCommitMessage( name );
}
if(LanguageDefinition.getHookFunctionFor("deactivate")) {
LanguageDefinition.getHookFunctionFor("deactivate")();
}
if ( !LanguageDefinition.isLoadedFor(name) ) {
LanguageDefinition._ACTIVE_LANG = null;
// update features that rely on the language definition
if ( EditorHas.functionBar() ) {
FunctionBar.refresh();
}
if ( LanguageDefinition.isValid() && EditorHas.formatSelector() ) {
FormatSelector.updateSelected();
}
if(LanguageDefinition.getHookFunctionFor("activate")) {
LanguageDefinition.getHookFunctionFor("activate")();
}
} else {
LanguageDefinition._ACTIVE_LANG = name;
FunctionBar.refresh();
if(LanguageDefinition.getHookFunctionFor("activate")) {
LanguageDefinition.getHookFunctionFor("activate")();
}
}
function hotkey( cmd ) {
return function () {
var def = LanguageDefinition.getDefinitionFor( cmd );
if ( typeof def == 'object' ) {
FunctionBar.executeAction( def );
}
};
}
window.ace_editor.commands.addCommand({
name: "header-1",
bindKey: {win: "Ctrl-1", mac: "Command-1"},
exec: hotkey('function-h1')
});
window.ace_editor.commands.addCommand({
name: "header-2",
bindKey: {win: "Ctrl-2", mac: "Command-2"},
exec: hotkey('function-h2')
});
window.ace_editor.commands.addCommand({
name: "header-3",
bindKey: {win: "Ctrl-3", mac: "Command-3"},
exec: hotkey('function-h3')
});
window.ace_editor.commands.addCommand({
name: "bold-text",
bindKey: {win: "Ctrl-b", mac: "Command-b"},
exec: hotkey('function-bold')
});
window.ace_editor.commands.addCommand({
name: "italic-text",
bindKey: {win: "Ctrl-i", mac: "Command-i"},
exec: hotkey('function-italic')
});
},
getHookFunctionFor: function(attr, specified_lang) {
if ( !specified_lang ) {
specified_lang = LanguageDefinition._ACTIVE_LANG;
}
if ( LanguageDefinition.isLoadedFor(specified_lang) &&
LanguageDefinition._LANG[specified_lang][attr] &&
typeof LanguageDefinition._LANG[specified_lang][attr] == 'function' ) {
return LanguageDefinition._LANG[specified_lang][attr];
}
return null;
},
/**
* gets a definition object for a specified attribute
*
* @param string attr The specified attribute.
* @param string specified_lang The language to pull a definition for.
* @return object if exists, null otherwise
*/
getDefinitionFor: function( attr, specified_lang ) {
if ( !specified_lang ) {
specified_lang = LanguageDefinition._ACTIVE_LANG;
}
if ( LanguageDefinition.isLoadedFor(specified_lang) &&
LanguageDefinition._LANG[specified_lang][attr] &&
typeof LanguageDefinition._LANG[specified_lang][attr] == 'object' ) {
return LanguageDefinition._LANG[specified_lang][attr];
}
return null;
},
/**
* isLoadedFor
* Checks to see if a definition file has been loaded for the
* specified markup language.
*
* @param string markup_name The name of the markup.
* @return boolean
*/
isLoadedFor: function( markup_name ) {
if ( LanguageDefinition._LOADED_LANGS.length === 0 ) {
return false;
}
for ( var i=0; i < LanguageDefinition._LOADED_LANGS.length; i++ ) {
if ( LanguageDefinition._LOADED_LANGS[i] == markup_name ) {
return true;
}
}
return false;
},
isValid: function() {
return ( LanguageDefinition._ACTIVE_LANG &&
typeof LanguageDefinition._LANG[LanguageDefinition._ACTIVE_LANG] ==
'object' );
}
};
/**
* EditorHas
* Various conditionals to check what features of the Gollum Editor are
* active/operational.
*/
var EditorHas = {
/**
* EditorHas.baseEditorMarkup
* True if the basic editor form is in place.
*
* @return boolean
*/
baseEditorMarkup: function() {
return ( $('#gollum-editor').length &&
$('#gollum-editor-body').length );
},
/**
* EditorHas.collapsibleInputs
* True if the editor contains collapsible inputs for things like the
* sidebar or footer, false otherwise.
*
* @return boolean
*/
collapsibleInputs: function() {
return $('#gollum-editor .collapsed, #gollum-editor .expanded').length;
},
/**
* EditorHas.formatSelector
* True if the editor has a format selector (for switching between
* language types), false otherwise.
*
* @return boolean
*/
formatSelector: function() {
return $('#wiki_format').length;
},
/**
* EditorHas.functionBar
* True if the Function Bar markup exists.
*
* @return boolean
*/
functionBar: function() {
return ( ActiveOptions.HasFunctionBar &&
$('#gollum-editor-function-bar').length );
},
/**
* EditorHas.ff4Environment
* True if in a Firefox 4.0 Beta environment.
*
* @return boolean
*/
ff4Environment: function() {
var ua = new RegExp(/Firefox\/4.0b/);
return ( ua.test( navigator.userAgent ) );
},
/**
* EditorHas.editSummaryMarkup
* True if the editor has a summary field (Gollum's commit message),
* false otherwise.
*
* @return boolean
*/
editSummaryMarkup: function() {
return ( $('input#gollum-editor-message-field').length > 0 );
},
/**
* EditorHas.help
* True if the editor contains the inline help sector, false otherwise.
*
* @return boolean
*/
help: function() {
return ( $('#gollum-editor #gollum-editor-help').length &&
$('#gollum-editor #function-help').length );
},
/**
* EditorHas.dir
* True if the editor contains the inline text direction button.
*
* @return boolean
*/
dir: function() {
return ($('#gollum-editor #function-text-direction').length );
},
/**
* EditorHas.previewButton
* True if the editor has a preview button, false otherwise.
*
* @return boolean
*/
previewButton: function() {
return ( $('#gollum-editor #gollum-editor-preview').length );
},
/**
* EditorHas.titleDisplayed
* True if the editor is displaying a title field, false otherwise.
*
* @return boolean
*/
titleDisplayed: function() {
return ( ActiveOptions.NewFile );
},
/**
* EditorHas.dragDropUpload
* True if the editor is supports drag and drop file uploads, false otherwise.
*
* @return boolean
*/
dragDropUpload: function() {
return $('#gollum-editor.uploads-allowed').length;
}
};
/**
* FunctionBar
*
* Things the function bar does.
*/
var FunctionBar = {
isActive: false,
/**
* FunctionBar.activate
* Activates the function bar, attaching all click events
* and displaying the bar.
*
*/
activate: function() {
debug('Activating function bar');
// check these out
$('#gollum-editor-function-bar button.function-button').each(function() {
if ( LanguageDefinition.getDefinitionFor( $(this).attr('id') ) ) {
$(this).click( FunctionBar.evtFunctionButtonClick );
$(this).attr('disabled', false);
}
else if ( !['function-help', 'function-text-direction'].includes( $(this).attr('id') ) ) {
$(this).attr('disabled', true);
}
});
// show bar as active
$('#gollum-editor-function-bar').addClass( 'active' );
FunctionBar.isActive = true;
},
deactivate: function() {
// When we switch markup language, unbind all buttons *except* for the text direction (LTR/RTL) switiching button
$('#gollum-editor-function-bar button.function-button').not('#function-text-direction').unbind('click');
$('#gollum-editor-function-bar').removeClass( 'active' );
FunctionBar.isActive = false;
},
/**
* FunctionBar.evtFunctionButtonClick
* Event handler for the function buttons. Traps the click and
* executes the proper language action.
*
* @param jQuery.Event jQuery event object.
*/
evtFunctionButtonClick: function(e) {
e.preventDefault();
var def = LanguageDefinition.getDefinitionFor( $(this).attr('id') );
if ( typeof def == 'object' ) {
FunctionBar.executeAction( def );
}
},
/**
* FunctionBar.executeAction
* Executes a language-specific defined action for a function button.
*
*/
executeAction: function( definitionObject ) {
// get the selected text from the textarea
var editor = window.ace_editor;
var txt = editor.getValue();
var breakBefore = false;
var breakAfter = false;
var selRange = editor.getSelectionRange();
var selText = editor.getSelectedText();
var selTextLength = selText.length;
var wholeLine = false;
if (selText == '' ) {
if (definitionObject.whole_line && definitionObject.whole_line == true) {
wholeLine = true;
var line = selRange.start.row;
selText = editor.session.getLine(line);
var Range = ace.require('ace/range').Range;
selRange = new Range(line, 0, line, selText.length);
} else if (definitionObject.break_line && definitionObject.break_line == true) {
breakBefore = true;
breakAfter = true;
}
} else if (definitionObject.break_line && definitionObject.break_line == true) {
breakBefore = true;
breakAfter = true;
}
var repText = selText;
// execute a replacement function if one exists
if ( definitionObject.exec &&
typeof definitionObject.exec == 'function' ) {
// FIXME: In case of Ace Editor, the function shall not operate on
// the text area, but on the editor. But since a lot functions
// does not use the third args, this works in these cases. But
// we shall fix it.
definitionObject.exec( txt, selText, $('#gollum-editor-body'), selRange );
return;
}
// execute a search/replace if they exist
var searchExp = /([^\n]+)/gi;
if ( definitionObject.search &&
typeof definitionObject.search == 'object' ) {
debug('Replacing search Regex');
searchExp = null;
searchExp = new RegExp ( definitionObject.search );
debug( searchExp );
}
debug('repText is ' + '"' + repText + '"');
// replace text
if ( definitionObject.replace &&
typeof definitionObject.replace == 'string' ) {
debug('Running replacement - using ' + definitionObject.replace);
var rt = definitionObject.replace;
repText = escape( repText );
repText = repText.replace( searchExp, rt );
// remove backreferences
repText = repText.replace( /\$[\d]/g, '' );
repText = unescape( repText );
if ( repText === '' ) {
debug('Search string is empty');
repText = rt.replace( /\$[\d]/g, '' );
}
var cursorOffset = undefined;
if (wholeLine == false) {
// find position of $1 - this is where we will place the cursor
repPosition = rt.indexOf('$1');
// if the position of $1 doesn't exist, stick the cursor in
// the middle
if ( repPosition == -1 ) {
repPosition = Math.floor( rt.length / 2 );
}
var tempString = rt.substring(0, repPosition);
var verticalOffset = tempString.split('\n').length - 1;
var horizontalOffset = repPosition;
if ( verticalOffset > 0 ) {
horizontalOffset = horizontalOffset - tempString.lastIndexOf('\n')
}
horizontalOffset = horizontalOffset + selTextLength;
cursorOffset = [verticalOffset, horizontalOffset]
}
}
// append if necessary
if ( definitionObject.append &&
typeof definitionObject.append == 'string' ) {
repText += definitionObject.append;
}
if ( repText ) {
$.GollumEditor.replaceSelection(repText, breakBefore, breakAfter, selRange, cursorOffset)
}
},
isShown: function() {
return ($('#gollum-editor-function-bar').is(':visible'));
},
refresh: function() {
if ( EditorHas.functionBar() ) {
debug('Refreshing function bar');
if ( LanguageDefinition.isValid() ) {
FunctionBar.deactivate();
FunctionBar.activate();
if ( Help ) {
Help.setActiveHelp( LanguageDefinition.getActiveLanguage() );
}
} else {
debug('Language definition is invalid.');
if ( FunctionBar.isShown() ) {
// deactivate the function bar; it's not gonna work now
FunctionBar.deactivate();
}
if ( Help.isShown() ) {
Help.hide();
}
}
}
}
};
/**
* FormatSelector
*
* Functions relating to the format selector (if it exists)
*/
var FormatSelector = {
$_SELECTOR: null,
/**
* FormatSelector.evtChangeFormat
* Event handler for when a format has been changed by the format
* selector. Will automatically load a new language definition
* via JS if necessary.
*
* @return void
*/
evtChangeFormat: function( e ) {
var newMarkup = $(this).val();
FormatSelector.updateCommitMessage( newMarkup );
LanguageDefinition.setActiveLanguage( newMarkup );
},
updateCommitMessage: function( newMarkup ) {
var msg = document.getElementById( "gollum-editor-message-field" );
var val = msg.value;
// Must start with created or updated.
if (/^(?:created|updated)/i.test(val)) {
msg.value = val.replace( /\([^\)]*\)$/, "(" + newMarkup + ")" );
}
},
/**
* FormatSelector.init
* Initializes the format selector.
*
* @return void
*/
init: function( $sel ) {
debug('Initializing format selector');
// unbind events if init is being called twice for some reason
if ( FormatSelector.$_SELECTOR &&
typeof FormatSelector.$_SELECTOR == 'object' ) {
FormatSelector.$_SELECTOR.unbind( 'change' );
}
FormatSelector.$_SELECTOR = $sel;
// set format selector to the current language
FormatSelector.updateSelected();
FormatSelector.$_SELECTOR.change( FormatSelector.evtChangeFormat );
},
/**
* FormatSelector.update
*/
updateSelected: function() {
var currentLang = LanguageDefinition.getActiveLanguage();
FormatSelector.$_SELECTOR.val( currentLang );
}
};
/**
* Help
*
* Functions that manage the display and loading of inline help files.
*/
var Help = {
_ACTIVE_HELP: '',
_LOADED_HELP_LANGS: [],
_HELP: {},
/**
* Help.define
*
* Defines a new help context and enables the help function if it
* exists in the Gollum Function Bar.
*
* @param string name The name you're giving to this help context.
* Generally, this should match the language name.
* @param object definitionObject The definition object being loaded from a
* language / help definition file.
* @return void
*/
define: function( name, definitionObject ) {
if ( Help.isValidHelpFormat( definitionObject ) ) {
debug('help is a valid format');
Help._ACTIVE_HELP_LANG = name;
Help._LOADED_HELP_LANGS.push( name );
Help._HELP[name] = definitionObject;
if ( $("#function-help").length ) {
if ( $('#function-help').attr('disabled') ) {
$('#function-help').attr('disabled', false);
}
$('#function-help').unbind('click');
$('#function-help').click( Help.evtHelpButtonClick );
// generate help menus
Help.generateHelpMenuFor( name );
if ( $('#gollum-editor-help').length &&
typeof $('#gollum-editor-help').attr('data-autodisplay') !== 'undefined' &&
$('#gollum-editor-help').attr('data-autodisplay') === 'true' ) {
Help.show();
}
}
} else {
if ( $('#function-help').length ) {
$('#function-help').attr('disabled', true);
}
}
},
/**
* Help.generateHelpMenuFor
* Generates the markup for the main help menu given a context name.
*
* @param string name The context name.
* @return void
*/
generateHelpMenuFor: function( name ) {
if ( !Help._HELP[name] ) {
debug('Help is not defined for ' + name.toString());
return false;
}
var helpData = Help._HELP[name];
// clear this shiz out
$('#gollum-editor-help-parent').html('');
$('#gollum-editor-help-list').html('');
$('#gollum-editor-help-content').html('');
// go go inefficient algorithm
for ( var i=0; i < helpData.length; i++ ) {
if ( typeof helpData[i] != 'object' ) {
break;
}
var $newLi = $('
' +
helpData[i].menuName + '');
$('#gollum-editor-help-parent').append( $newLi );
if ( i === 0 ) {
// select on first run
$newLi.children('a').addClass('selected');
}
$newLi.children('a').click( Help.evtParentMenuClick );
}
// generate parent submenu on first run
Help.generateSubMenu( helpData[0], 0 );
$($('#gollum-editor-help-list li a').get(0)).click();
},
/**
* Help.generateSubMenu
* Generates the markup for the inline help sub-menu given the data
* object for the submenu and the array index to start at.
*
* @param object subData The data for the sub-menu.
* @param integer index The index clicked on (parent menu index).
* @return void
*/
generateSubMenu: function( subData, index ) {
$('#gollum-editor-help-list').html('');
$('#gollum-editor-help-content').html('');
for ( var i=0; i < subData.content.length; i++ ) {
if ( typeof subData.content[i] != 'object' ) {
break;
}
var $subLi = $('
' +
subData.content[i].menuName + '');
$('#gollum-editor-help-list').append( $subLi );
$subLi.children('a').click( Help.evtSubMenuClick );
}
},
hide: function() {
if ( $.browser.msie ) {
$('#gollum-editor-help').css('display', 'none');
} else {
$('#gollum-editor-help').animate({
opacity: 0
}, 200, function() {
$('#gollum-editor-help')
.animate({ height: 'hide' }, 200);
});
}
},
show: function() {
if ( $.browser.msie ) {
// bypass effects for internet explorer, since it does weird crap
// to text antialiasing with opacity animations
$('#gollum-editor-help').css('display', 'block');
} else {
$('#gollum-editor-help').animate({
height: 'show'
}, 200, function() {
$('#gollum-editor-help')
.animate({ opacity: 1 }, 300);
});
}
},
/**
* Help.showHelpFor
* Displays the actual help content given the two menu indexes, which are
* rendered in the rel="" attributes of the help menus
*
* @param integer index1 parent index
* @param integer index2 submenu index
* @return void
*/
showHelpFor: function( index1, index2 ) {
var html =
Help._HELP[Help._ACTIVE_HELP_LANG][index1].content[index2].data;
$('#gollum-editor-help-content').html(html);
},
/**
* Help.isLoadedFor
* Returns true if help is loaded for a specific markup language,
* false otherwise.
*
* @param string name The name of the markup language.
* @return boolean
*/
isLoadedFor: function( name ) {
for ( var i=0; i < Help._LOADED_HELP_LANGS.length; i++ ) {
if ( name == Help._LOADED_HELP_LANGS[i] ) {
return true;
}
}
return false;
},
isShown: function() {
return ($('#gollum-editor-help').is(':visible'));
},
/**
* Help.isValidHelpFormat
* Does a quick check to make sure that the help definition isn't in a
* completely messed-up format.
*
* @param object (Array) helpArr The help definition array.
* @return boolean
*/
isValidHelpFormat: function( helpArr ) {
return ( typeof helpArr == 'object' &&
helpArr.length &&
typeof helpArr[0].menuName == 'string' &&
typeof helpArr[0].content == 'object' &&
helpArr[0].content.length );
},
/**
* Help.setActiveHelp
* Sets the active help definition to the one defined in the argument,
* re-rendering the help menu to match the new definition.
*
* @param string name The name of the help definition.
* @return void
*/
setActiveHelp: function( name ) {
if ( !Help.isLoadedFor( name ) ) {
if ( $('#function-help').length ) {
$('#function-help').attr('disabled', true);
}
if ( Help.isShown() ) {
Help.hide();
}
} else {
Help._ACTIVE_HELP_LANG = name;
if ( $("#function-help").length ) {
if ( $('#function-help').attr('disabled') ) {
$('#function-help').attr('disabled', false);
}
$('#function-help').unbind('click');
$('#function-help').click( Help.evtHelpButtonClick );
Help.generateHelpMenuFor( name );
}
}
},
/**
* Help.evtHelpButtonClick
* Event handler for clicking the help button in the function bar.
*
* @param jQuery.Event e The jQuery event object.
* @return void
*/
evtHelpButtonClick: function( e ) {
e.preventDefault();
if ( Help.isShown() ) {
// turn off autodisplay if it's on
if ( $('#gollum-editor-help').length &&
$('#gollum-editor-help').attr('data-autodisplay') !== 'undefined' &&
$('#gollum-editor-help').attr('data-autodisplay') === 'true' ) {
$.post('/wiki/help?_method=delete');
$('#gollum-editor-help').attr('data-autodisplay', '');
}
Help.hide(); }
else { Help.show(); }
},
/**
* Help.evtParentMenuClick
* Event handler for clicking on an item in the parent menu. Automatically
* renders the submenu for the parent menu as well as the first result for
* the actual plain text.
*
* @param jQuery.Event e The jQuery event object.
* @return void
*/
evtParentMenuClick: function( e ) {
e.preventDefault();
// short circuit if we've selected this already
if ( $(this).hasClass('selected') ) { return; }
// populate from help data for this
var helpIndex = $(this).attr('rel');
var subData = Help._HELP[Help._ACTIVE_HELP_LANG][helpIndex];
$('#gollum-editor-help-parent li a').removeClass('selected');
$(this).addClass('selected');
Help.generateSubMenu( subData, helpIndex );
$($('#gollum-editor-help-list li a').get(0)).click();
},
/**
* Help.evtSubMenuClick
* Event handler for clicking an item in a help submenu. Renders the
* appropriate text for the submenu link.
*
* @param jQuery.Event e The jQuery event object.
* @return void
*/
evtSubMenuClick: function( e ) {
e.preventDefault();
if ( $(this).hasClass('selected') ) { return; }
// split index rel data
var rawIndex = $(this).attr('rel').split(':');
$('#gollum-editor-help-list li a').removeClass('selected');
$(this).addClass('selected');
Help.showHelpFor( rawIndex[0], rawIndex[1] );
}
};
// Publicly-accessible function to Help.define
$.GollumEditor.defineHelp = Help.define;
// Dialog exists as its own thing now
$.GollumEditor.Dialog = $.GollumDialog;
$.GollumEditor.replaceSelection = function( repText, breakBefore, breakAfter, selRange, cursorOffset ) {
var editor = window.ace_editor;
var newlinesPrefixed = 0;
if (selRange == undefined ) {
var selRange = editor.selection.getRange();
}
if ( breakBefore == true ) {
var previousLine = editor.session.doc.getLine(selRange.start.row-1);
if (selRange.start.column > 0) {
repText = "\n\n" + repText
newlinesPrefixed = 2;
}
else if (previousLine != "") {
repText = "\n" + repText
newlinesPrefixed = 1;
}
}
if ( breakAfter == true ) {
var nextLine = editor.session.doc.getLine(selRange.end.row+1);
if (selRange.end.column < editor.session.doc.getLine(selRange.end.row).length) {
repText = repText + "\n\n"
}
else if (nextLine != "") {
repText = repText + "\n"
}
}
editor.session.replace(selRange, repText);
if (cursorOffset != undefined) {
totalVerticalOffset = cursorOffset[0] + newlinesPrefixed + selRange.start.row;
totalHorizontalOffset = cursorOffset[1] + selRange.start.column;
editor.navigateTo(totalVerticalOffset, totalHorizontalOffset);
}
editor.focus();
};
// Placeholder exists as its own thing now
$.GollumEditor.Placeholder = $.GollumPlaceholder;
})(jQuery);