move quote formatting out of react component
parent
b72d1a78ec
commit
b35a3531bb
|
@ -22,9 +22,11 @@ import EditorModel from '../../../editor/model';
|
|||
import HistoryManager from '../../../editor/history';
|
||||
import {setCaretPosition} from '../../../editor/caret';
|
||||
import {
|
||||
replaceRangeAndExpandSelection,
|
||||
formatRangeAsQuote,
|
||||
formatInline,
|
||||
} from '../../../editor/operations';
|
||||
import {getCaretOffsetAndText, getRangeForSelection, getSelectionOffsetAndText} from '../../../editor/dom';
|
||||
import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
|
||||
import Autocomplete from '../rooms/Autocomplete';
|
||||
import {autoCompleteCreator} from '../../../editor/parts';
|
||||
import {renderModel} from '../../../editor/render';
|
||||
|
@ -427,37 +429,6 @@ export default class BasicMessageEditor extends React.Component {
|
|||
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) {
|
||||
const range = getRangeForSelection(
|
||||
this._editorRef,
|
||||
|
@ -479,30 +450,11 @@ export default class BasicMessageEditor extends React.Component {
|
|||
}
|
||||
|
||||
_formatQuote = () => {
|
||||
const {model} = this.props;
|
||||
const {partCreator} = this.props.model;
|
||||
this._replaceSelection(range => {
|
||||
const parts = range.parts;
|
||||
parts.splice(0, 0, partCreator.plain("> "));
|
||||
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;
|
||||
});
|
||||
const range = getRangeForSelection(
|
||||
this._editorRef,
|
||||
this.props.model,
|
||||
document.getSelection());
|
||||
formatRangeAsQuote(range);
|
||||
}
|
||||
|
||||
_formatCodeBlock = () => {
|
||||
|
|
|
@ -45,7 +45,7 @@ export function getCaretOffsetAndText(editor, sel) {
|
|||
return {caret: offset, text};
|
||||
}
|
||||
|
||||
export function getSelectionOffsetAndText(editor, selectionNode, selectionOffset) {
|
||||
function getSelectionOffsetAndText(editor, selectionNode, selectionOffset) {
|
||||
// sometimes selectionNode is an element, and then selectionOffset means
|
||||
// the index of a child element ... - 1 🤷
|
||||
if (selectionNode.nodeType === Node.ELEMENT_NODE && selectionOffset !== 0) {
|
||||
|
|
|
@ -23,4 +23,8 @@ export default class DocumentOffset {
|
|||
asPosition(model) {
|
||||
return model.positionForOffset(this.offset, this.atNodeEnd);
|
||||
}
|
||||
|
||||
add(delta, atNodeEnd = false) {
|
||||
return new DocumentOffset(this.offset + delta, atNodeEnd);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
const {model, parts} = range;
|
||||
const {partCreator} = model;
|
||||
|
|
|
@ -33,6 +33,10 @@ export default class Range {
|
|||
this._start = this._start.backwardsWhile(this._model, predicate);
|
||||
}
|
||||
|
||||
get model() {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
get text() {
|
||||
let text = "";
|
||||
this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => {
|
||||
|
|
Loading…
Reference in New Issue