[WIP] Improve editor buttons: insert markup at the correct position. Resolves #504 (#1348)

* Improve editor buttons: insert markup at the correct position

* Center cursor after replacing text.

* Reimplement keyboard shortcuts for ace.

* Add break_line and whole_line to all language definitions

* Implement a better unordered list function.
This commit is contained in:
Dawa Ometto
2018-12-28 23:23:06 +01:00
committed by GitHub
parent 073137af7d
commit 2aea55a92c
8 changed files with 271 additions and 251 deletions
@@ -335,29 +335,6 @@
if(LanguageDefinition.getHookFunctionFor("activate")) {
LanguageDefinition.getHookFunctionFor("activate")();
}
function hotkey( e, cmd ) {
e.preventDefault();
var def = LanguageDefinition.getDefinitionFor( cmd );
if ( typeof def == 'object' ) {
FunctionBar.executeAction( def );
}
// Prevent bubbling of hotkey.
return false;
}
Mousetrap.bind('mod+1', function( e ){ hotkey( e, 'function-h1' ); });
Mousetrap.bind('mod+2', function( e ){ hotkey( e, 'function-h2' ); });
Mousetrap.bind('mod+3', function( e ){ hotkey( e, 'function-h3' ); });
Mousetrap.bind('mod+b', function( e ){ hotkey( e, 'function-bold' ); });
Mousetrap.bind('mod+i', function( e ){ hotkey( e, 'function-italic' ); });
Mousetrap.bind('mod+s', function( e ){
e.preventDefault();
$("#gollum-editor-submit").trigger("click");
return false;
});
} );
} else {
LanguageDefinition._ACTIVE_LANG = name;
@@ -367,6 +344,41 @@
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) {
@@ -666,10 +678,29 @@
// 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;
var reselect = true;
var cursor = null;
// execute a replacement function if one exists
if ( definitionObject.exec &&
@@ -703,126 +734,46 @@
// remove backreferences
repText = repText.replace( /\$[\d]/g, '' );
repText = unescape( repText );
if ( repText === '' ) {
debug('Search string is empty');
// find position of $1 - this is where we will place the cursor
cursor = rt.indexOf('$1');
// we have an empty string, so just remove backreferences
repText = rt.replace( /\$[\d]/g, '' );
}
// if the position of $1 doesn't exist, stick the cursor in
// the middle
if ( cursor == -1 ) {
cursor = Math.floor( rt.length / 2 );
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' ) {
if ( repText == selText ) {
reselect = false;
}
repText += definitionObject.append;
}
if ( repText ) {
editor.session.replace(editor.selection.getRange(), repText);
editor.focus();
$.GollumEditor.replaceSelection(repText, breakBefore, breakAfter, selRange, cursorOffset)
}
},
/**
* getFieldSelectionPosition
* Retrieves the selection range for the textarea.
*
* @return object the .start and .end offsets in the string
*/
getFieldSelectionPosition: function( $field ) {
if ($field.length) {
var start = 0, end = 0;
var el = $field.get(0);
if (typeof el.selectionStart == "number" &&
typeof el.selectionEnd == "number") {
start = el.selectionStart;
end = el.selectionEnd;
} else {
var range = document.selection.createRange();
var stored_range = range.duplicate();
stored_range.moveToElementText( el );
stored_range.setEndPoint( 'EndToEnd', range );
start = stored_range.text.length - range.text.length;
end = start + range.text.length;
// so, uh, we're close, but we need to search for line breaks and
// adjust the start/end points accordingly since IE counts them as
// 2 characters in TextRange.
var s = start;
var lb = 0;
var i;
debug('IE: start position is currently ' + s);
for ( i=0; i < s; i++ ) {
if ( el.value.charAt(i).match(/\r/) ) {
++lb;
}
}
if ( lb ) {
debug('IE start: compensating for ' + lb + ' line breaks');
start = start - lb;
lb = 0;
}
var e = end;
for ( i=0; i < e; i++ ) {
if ( el.value.charAt(i).match(/\r/) ) {
++lb;
}
}
if ( lb ) {
debug('IE end: compensating for ' + lb + ' line breaks');
end = end - lb;
}
}
return {
start: start,
end: end
};
} // end if ($field.length)
},
/**
* getFieldSelection
* Returns the currently selected substring of the textarea.
*
* @param jQuery A jQuery object for the textarea.
* @return string Selected string.
*/
getFieldSelection: function( $field ) {
var selStr = '';
var selPos;
if ( $field.length ) {
selPos = FunctionBar.getFieldSelectionPosition( $field );
selStr = $field.val().substring( selPos.start, selPos.end );
debug('Selected: ' + selStr + ' (' + selPos.start + ', ' +
selPos.end + ')');
return selStr;
}
return false;
},
isShown: function() {
return ($('#gollum-editor-function-bar').is(':visible'));
},
@@ -847,67 +798,8 @@
}
}
}
},
/**
* replaceFieldSelection
* Replaces the currently selected substring of the textarea with
* a new string.
*
* @param jQuery A jQuery object for the textarea.
* @param string The string to replace the current selection with.
* @param boolean Reselect the new text range.
*/
replaceFieldSelection: function( $field, replaceText, reselect, cursorOffset ) {
var selPos = FunctionBar.getFieldSelectionPosition( $field );
var fullStr = $field.val();
var selectNew = true;
if ( reselect === false) {
selectNew = false;
}
var scrollTop = null;
if ( $field[0].scrollTop ) {
scrollTop = $field[0].scrollTop;
}
$field.val( fullStr.substring(0, selPos.start) + replaceText +
fullStr.substring(selPos.end) );
$field[0].focus();
if ( selectNew ) {
if ( $field[0].setSelectionRange ) {
if ( cursorOffset ) {
$field[0].setSelectionRange(
selPos.start + cursorOffset,
selPos.start + cursorOffset
);
} else {
$field[0].setSelectionRange( selPos.start,
selPos.start + replaceText.length );
}
} else if ( $field[0].createTextRange ) {
var range = $field[0].createTextRange();
range.collapse( true );
if ( cursorOffset ) {
range.moveEnd( selPos.start + cursorOffset );
range.moveStart( selPos.start + cursorOffset );
} else {
range.moveEnd( 'character', selPos.start + replaceText.length );
range.moveStart( 'character', selPos.start );
}
range.select();
}
}
if ( scrollTop ) {
// this jumps sometimes in FF
$field[0].scrollTop = scrollTop;
}
}
};
};
/**
@@ -1277,9 +1169,41 @@
// Dialog exists as its own thing now
$.GollumEditor.Dialog = $.GollumDialog;
$.GollumEditor.replaceSelection = function( repText ) {
$.GollumEditor.replaceSelection = function( repText, breakBefore, breakAfter, selRange, cursorOffset ) {
var editor = window.ace_editor;
editor.session.replace(editor.selection.getRange(), repText);
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();
};
@@ -23,33 +23,54 @@ var AsciiDoc = {
},
'function-ul' : {
search: /(^[\n]+)([\n\s]*)/g,
replace: "* $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '* ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-ol' : {
search: /(.+)([\n]?)/g,
replace: ". $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '. ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-blockquote' : {
search: /(.+)([\n]?)/g,
replace: "----\n$1$2\n----\n"
replace: "----\n$1$2\n----",
break_line: true,
},
'function-h1' : {
search: /(.+)([\n]?)/g,
replace: "= $1$2"
replace: "= $1$2",
break_line: true,
whole_line: true
},
'function-h2' : {
search: /(.+)([\n]?)/g,
replace: "== $1$2"
replace: "== $1$2",
break_line: true,
whole_line: true
},
'function-h3' : {
search: /(.+)([\n]?)/g,
replace: "=== $1$2"
replace: "=== $1$2",
break_line: true,
whole_line: true
},
'function-link' : {
@@ -26,14 +26,28 @@ var Creole = {
},
'function-ul' : {
search: /(.+)([\n]?)/gi,
replace: "* $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '* ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
/* This looks silly but is completely valid Markdown */
'function-ol' : {
search: /(.+)([\n]?)/gi,
replace: "# $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '# ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-link' : {
@@ -41,45 +41,55 @@ var MarkDown = {
},
'function-ul' : {
search: /(.+)([\n]?)/g,
replace: "* $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '* ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
/* based on rdoc.js */
'function-ol' : {
exec: function( txt, selText, $field ) {
var count = 1;
// split into lines
var repText = '';
var lines = selText.split("\n");
var hasContent = /[\w]+/;
for ( var i = 0; i < lines.length; i++ ) {
if ( hasContent.test(lines[i]) ) {
repText += (i + 1).toString() + '. ' +
lines[i] + "\n";
}
}
$.GollumEditor.replaceSelection( repText );
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += (i+1).toString() + '. ' +
lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-blockquote' : {
search: /(.+)([\n]?)/g,
replace: "> $1$2"
replace: "> $1$2",
break_line: true,
},
'function-h1' : {
search: /(.+)([\n]?)/g,
replace: "# $1$2"
replace: "# $1$2",
break_line: true,
whole_line: true
},
'function-h2' : {
search: /(.+)([\n]?)/g,
replace: "## $1$2"
replace: "## $1$2",
break_line: true,
whole_line: true
},
'function-h3' : {
search: /(.+)([\n]?)/g,
replace: "### $1$2"
replace: "### $1$2",
break_line: true,
whole_line: true
},
'function-link' : {
@@ -21,34 +21,55 @@ var OrgMode = {
},
'function-ul' : {
search: /(.+)([\n]?)/g,
replace: "* $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '* ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
/* This works, just like it works for Markdown */
'function-ol' : {
search: /(.+)([\n]?)/g,
replace: "1. $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += (i+1).toString() + '. ' +
lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-blockquote' : {
search: /(.+)([\n]?)/g,
replace: "#+BEGIN_QUOTE\n$1$2\n#+END_QUOTE\n"
replace: "#+BEGIN_QUOTE\n$1$2\n#+END_QUOTE",
break_line: true,
},
'function-h1' : {
search: /(.+)([\n]?)/g,
replace: "* $1$2"
replace: "* $1$2",
break_line: true,
whole_line: true
},
'function-h2' : {
search: /(.+)([\n]?)/g,
replace: "** $1$2"
replace: "** $1$2",
break_line: true,
whole_line: true
},
'function-h3' : {
search: /(.+)([\n]?)/g,
replace: "*** $1$2"
replace: "*** $1$2",
break_line: true,
whole_line: true
},
'function-link' : {
@@ -22,17 +22,23 @@ var Pod = {
'function-h1' : {
search: /(.+)([\n]?)/gi,
replace: "=head1 $1$2"
replace: "=head1 $1$2",
break_line: true,
whole_line: true
},
'function-h2' : {
search: /(.+)([\n]?)/gi,
replace: "=head2 $1$2"
replace: "=head2 $1$2",
break_line: true,
whole_line: true
},
'function-h3' : {
search: /(.+)([\n]?)/gi,
replace: "=head3 $1$2"
replace: "=head3 $1$2",
break_line: true,
whole_line: true
},
'function-link' : {
@@ -31,40 +31,49 @@ var RDoc = {
},
'function-ul' : {
search: /(.+)([\n]?)/gi,
replace: "* $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '* ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-ol' : {
exec: function( txt, selText, $field ) {
var count = 1;
// split into lines
var repText = '';
var lines = selText.split("\n");
var hasContent = /[\w]+/;
for ( var i = 0; i < lines.length; i++ ) {
if ( hasContent.test(lines[i]) ) {
repText += '(' + (i + 1).toString() + ') ' +
lines[i];
}
repText += (i+1).toString() + '. ' +
lines[i] + "\n";
}
$.GollumEditor.replaceSelection( repText );
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-h1' : {
search: /(.+)([\n]?)/gi,
replace: "= $1$2"
replace: "= $1$2",
break_line: true,
whole_line: true
},
'function-h2' : {
search: /(.+)([\n]?)/gi,
replace: "== $1$2"
replace: "== $1$2",
break_line: true,
whole_line: true
},
'function-h3' : {
search: /(.+)([\n]?)/gi,
replace: "=== $1$2"
replace: "=== $1$2",
break_line: true,
whole_line: true,
},
'function-critic-accept' : {
@@ -25,18 +25,33 @@ var Textile = {
},
'function-ul' : {
search: /(.+)([\n]?)/gi,
replace: "* $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '* ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-ol' : {
search: /(.+)([\n]?)/gi,
replace: "# $1$2"
exec: function( txt, selText, $field ) {
var repText = '';
var lines = selText.split("\n");
for ( var i = 0; i < lines.length; i++ ) {
repText += '# ' + lines[i] + "\n";
}
repText = repText.substring(0, repText.length-1)
$.GollumEditor.replaceSelection( repText, true, true );
},
},
'function-blockquote' : {
search: /(.+)([\n]?)/gi,
replace: "bq. $1$2"
replace: "bq. $1$2",
break_line: true,
},
'function-link' : {