devexpress-richedit
Version:
DevExpress Rich Text Editor is an advanced word-processing tool designed for working with rich text documents.
133 lines (132 loc) • 6.53 kB
JavaScript
import { FixedInterval } from '@devexpress/utils/lib/intervals/fixed';
import { SimpleRunInsertedSubDocumentChange } from '../../changes/sub-document/text/simple-run-inserted';
import { CompositionHistoryItem } from '../../history/base/history-item';
import { InsertTextHistoryItem } from '../../history/items/insert-text-history-item';
import { RunsBaseManipulator } from '../runs-base-manipulator';
import { ChunkSizeCorrector } from './chunk-size-corrector';
import { InsertTextManipulatorParams } from './insert-text-manipulator-params';
import { SubDocumentPosition } from '../../sub-document';
import { InsertParagraphManipulatorParams } from '../paragraph-manipulator/insert-paragraph-manipulator-params';
import { RunType } from '../../runs/run-type';
export class InsertTextViaHistoryResult {
constructor(inserted, insertedInterval) {
this.inserted = inserted;
this.insertedInterval = insertedInterval;
}
}
export class TextManipulator extends RunsBaseManipulator {
constructor(manipulator) {
super(manipulator);
this.chunkSizeCorrector = new ChunkSizeCorrector();
}
insertTextViaHistory(params) {
if (!params.correctAndCheckParams())
return new InsertTextViaHistoryResult(false, null);
this.history.addAndRedo(new InsertTextHistoryItem(this.modelManipulator, params));
return new InsertTextViaHistoryResult(true, new FixedInterval(params.subDocPos.position, params.text.length));
}
insertTextInner(params) {
params.subDocPos.subDocument = params.subDocPos.subDocument.getActualSubDocument();
const insertedRun = this.insertRunInternal(params.subDocPos, params.charPropsBundle, params.runType, params.text);
const textRun = params.subDocPos.subDocument.chunks[insertedRun.chunkIndex].textRuns[insertedRun.runIndex];
textRun.paragraph.length += params.text.length;
this.chunkSizeCorrector.correctChunkSizeAtInsertPosition(params.subDocPos.subDocument, params.subDocPos.position);
this.modelManipulator.notifyModelChanged(new SimpleRunInsertedSubDocumentChange(params.subDocPos.subDocument.id, params.subDocPos.position, params.text.length, textRun.maskedCharacterProperties, textRun.characterStyle, params.runType, params.text));
return new FixedInterval(params.subDocPos.position, params.text.length);
}
insertTextWithParagraphs(params, inputPosition, viaHistory = true) {
const text = params.text;
const position = params.subDocPos.position;
const subDocument = params.subDocPos.subDocument;
const charPropsBundle = params.charPropsBundle;
const parMarkRegexp = new RegExp(/\r\n|\n|\r/g);
const stringLength = text.length;
let paragraphMatch = parMarkRegexp.exec(text);
let fromPos = 0;
let toPos = position;
const resultIntervals = [];
while (fromPos < stringLength) {
let insertedTextLength;
if (paragraphMatch?.index === fromPos) {
const params = InsertParagraphManipulatorParams.makeParamsByPosition(new SubDocumentPosition(subDocument, toPos), inputPosition);
if (viaHistory)
resultIntervals.push(this.modelManipulator.paragraph.insertParagraphViaHistory(params));
else {
params.correctAndCheckParams();
resultIntervals.push(this.modelManipulator.paragraph.insertParagraphInner(params));
}
insertedTextLength = paragraphMatch.toString().length;
paragraphMatch = parMarkRegexp.exec(text);
}
else {
const paragraphEndPos = paragraphMatch?.index ?? stringLength;
const paragraphText = text.substring(fromPos, paragraphEndPos);
const params = new InsertTextManipulatorParams(new SubDocumentPosition(subDocument, toPos), charPropsBundle, RunType.TextRun, paragraphText);
if (viaHistory)
resultIntervals.push(this.modelManipulator.text.insertTextViaHistory(params).insertedInterval);
else {
params.correctAndCheckParams();
resultIntervals.push(this.insertTextInner(params));
}
insertedTextLength = paragraphText.length;
}
toPos += insertedTextLength;
fromPos += insertedTextLength;
}
return resultIntervals;
}
getLastModifiableHistoryItem(checkType) {
return this.getLastModifiableHistoryItemNode(checkType)?.item;
}
getLastModifiableHistoryItemNode(checkType) {
const history = this.history;
if (!history)
return null;
const historyItems = history.historyItems;
const result = history.currentIndex == historyItems.length - 1 ? this.getLastHistoryItemNode(historyItems, null, checkType) : null;
return result?.empty ? null : result;
}
getLastHistoryItemNode(historyItems, parent, checkType) {
for (let ind = historyItems.length - 1, item; item = historyItems[ind]; ind--) {
if (!item.canBeMerged())
return new NullHistoryItemNode();
else if (checkType(item))
return new HistoryItemNode(item, parent);
else if (item instanceof CompositionHistoryItem) {
const lowLevelResult = this.getLastHistoryItemNode(item.historyItems, new ParentHistoryItemNode(item, parent), checkType);
if (lowLevelResult.exist)
return lowLevelResult;
}
}
return new UndefinedHistoryItemNode();
}
}
class HistoryItemNode {
constructor(item, parent) {
this.item = item;
this.parent = parent;
}
get root() {
let result = this;
while (result.parent)
result = result.parent;
return result;
}
get exist() { return this.item !== undefined; }
get empty() { return !this.item; }
}
class ParentHistoryItemNode extends HistoryItemNode {
constructor(item, parent) {
super(item, parent);
}
}
class UndefinedHistoryItemNode extends HistoryItemNode {
constructor() {
super(undefined, null);
}
}
class NullHistoryItemNode extends HistoryItemNode {
constructor() {
super(null, null);
}
}