From 2aea55a92c1af8150e6c484bbc538e44e754ac0d Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Fri, 28 Dec 2018 23:23:06 +0100 Subject: [PATCH] [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. --- .../javascript/editor/gollum.editor.js.erb | 302 +++++++----------- .../javascript/editor/langs/asciidoc.js | 37 ++- .../gollum/javascript/editor/langs/creole.js | 22 +- .../javascript/editor/langs/markdown.js | 48 +-- .../gollum/javascript/editor/langs/org.js | 39 ++- .../gollum/javascript/editor/langs/pod.js | 12 +- .../gollum/javascript/editor/langs/rdoc.js | 37 ++- .../gollum/javascript/editor/langs/textile.js | 25 +- 8 files changed, 271 insertions(+), 251 deletions(-) diff --git a/lib/gollum/public/gollum/javascript/editor/gollum.editor.js.erb b/lib/gollum/public/gollum/javascript/editor/gollum.editor.js.erb index d49dd18e..8992691f 100755 --- a/lib/gollum/public/gollum/javascript/editor/gollum.editor.js.erb +++ b/lib/gollum/public/gollum/javascript/editor/gollum.editor.js.erb @@ -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(); }; diff --git a/lib/gollum/public/gollum/javascript/editor/langs/asciidoc.js b/lib/gollum/public/gollum/javascript/editor/langs/asciidoc.js index b4a7a5b9..60388ec5 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/asciidoc.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/asciidoc.js @@ -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' : { diff --git a/lib/gollum/public/gollum/javascript/editor/langs/creole.js b/lib/gollum/public/gollum/javascript/editor/langs/creole.js index 90c4cd0e..44a173f4 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/creole.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/creole.js @@ -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' : { diff --git a/lib/gollum/public/gollum/javascript/editor/langs/markdown.js b/lib/gollum/public/gollum/javascript/editor/langs/markdown.js index 5b2cb55a..2704fcfb 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/markdown.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/markdown.js @@ -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' : { diff --git a/lib/gollum/public/gollum/javascript/editor/langs/org.js b/lib/gollum/public/gollum/javascript/editor/langs/org.js index f6a0c5e2..d282f309 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/org.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/org.js @@ -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' : { diff --git a/lib/gollum/public/gollum/javascript/editor/langs/pod.js b/lib/gollum/public/gollum/javascript/editor/langs/pod.js index 89125bb3..fdcb1f0e 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/pod.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/pod.js @@ -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' : { diff --git a/lib/gollum/public/gollum/javascript/editor/langs/rdoc.js b/lib/gollum/public/gollum/javascript/editor/langs/rdoc.js index e0f5fe1f..6efdcde5 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/rdoc.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/rdoc.js @@ -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' : { diff --git a/lib/gollum/public/gollum/javascript/editor/langs/textile.js b/lib/gollum/public/gollum/javascript/editor/langs/textile.js index 999ce2f4..1a8e13f0 100644 --- a/lib/gollum/public/gollum/javascript/editor/langs/textile.js +++ b/lib/gollum/public/gollum/javascript/editor/langs/textile.js @@ -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' : {