move quote formatting out of react component

This commit is contained in:
Bruno Windels 2019-09-04 12:40:03 +02:00
parent b72d1a78ec
commit b35a3531bb
5 changed files with 55 additions and 57 deletions

View File

@ -22,9 +22,11 @@ import EditorModel from '../../../editor/model';
import HistoryManager from '../../../editor/history'; import HistoryManager from '../../../editor/history';
import {setCaretPosition} from '../../../editor/caret'; import {setCaretPosition} from '../../../editor/caret';
import { import {
replaceRangeAndExpandSelection,
formatRangeAsQuote,
formatInline, formatInline,
} from '../../../editor/operations'; } from '../../../editor/operations';
import {getCaretOffsetAndText, getRangeForSelection, getSelectionOffsetAndText} from '../../../editor/dom'; import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
import Autocomplete from '../rooms/Autocomplete'; import Autocomplete from '../rooms/Autocomplete';
import {autoCompleteCreator} from '../../../editor/parts'; import {autoCompleteCreator} from '../../../editor/parts';
import {renderModel} from '../../../editor/render'; import {renderModel} from '../../../editor/render';
@ -427,37 +429,6 @@ export default class BasicMessageEditor extends React.Component {
return caretPosition; return caretPosition;
} }
_replaceSelection(callback) {
const selection = document.getSelection();
if (selection.isCollapsed) {
return;
}
const focusOffset = getSelectionOffsetAndText(
this._editorRef,
selection.focusNode,
selection.focusOffset,
).offset;
const anchorOffset = getSelectionOffsetAndText(
this._editorRef,
selection.anchorNode,
selection.anchorOffset,
).offset;
const {model} = this.props;
const focusPosition = focusOffset.asPosition(model);
const anchorPosition = anchorOffset.asPosition(model);
const range = model.startRange(focusPosition, anchorPosition);
const firstPosition = focusPosition.compare(anchorPosition) < 0 ? focusPosition : anchorPosition;
model.transform(() => {
const oldLen = range.length;
const newParts = callback(range);
const addedLen = range.replace(newParts);
const lastOffset = firstPosition.asOffset(model);
lastOffset.offset += oldLen + addedLen;
return lastOffset.asPosition(model);
});
}
_wrapSelectionAsInline(prefix, suffix = prefix) { _wrapSelectionAsInline(prefix, suffix = prefix) {
const range = getRangeForSelection( const range = getRangeForSelection(
this._editorRef, this._editorRef,
@ -479,30 +450,11 @@ export default class BasicMessageEditor extends React.Component {
} }
_formatQuote = () => { _formatQuote = () => {
const {model} = this.props; const range = getRangeForSelection(
const {partCreator} = this.props.model; this._editorRef,
this._replaceSelection(range => { this.props.model,
const parts = range.parts; document.getSelection());
parts.splice(0, 0, partCreator.plain("> ")); formatRangeAsQuote(range);
const startsWithPartial = range.start.offset !== 0;
const isFirstPart = range.start.index === 0;
const previousIsNewline = !isFirstPart && model.parts[range.start.index - 1].type === "newline";
// prepend a newline if there is more text before the range on this line
if (startsWithPartial || (!isFirstPart && !previousIsNewline)) {
parts.splice(0, 0, partCreator.newline());
}
// start at position 1 to make sure we skip the potentially inserted newline above,
// as we already inserted a quote sign for it above
for (let i = 1; i < parts.length; ++i) {
const part = parts[i];
if (part.type === "newline") {
parts.splice(i + 1, 0, partCreator.plain("> "));
}
}
parts.push(partCreator.newline());
parts.push(partCreator.newline());
return parts;
});
} }
_formatCodeBlock = () => { _formatCodeBlock = () => {

View File

@ -45,7 +45,7 @@ export function getCaretOffsetAndText(editor, sel) {
return {caret: offset, text}; return {caret: offset, text};
} }
export function getSelectionOffsetAndText(editor, selectionNode, selectionOffset) { function getSelectionOffsetAndText(editor, selectionNode, selectionOffset) {
// sometimes selectionNode is an element, and then selectionOffset means // sometimes selectionNode is an element, and then selectionOffset means
// the index of a child element ... - 1 🤷 // the index of a child element ... - 1 🤷
if (selectionNode.nodeType === Node.ELEMENT_NODE && selectionOffset !== 0) { if (selectionNode.nodeType === Node.ELEMENT_NODE && selectionOffset !== 0) {

View File

@ -23,4 +23,8 @@ export default class DocumentOffset {
asPosition(model) { asPosition(model) {
return model.positionForOffset(this.offset, this.atNodeEnd); return model.positionForOffset(this.offset, this.atNodeEnd);
} }
add(delta, atNodeEnd = false) {
return new DocumentOffset(this.offset + delta, atNodeEnd);
}
} }

View File

@ -29,6 +29,44 @@ export function replaceRangeAndExpandSelection(model, range, newParts) {
}); });
} }
export function rangeStartsAtBeginningOfLine(range) {
const {model} = range;
const startsWithPartial = range.start.offset !== 0;
const isFirstPart = range.start.index === 0;
const previousIsNewline = !isFirstPart && model.parts[range.start.index - 1].type === "newline";
return !startsWithPartial && (isFirstPart || previousIsNewline);
}
export function rangeEndsAtEndOfLine(range) {
const {model} = range;
const lastPart = model.parts[range.end.index];
const endsWithPartial = range.end.offset !== lastPart.length;
const isLastPart = range.end.index === model.parts.length - 1;
const nextIsNewline = !isLastPart && model.parts[range.end.index + 1].type === "newline";
return !endsWithPartial && (isLastPart || nextIsNewline);
}
export function formatRangeAsQuote(range) {
const {model, parts} = range;
const {partCreator} = model;
for (let i = 0; i < parts.length; ++i) {
const part = parts[i];
if (part.type === "newline") {
parts.splice(i + 1, 0, partCreator.plain("> "));
}
}
parts.unshift(partCreator.plain("> "));
if (!rangeStartsAtBeginningOfLine(range)) {
parts.unshift(partCreator.newline());
}
if (rangeEndsAtEndOfLine(range)) {
parts.push(partCreator.newline());
}
parts.push(partCreator.newline());
replaceRangeAndExpandSelection(model, range, parts);
}
export function formatInline(range, prefix, suffix = prefix) { export function formatInline(range, prefix, suffix = prefix) {
const {model, parts} = range; const {model, parts} = range;
const {partCreator} = model; const {partCreator} = model;

View File

@ -33,6 +33,10 @@ export default class Range {
this._start = this._start.backwardsWhile(this._model, predicate); this._start = this._start.backwardsWhile(this._model, predicate);
} }
get model() {
return this._model;
}
get text() { get text() {
let text = ""; let text = "";
this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => { this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => {