Commit 692ea39a authored by Robert Knight's avatar Robert Knight

Preserve selection when applying block formatting

Previously all modified lines in a block were replaced in one
call to replaceText() when applying block formatting, which
lost the selection.

This rewrites the command to transform each line separately
which preserves the selection.
parent c069f232
...@@ -66,6 +66,12 @@ function replaceText(state, pos, length, text) { ...@@ -66,6 +66,12 @@ function replaceText(state, pos, length, text) {
// Increment end by difference in length between original and replaced // Increment end by difference in length between original and replaced
// text // text
newSelectionEnd += text.length - length; newSelectionEnd += text.length - length;
} else if (pos < newSelectionStart &&
pos + length > newSelectionEnd) {
// 6. Replaced text fully contains selection:
// Expand selection to replaced text
newSelectionStart = pos;
newSelectionEnd = pos + length;
} }
return { return {
...@@ -157,6 +163,56 @@ function toggleSpanStyle(state, prefix, suffix, placeholder) { ...@@ -157,6 +163,56 @@ function toggleSpanStyle(state, prefix, suffix, placeholder) {
return newState; return newState;
} }
function startOfLine(str, pos) {
var start = str.lastIndexOf('\n', pos);
if (start < 0) {
return 0;
} else {
return start + 1;
}
}
function endOfLine(str, pos) {
var end = str.indexOf('\n', pos);
if (end < 0) {
return str.length;
} else {
return end;
}
}
/**
* Transform lines between two positions in an input field.
*
* @param {EditorState} state - The initial state of the input field
* @param {number} start - The start position within the input text
* @param {number} end - The end position within the input text
* @param {(EditorState, number) => EditorState} callback
* - Callback which is invoked with the current state of the input and
* the start of the current line and returns the new state of the input.
*/
function transformLines(state, start, end, callback) {
var lineStart = startOfLine(state.text, start);
var lineEnd = endOfLine(state.text, start);
while (lineEnd <= endOfLine(state.text, end)) {
var isLastLine = lineEnd === state.text.length;
var currentLineLength = lineEnd - lineStart;
state = callback(state, lineStart, lineEnd);
var newLineLength = endOfLine(state.text, lineStart) - lineStart;
end += newLineLength - currentLineLength;
if (isLastLine) {
break;
}
lineStart = lineStart + newLineLength + 1;
lineEnd = endOfLine(state.text, lineStart);
}
return state;
}
/** /**
* Toggle Markdown-style formatting around a block of text. * Toggle Markdown-style formatting around a block of text.
* *
...@@ -166,42 +222,34 @@ function toggleSpanStyle(state, prefix, suffix, placeholder) { ...@@ -166,42 +222,34 @@ function toggleSpanStyle(state, prefix, suffix, placeholder) {
* @return {EditorState} - The new state of the input field. * @return {EditorState} - The new state of the input field.
*/ */
function toggleBlockStyle(state, prefix) { function toggleBlockStyle(state, prefix) {
// Expand the start and end of the selection to the start and var start = state.selectionStart;
// and of their respective lines var end = state.selectionEnd;
var start = state.text.lastIndexOf('\n', state.selectionStart);
if (start < 0) {
start = 0;
} else {
start += 1;
}
var end = state.text.indexOf('\n', state.selectionEnd);
if (end < 0) {
end = state.text.length;
}
// Test whether all input lines are already formatted with this style // Test whether all lines in the selected range already have the style
var lines = state.text.slice(start, end).split('\n'); // applied
var prefixedLines = lines.filter(function (line) { var blockHasStyle = true;
return line.slice(0, prefix.length) === prefix; transformLines(state, start, end, function (state, lineStart) {
if (state.text.slice(lineStart, lineStart + prefix.length) !== prefix) {
blockHasStyle = false;
}
return state;
}); });
var newLines; if (blockHasStyle) {
if (prefixedLines.length === lines.length) { // Remove the formatting.
// All lines already start with the block prefix, remove the formatting. return transformLines(state, start, end, function (state, lineStart) {
newLines = lines.map(function (line) { return replaceText(state, lineStart, prefix.length, '');
return line.slice(prefix.length);
}); });
} else { } else {
// Add the block style to any lines which do not already have it applied // Add the block style to any lines which do not already have it applied
newLines = lines.map(function (line) { return transformLines(state, start, end, function (state, lineStart) {
if (line.slice(0, prefix.length) === prefix) { if (state.text.slice(lineStart, lineStart + prefix.length) === prefix) {
return line; return state;
} else { } else {
return prefix + line; return replaceText(state, lineStart, 0, prefix);
} }
}); });
} }
return replaceText(state, start, end - start, newLines.join('\n'));
} }
module.exports = { module.exports = {
......
...@@ -78,13 +78,18 @@ describe('markdown commands', function () { ...@@ -78,13 +78,18 @@ describe('markdown commands', function () {
it('adds formatting to blocks', function () { it('adds formatting to blocks', function () {
var output = toggle(parseState('one\n<sel>two\nthree</sel>\nfour')); var output = toggle(parseState('one\n<sel>two\nthree</sel>\nfour'));
assert.equal(formatState(output), 'one\n<sel>> two\n> three</sel>\nfour'); assert.equal(formatState(output), 'one\n> <sel>two\n> three</sel>\nfour');
}); });
it('removes formatting from blocks', function () { it('removes formatting from blocks', function () {
var output = toggle(parseState('one \n<sel>> two\n> three</sel>\nfour')); var output = toggle(parseState('one \n<sel>> two\n> three</sel>\nfour'));
assert.equal(formatState(output), 'one \n<sel>two\nthree</sel>\nfour'); assert.equal(formatState(output), 'one \n<sel>two\nthree</sel>\nfour');
}); });
it('preserves the selection', function () {
var output = toggle(parseState('one <sel>two\nthree </sel>four'));
assert.equal(formatState(output), '> one <sel>two\n> three </sel>four');
});
}); });
describe('link formatting', function () { describe('link formatting', function () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment