diff options
Diffstat (limited to 'app/assets/javascripts/gl_form.js')
-rw-r--r-- | app/assets/javascripts/gl_form.js | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index a66555838ba..b98fe9f6ce2 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -3,9 +3,16 @@ import autosize from 'autosize'; import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_complete'; import dropzoneInput from './dropzone_input'; import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown'; +import IndentHelper from './helpers/indent_helper'; +import { keystroke } from './lib/utils/common_utils'; +import * as keys from './lib/utils/keycodes'; +import UndoStack from './lib/utils/undo_stack'; export default class GLForm { constructor(form, enableGFM = {}) { + this.handleKeyShortcuts = this.handleKeyShortcuts.bind(this); + this.setState = this.setState.bind(this); + this.form = form; this.textarea = this.form.find('textarea.js-gfm-input'); this.enableGFM = Object.assign({}, defaultAutocompleteConfig, enableGFM); @@ -16,6 +23,10 @@ export default class GLForm { this.enableGFM[item] = Boolean(dataSources[item]); } }); + + this.undoStack = new UndoStack(); + this.indentHelper = new IndentHelper(this.textarea[0]); + // Before we start, we should clean up any previous data for this form this.destroy(); // Set up the form @@ -85,9 +96,84 @@ export default class GLForm { clearEventListeners() { this.textarea.off('focus'); this.textarea.off('blur'); + this.textarea.off('keydown'); removeMarkdownListeners(this.form); } + setState(state) { + const selection = [this.textarea[0].selectionStart, this.textarea[0].selectionEnd]; + this.textarea.val(state); + this.textarea[0].setSelectionRange(selection[0], selection[1]); + } + + /* + Handle keypresses for a custom undo/redo stack. + We need this because the toolbar buttons and indentation helpers mess with the browser's + native undo/redo capability. + */ + handleUndo(event) { + const content = this.textarea.val(); + const { selectionStart, selectionEnd } = this.textarea[0]; + const stack = this.undoStack; + + if (stack.isEmpty()) { + // ==== Save initial state in undo history ==== + stack.save(content); + } + + if (keystroke(event, keys.Z_KEY_CODE, 'l')) { + // ==== Undo ==== + event.preventDefault(); + stack.save(content); + if (stack.canUndo()) { + this.setState(stack.undo()); + } + } else if (keystroke(event, keys.Z_KEY_CODE, 'ls') || keystroke(event, keys.Y_KEY_CODE, 'l')) { + // ==== Redo ==== + event.preventDefault(); + if (stack.canRedo()) { + this.setState(stack.redo()); + } + } else if ( + keystroke(event, keys.SPACE_KEY_CODE) || + keystroke(event, keys.ENTER_KEY_CODE) || + selectionStart !== selectionEnd + ) { + // ==== Save after finishing a word or before deleting a large selection ==== + stack.save(content); + } else if (content === '') { + // ==== Save after deleting everything ==== + stack.save(''); + } else { + // ==== Save after 1 second of inactivity ==== + stack.scheduleSave(content); + } + } + + handleIndent(event) { + if (keystroke(event, keys.LEFT_BRACKET_KEY_CODE, 'l')) { + // ==== Unindent selected lines ==== + event.preventDefault(); + this.indentHelper.unindent(); + } else if (keystroke(event, keys.RIGHT_BRACKET_KEY_CODE, 'l')) { + // ==== Indent selected lines ==== + event.preventDefault(); + this.indentHelper.indent(); + } else if (keystroke(event, keys.ENTER_KEY_CODE)) { + // ==== Auto-indent new lines ==== + event.preventDefault(); + this.indentHelper.newline(); + } else if (keystroke(event, keys.BACKSPACE_KEY_CODE)) { + // ==== Auto-delete indents at the beginning of the line ==== + this.indentHelper.backspace(event); + } + } + + handleKeyShortcuts(event) { + this.handleIndent(event); + this.handleUndo(event); + } + addEventListeners() { this.textarea.on('focus', function focusTextArea() { $(this) @@ -99,5 +185,6 @@ export default class GLForm { .closest('.md-area') .removeClass('is-focused'); }); + this.textarea.on('keydown', e => this.handleKeyShortcuts(e.originalEvent)); } } |