Merge pull request #3891 from matrix-org/t3chguy/fix_multi_paragraph_formatting

Fix paragraph-awareness of the composer formatting features
This commit is contained in:
Michael Telatynski 2020-01-23 13:31:12 +00:00 committed by GitHub
commit d7a4698db8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 251 additions and 27 deletions

View file

@ -250,7 +250,7 @@ function parseHtmlMessage(html, partCreator, isQuotedMessage) {
}
export function parsePlainTextMessage(body, partCreator, isQuotedMessage) {
const lines = body.split("\n");
const lines = body.split(/\r\n|\r|\n/g); // split on any new-line combination not just \n, collapses \r\n
const parts = lines.reduce((parts, line, i) => {
if (isQuotedMessage) {
parts.push(partCreator.plain(QUOTE_LINE_PREFIX));

View file

@ -100,27 +100,71 @@ export function formatRangeAsCode(range) {
replaceRangeAndExpandSelection(range, parts);
}
// parts helper methods
const isBlank = part => !part.text || !/\S/.test(part.text);
const isNL = part => part.type === "newline";
export function toggleInlineFormat(range, prefix, suffix = prefix) {
const {model, parts} = range;
const {partCreator} = model;
const isFormatted = parts.length &&
parts[0].text.startsWith(prefix) &&
parts[parts.length - 1].text.endsWith(suffix);
// compute paragraph [start, end] indexes
const paragraphIndexes = [];
let startIndex = 0;
// start at i=2 because we look at i and up to two parts behind to detect paragraph breaks at their end
for (let i = 2; i < parts.length; i++) {
// paragraph breaks can be denoted in a multitude of ways,
// - 2 newline parts in sequence
// - newline part, plain(<empty or just spaces>), newline part
if (isFormatted) {
// remove prefix and suffix
const partWithoutPrefix = parts[0].serialize();
partWithoutPrefix.text = partWithoutPrefix.text.substr(prefix.length);
parts[0] = partCreator.deserializePart(partWithoutPrefix);
// bump startIndex onto the first non-blank after the paragraph ending
if (isBlank(parts[i - 2]) && isNL(parts[i - 1]) && !isNL(parts[i]) && !isBlank(parts[i])) {
startIndex = i;
}
const partWithoutSuffix = parts[parts.length - 1].serialize();
const suffixPartText = partWithoutSuffix.text;
partWithoutSuffix.text = suffixPartText.substring(0, suffixPartText.length - suffix.length);
parts[parts.length - 1] = partCreator.deserializePart(partWithoutSuffix);
} else {
parts.unshift(partCreator.plain(prefix));
parts.push(partCreator.plain(suffix));
// if at a paragraph break, store the indexes of the paragraph
if (isNL(parts[i - 1]) && isNL(parts[i])) {
paragraphIndexes.push([startIndex, i - 1]);
startIndex = i + 1;
} else if (isNL(parts[i - 2]) && isBlank(parts[i - 1]) && isNL(parts[i])) {
paragraphIndexes.push([startIndex, i - 2]);
startIndex = i + 1;
}
}
const lastNonEmptyPart = parts.map(isBlank).lastIndexOf(false);
// If we have not yet included the final paragraph then add it now
if (startIndex <= lastNonEmptyPart) {
paragraphIndexes.push([startIndex, lastNonEmptyPart + 1]);
}
// keep track of how many things we have inserted as an offset:=0
let offset = 0;
paragraphIndexes.forEach(([startIndex, endIndex]) => {
// for each paragraph apply the same rule
const base = startIndex + offset;
const index = endIndex + offset;
const isFormatted = (index - base > 0) &&
parts[base].text.startsWith(prefix) &&
parts[index - 1].text.endsWith(suffix);
if (isFormatted) {
// remove prefix and suffix
const partWithoutPrefix = parts[base].serialize();
partWithoutPrefix.text = partWithoutPrefix.text.substr(prefix.length);
parts[base] = partCreator.deserializePart(partWithoutPrefix);
const partWithoutSuffix = parts[index - 1].serialize();
const suffixPartText = partWithoutSuffix.text;
partWithoutSuffix.text = suffixPartText.substring(0, suffixPartText.length - suffix.length);
parts[index - 1] = partCreator.deserializePart(partWithoutSuffix);
} else {
parts.splice(index, 0, partCreator.plain(suffix)); // splice in the later one first to not change offset
parts.splice(base, 0, partCreator.plain(prefix));
offset += 2; // offset index to account for the two items we just spliced in
}
});
replaceRangeAndExpandSelection(range, parts);
}