Resolve "Loss of input text on comments after preview"
This commit is contained in:
parent
a2d82d4c55
commit
44fce0b0fd
4 changed files with 71 additions and 85 deletions
|
@ -2,7 +2,7 @@ import $ from 'jquery';
|
||||||
import autosize from 'autosize';
|
import autosize from 'autosize';
|
||||||
import GfmAutoComplete from './gfm_auto_complete';
|
import GfmAutoComplete from './gfm_auto_complete';
|
||||||
import dropzoneInput from './dropzone_input';
|
import dropzoneInput from './dropzone_input';
|
||||||
import textUtils from './lib/utils/text_markdown';
|
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
|
||||||
|
|
||||||
export default class GLForm {
|
export default class GLForm {
|
||||||
constructor(form, enableGFM = false) {
|
constructor(form, enableGFM = false) {
|
||||||
|
@ -47,7 +47,7 @@ export default class GLForm {
|
||||||
}
|
}
|
||||||
// form and textarea event listeners
|
// form and textarea event listeners
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
textUtils.init(this.form);
|
addMarkdownListeners(this.form);
|
||||||
// hide discard button
|
// hide discard button
|
||||||
this.form.find('.js-note-discard').hide();
|
this.form.find('.js-note-discard').hide();
|
||||||
this.form.show();
|
this.form.show();
|
||||||
|
@ -86,7 +86,7 @@ export default class GLForm {
|
||||||
clearEventListeners() {
|
clearEventListeners() {
|
||||||
this.textarea.off('focus');
|
this.textarea.off('focus');
|
||||||
this.textarea.off('blur');
|
this.textarea.off('blur');
|
||||||
textUtils.removeListeners(this.form);
|
removeMarkdownListeners(this.form);
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListeners() {
|
addEventListeners() {
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
|
/* eslint-disable import/prefer-default-export, func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len, vars-on-top */
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import { insertText } from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
const textUtils = {};
|
function selectedText(text, textarea) {
|
||||||
|
|
||||||
textUtils.selectedText = function(text, textarea) {
|
|
||||||
return text.substring(textarea.selectionStart, textarea.selectionEnd);
|
return text.substring(textarea.selectionStart, textarea.selectionEnd);
|
||||||
};
|
}
|
||||||
|
|
||||||
textUtils.lineBefore = function(text, textarea) {
|
function lineBefore(text, textarea) {
|
||||||
var split;
|
var split;
|
||||||
split = text.substring(0, textarea.selectionStart).trim().split('\n');
|
split = text.substring(0, textarea.selectionStart).trim().split('\n');
|
||||||
return split[split.length - 1];
|
return split[split.length - 1];
|
||||||
};
|
}
|
||||||
|
|
||||||
textUtils.lineAfter = function(text, textarea) {
|
function lineAfter(text, textarea) {
|
||||||
return text.substring(textarea.selectionEnd).trim().split('\n')[0];
|
return text.substring(textarea.selectionEnd).trim().split('\n')[0];
|
||||||
};
|
}
|
||||||
|
|
||||||
textUtils.blockTagText = function(text, textArea, blockTag, selected) {
|
function blockTagText(text, textArea, blockTag, selected) {
|
||||||
var lineAfter, lineBefore;
|
const before = lineBefore(text, textArea);
|
||||||
lineBefore = this.lineBefore(text, textArea);
|
const after = lineAfter(text, textArea);
|
||||||
lineAfter = this.lineAfter(text, textArea);
|
if (before === blockTag && after === blockTag) {
|
||||||
if (lineBefore === blockTag && lineAfter === blockTag) {
|
|
||||||
// To remove the block tag we have to select the line before & after
|
// To remove the block tag we have to select the line before & after
|
||||||
if (blockTag != null) {
|
if (blockTag != null) {
|
||||||
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
|
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
|
||||||
|
@ -32,10 +29,30 @@ textUtils.blockTagText = function(text, textArea, blockTag, selected) {
|
||||||
} else {
|
} else {
|
||||||
return blockTag + "\n" + selected + "\n" + blockTag;
|
return blockTag + "\n" + selected + "\n" + blockTag;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
|
function moveCursor(textArea, tag, wrapped, removedLastNewLine) {
|
||||||
var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
|
var pos;
|
||||||
|
if (!textArea.setSelectionRange) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (textArea.selectionStart === textArea.selectionEnd) {
|
||||||
|
if (wrapped) {
|
||||||
|
pos = textArea.selectionStart - tag.length;
|
||||||
|
} else {
|
||||||
|
pos = textArea.selectionStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removedLastNewLine) {
|
||||||
|
pos -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textArea.setSelectionRange(pos, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function insertMarkdownText(textArea, text, tag, blockTag, selected, wrap) {
|
||||||
|
var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
|
||||||
removedLastNewLine = false;
|
removedLastNewLine = false;
|
||||||
removedFirstNewLine = false;
|
removedFirstNewLine = false;
|
||||||
currentLineEmpty = false;
|
currentLineEmpty = false;
|
||||||
|
@ -67,9 +84,9 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
|
||||||
|
|
||||||
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
|
if (selectedSplit.length > 1 && (!wrap || (blockTag != null && blockTag !== ''))) {
|
||||||
if (blockTag != null && blockTag !== '') {
|
if (blockTag != null && blockTag !== '') {
|
||||||
insertText = this.blockTagText(text, textArea, blockTag, selected);
|
textToInsert = blockTagText(text, textArea, blockTag, selected);
|
||||||
} else {
|
} else {
|
||||||
insertText = selectedSplit.map(function(val) {
|
textToInsert = selectedSplit.map(function(val) {
|
||||||
if (val.indexOf(tag) === 0) {
|
if (val.indexOf(tag) === 0) {
|
||||||
return "" + (val.replace(tag, ''));
|
return "" + (val.replace(tag, ''));
|
||||||
} else {
|
} else {
|
||||||
|
@ -78,78 +95,42 @@ textUtils.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
|
textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removedFirstNewLine) {
|
if (removedFirstNewLine) {
|
||||||
insertText = '\n' + insertText;
|
textToInsert = '\n' + textToInsert;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removedLastNewLine) {
|
if (removedLastNewLine) {
|
||||||
insertText += '\n';
|
textToInsert += '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.queryCommandSupported('insertText')) {
|
insertText(textArea, textToInsert);
|
||||||
inserted = document.execCommand('insertText', false, insertText);
|
return moveCursor(textArea, tag, wrap, removedLastNewLine);
|
||||||
}
|
}
|
||||||
if (!inserted) {
|
|
||||||
try {
|
|
||||||
document.execCommand("ms-beginUndoUnit");
|
|
||||||
} catch (error) {}
|
|
||||||
textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
|
|
||||||
try {
|
|
||||||
document.execCommand("ms-endUndoUnit");
|
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
|
|
||||||
};
|
|
||||||
|
|
||||||
textUtils.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
|
function updateText(textArea, tag, blockTag, wrap) {
|
||||||
var pos;
|
|
||||||
if (!textArea.setSelectionRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (textArea.selectionStart === textArea.selectionEnd) {
|
|
||||||
if (wrapped) {
|
|
||||||
pos = textArea.selectionStart - tag.length;
|
|
||||||
} else {
|
|
||||||
pos = textArea.selectionStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedLastNewLine) {
|
|
||||||
pos -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return textArea.setSelectionRange(pos, pos);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
textUtils.updateText = function(textArea, tag, blockTag, wrap) {
|
|
||||||
var $textArea, selected, text;
|
var $textArea, selected, text;
|
||||||
$textArea = $(textArea);
|
$textArea = $(textArea);
|
||||||
textArea = $textArea.get(0);
|
textArea = $textArea.get(0);
|
||||||
text = $textArea.val();
|
text = $textArea.val();
|
||||||
selected = this.selectedText(text, textArea);
|
selected = selectedText(text, textArea);
|
||||||
$textArea.focus();
|
$textArea.focus();
|
||||||
return this.insertText(textArea, text, tag, blockTag, selected, wrap);
|
return insertMarkdownText(textArea, text, tag, blockTag, selected, wrap);
|
||||||
};
|
}
|
||||||
|
|
||||||
textUtils.init = function(form) {
|
function replaceRange(s, start, end, substitute) {
|
||||||
var self;
|
|
||||||
self = this;
|
|
||||||
return $('.js-md', form).off('click').on('click', function() {
|
|
||||||
var $this;
|
|
||||||
$this = $(this);
|
|
||||||
return self.updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
textUtils.removeListeners = function(form) {
|
|
||||||
return $('.js-md', form).off('click');
|
|
||||||
};
|
|
||||||
|
|
||||||
textUtils.replaceRange = function(s, start, end, substitute) {
|
|
||||||
return s.substring(0, start) + substitute + s.substring(end);
|
return s.substring(0, start) + substitute + s.substring(end);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default textUtils;
|
export function addMarkdownListeners(form) {
|
||||||
|
return $('.js-md', form).off('click').on('click', function() {
|
||||||
|
const $this = $(this);
|
||||||
|
return updateText($this.closest('.md-area').find('textarea'), $this.data('mdTag'), $this.data('mdBlock'), !$this.data('mdPrepend'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeMarkdownListeners(form) {
|
||||||
|
return $('.js-md', form).off('click');
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix Firefox stealing formatting characters on issue notes
|
||||||
|
merge_request:
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -1,4 +1,4 @@
|
||||||
import textUtils from '~/lib/utils/text_markdown';
|
import { insertMarkdownText } from '~/lib/utils/text_markdown';
|
||||||
|
|
||||||
describe('init markdown', () => {
|
describe('init markdown', () => {
|
||||||
let textArea;
|
let textArea;
|
||||||
|
@ -21,7 +21,7 @@ describe('init markdown', () => {
|
||||||
textArea.selectionStart = 0;
|
textArea.selectionStart = 0;
|
||||||
textArea.selectionEnd = 0;
|
textArea.selectionEnd = 0;
|
||||||
|
|
||||||
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||||
});
|
});
|
||||||
|
@ -32,7 +32,7 @@ describe('init markdown', () => {
|
||||||
textArea.value = initialValue;
|
textArea.value = initialValue;
|
||||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||||
|
|
||||||
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}\n* `);
|
expect(textArea.value).toEqual(`${initialValue}\n* `);
|
||||||
});
|
});
|
||||||
|
@ -43,7 +43,7 @@ describe('init markdown', () => {
|
||||||
textArea.value = initialValue;
|
textArea.value = initialValue;
|
||||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||||
|
|
||||||
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||||
});
|
});
|
||||||
|
@ -54,7 +54,7 @@ describe('init markdown', () => {
|
||||||
textArea.value = initialValue;
|
textArea.value = initialValue;
|
||||||
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
textArea.setSelectionRange(initialValue.length, initialValue.length);
|
||||||
|
|
||||||
textUtils.insertText(textArea, textArea.value, '*', null, '', false);
|
insertMarkdownText(textArea, textArea.value, '*', null, '', false);
|
||||||
|
|
||||||
expect(textArea.value).toEqual(`${initialValue}* `);
|
expect(textArea.value).toEqual(`${initialValue}* `);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue