/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; (function () { /** * Класс, восстанавливающий удаленные части документа * @param {AscWord.Document} logicDocument * @constructor */ function DeletedTextRecovery(logicDocument) { this.document = logicDocument; /** * Список всех изменений связанных с удалением текста * @type {*[]} */ this.m_RewiewDelPoints = []; this.arrColor = []; this.userId = undefined; this.userName = undefined; this.userTime = undefined; } /** * Восстанавливаем удаленный текст в текущей точке истории версий * @return {boolean} */ DeletedTextRecovery.prototype.RecoverDeletedText = function() { this.UndoRecoveredText(); return this.ShowDelText(); }; /** * Отменяем восстановление удаленного текста, если оно было */ DeletedTextRecovery.prototype.UndoRecoveredText = function () { if (!this.HaveRecoveredText()) return; let localHistory = AscCommon.History; let changes = localHistory.Undo(); this.document.UpdateAfterUndoRedo(changes); localHistory.ClearRedo(); }; /** * Запрашивем,есть ли восстановленный удаленный текст * @return {boolean} */ DeletedTextRecovery.prototype.HaveRecoveredText = function() { let localHistory = AscCommon.History; let lastPoint = localHistory.Points[localHistory.Points.length - 1]; return !!(lastPoint && lastPoint.Description === AscDFH.historydescription_Collaborative_DeletedTextRecovery); }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Private area //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Инициализация и создание промежуточных данных для отображения удаленного текста в текущей ревизии */ DeletedTextRecovery.prototype.HandleChanges = function() { AscCommon.CollaborativeEditing.CoHistory.SplitChangesByPoints(); let oCoHistory = AscCommon.CollaborativeEditing.CoHistory; let arrChangesList = oCoHistory.Changes; let arrPointsList = AscCommon.CollaborativeEditing.CoHistory.ChangesSplitByPoints; let nIndex = AscCommon.CollaborativeEditing.CoHistory.curChangeIndex; let arrChanges = arrChangesList.slice(0, arrPointsList[nIndex]); if (!arrChanges || !arrChanges.length) return; this.m_RewiewDelPoints = arrChanges; }; /** * Получаем подготовленные данные, разбитые по точкам * @return {*[]} */ DeletedTextRecovery.prototype.GetChanges = function() { let arr = this.m_RewiewDelPoints.slice(); let arrOutput = [] for (let i = 0; i < arr.length; i++) { let oChange = arr[i]; if (oChange.ConvertToSimpleChanges) { let arrSplitChange = oChange.ConvertToSimpleChanges(); if (arrSplitChange && arrSplitChange.length) { for (let j = 0; j < arrSplitChange.length; j++) { arrOutput.push(arrSplitChange[j]) } } else { arrOutput.push(oChange); } } else arrOutput.push(oChange); } return arrOutput; }; DeletedTextRecovery.prototype.GetRemoveTextChanges = function (arrInputChanges, oRemoveText) { let oAddText = new AddTextPositions(); // отбираем удаленный текст связанный с текущей ревизией for (let i = 0; i < arrInputChanges.length; i++) { let oCurChange = arrInputChanges[i]; // пропускаем все изменения связанные со сплитом if (oCurChange instanceof CChangesRunOnStartSplit) { while (oCurChange && !(oCurChange instanceof CChangesRunOnEndSplit)) { i++; oCurChange = arrInputChanges[i]; } } if (oCurChange === undefined) break; if (oCurChange instanceof AscCommon.CChangesTableIdDescription || !oCurChange.Copy) continue; let oNewCurChange = oCurChange.Copy(); oRemoveText.ProceedChange(oNewCurChange); if (oNewCurChange instanceof CChangesRunAddItem || oNewCurChange instanceof CChangesParagraphAddItem || oNewCurChange instanceof CChangesDocumentAddItem) oAddText.Add(oCurChange.Class, oCurChange, oCurChange.UseArray ? oCurChange.PosArray[0] : oCurChange.Pos, i); else if (oNewCurChange instanceof CChangesRunRemoveItem || oNewCurChange instanceof CChangesParagraphRemoveItem || oNewCurChange instanceof CChangesDocumentRemoveItem) oRemoveText.Check(oAddText, oCurChange, i); } oRemoveText.DelDuplicate(); } DeletedTextRecovery.prototype.CommuteChanges = function (arrInputChanges, arrSaveData, oRemoveText) { // коммутируем изменения let arrRevInput = arrInputChanges; let arrDelChangesForCommute = oRemoveText.GetArrayChanges(); for (let j = 0; j < arrDelChangesForCommute.length; j++) { let oCurItem = arrDelChangesForCommute[j]; let nPos = oCurItem.nIndex; let oChange = oCurItem.item; if (oChange.IsContentChange()) { let _oChange = oChange.Copy(); if (AscCommon.CollaborativeEditing.CoHistory.CommuteContentChange(_oChange, nPos, arrRevInput)) arrSaveData.push(_oChange); } else { arrSaveData.push(oChange); } } oRemoveText.ResetData(); } /** * Отменяем заданные изменения * @param arrInputChanges * @return {*[]} */ DeletedTextRecovery.prototype.RedoUndoChanges = function (arrInputChanges) { let oRemoveText = new RemoveTextPositions(); let arrChanges = []; let arrDelChanges = []; this.GetRemoveTextChanges(arrInputChanges, oRemoveText); this.CommuteChanges(arrInputChanges, arrDelChanges, oRemoveText); for (let i = 0; i < arrDelChanges.length; i++) { this.RedoUndoChange(arrDelChanges[i], false, arrChanges); let nPos = arrDelChanges[i].UseArray ? arrDelChanges[i].PosArray[0] : arrDelChanges[i].Pos; oRemoveText.Add( arrDelChanges[i].Class, arrDelChanges[i], nPos, i ); } let result = { data: oRemoveText.ProceedPositions(this), classes: oRemoveText.oClasses }; return [arrChanges, result]; }; DeletedTextRecovery.prototype.ShowDelText = function () { let versionHistory = this.document.GetApi().getVersionHistory(); if (!versionHistory) return false; this.HandleChanges(); let arrInput = this.GetChanges(); if (arrInput.length === 0) return false; let localHistory = AscCommon.History; localHistory.Create_NewPoint(AscDFH.historydescription_Collaborative_DeletedTextRecovery); this.userId = versionHistory.userId; this.userName = versionHistory.userName; this.userTime = new Date(versionHistory.dateOfRevision).getTime(); // отменяем изменения до нужного места (необходимо для перемещения по истории) let arrCurrentPoint = this.RedoUndoChanges(arrInput); let delChanges = arrCurrentPoint[0]; let arrResult = arrCurrentPoint[1]; for (let i = 0; i < delChanges.length; i++) { this.document.History.Add(delChanges[i]); } this.Split(arrResult); this.document.RecalculateByChanges(delChanges); this.m_RewiewDelPoints = []; return true; }; DeletedTextRecovery.prototype.Split = function (arrInput) { let data = arrInput.data; let classes = arrInput.classes; let arrKeys = Object.keys(data); for (let nKey = 0; nKey < arrKeys.length; nKey++) { let strCurrentKey = arrKeys[nKey]; let arrCurrentRunData = data[strCurrentKey]; let oCurrentRun = classes[strCurrentKey]; arrCurrentRunData.sort(function (a, b) { return a.nStart - b.nStart }); for (let j = arrCurrentRunData.length - 1; j >= 0; j--) { let oCurrentRule = arrCurrentRunData[j]; let nStart = oCurrentRule.nStart; let nEnd = oCurrentRule.nEnd; if (oCurrentRun instanceof CDocument) { let arrContent = oCurrentRun.Content; for (let j = nStart; j <= nEnd; j++) { let oCurrentParagraph = arrContent[j]; if (oCurrentParagraph) this.SetReviewInfo(oCurrentParagraph); } } else if (oCurrentRun instanceof Paragraph) { let arrContent = oCurrentRun.Content; if (nStart === 0 && arrContent.length === nEnd) { this.SetReviewInfo(oCurrentRun); } else { for (let i = nStart; i <= nEnd; i++) { this.SetReviewInfo(arrContent[i]); } } } else if (oCurrentRun instanceof ParaRun) { let newCollab = []; if (oCurrentRun.Content.length === 0 || (nEnd + 1 - nStart) === oCurrentRun.Content.length) { this.SetReviewInfo(oCurrentRun); continue; } for (let i = 0; i < oCurrentRun.CollaborativeMarks.Ranges.length; i++) { let oCollab = oCurrentRun.CollaborativeMarks.Ranges[i]; newCollab.push({PosS: oCollab.PosS, PosE: oCollab.PosE, Color: oCollab.Color, oCurrentRun: oCurrentRun}); } let oParent = oCurrentRun.GetParent(); let RunPos = this.FindPosInParent(oCurrentRun); let RightRun = oCurrentRun.SplitForSpreadCollaborativeMark(nStart); oParent.Add_ToContent(RunPos + 1, RightRun); let oNewer = RightRun.SplitForSpreadCollaborativeMark(nEnd - nStart + 1); oParent.Add_ToContent(RunPos + 2, oNewer); this.SetReviewInfo(RightRun); for (let i = 0; i < newCollab.length; i++) { let oCurCollaborativeMark = newCollab[i]; this.arrColor.push(oCurCollaborativeMark); } } } } } DeletedTextRecovery.prototype.SetReviewInfo = function (oReviewInfoParent) { if (!oReviewInfoParent === undefined) return; // TODO: Проверку по каким классам нужно проходить лучше переделать if (!oReviewInfoParent || !oReviewInfoParent.GetReviewInfo) { if (oReviewInfoParent instanceof ParaMath) { let oRootContent = oReviewInfoParent.Root.Content; for (let i = 0; i < oRootContent.length; i++) { let oCurrentContent = oRootContent[i]; this.SetReviewInfo(oCurrentContent); } } else if (oReviewInfoParent.Content.length > 0) { for (let i = 0; i < oReviewInfoParent.Content.length; i++) { let oCurrentContent = oReviewInfoParent.Content[i]; this.SetReviewInfo(oCurrentContent); } } return; } if (oReviewInfoParent.GetReviewType() !== reviewtype_Remove) { let reviewInfo = oReviewInfoParent.GetReviewInfo(); if (!reviewInfo) reviewInfo = new AscWord.ReviewInfo(); else reviewInfo = reviewInfo.Copy(); reviewInfo.UserId = this.userId; reviewInfo.UserName = this.userName; reviewInfo.DateTime = this.userTime; oReviewInfoParent.SetReviewTypeWithInfo(reviewtype_Remove, reviewInfo, false); } }; DeletedTextRecovery.prototype.FindPosInParent = function(oClass) { let oParent = oClass.GetParent(); let arrParentContent = oParent.Content; for (let i = 0; i < arrParentContent.length; i++) { if (arrParentContent[i] === oClass) return i; } }; DeletedTextRecovery.prototype.RedoUndoChange = function (oChange, isRedo, arrToSave) { if (!oChange) return; if (oChange.IsContentChange()) { let arrSimpleChanges = oChange.ConvertToSimpleChanges(); for (let simpleIndex = arrSimpleChanges.length - 1; simpleIndex >= 0; simpleIndex--) { if (isRedo) arrSimpleChanges[simpleIndex].Redo(); else arrSimpleChanges[simpleIndex].Undo(); let oRevChange = arrSimpleChanges[simpleIndex]; if (!isRedo) oRevChange = oRevChange.CreateReverseChange(); if (oRevChange) arrToSave.push(oRevChange); } } else { if (isRedo) oChange.Redo(); else oChange.Undo(); let oRevChange = oChange; if (!isRedo) oRevChange = oRevChange.CreateReverseChange(); if (oRevChange) arrToSave.push(oRevChange); } }; function AddTextPositions() { this.data = {}; this.Add = function (oClass, oItem, Pos, nIndex) { if (!this.data[oClass.Id]) this.data[oClass.Id] = []; this.data[oClass.Id].push({item: oItem, pos: Pos, nIndex: nIndex}); } } function RemoveTextPositions() { this.data = {}; this.oClasses = {}; this.arrClasses = []; this.Check = function (oAddText, oCurChange, i) { if (oCurChange.Class) { let strCurrentId = oCurChange.Class.Id; let arrRemData = oAddText.data[strCurrentId]; if (!arrRemData) { this.Add(oCurChange.Class, oCurChange, oCurChange.UseArray ? oCurChange.PosArray[0] : oCurChange.Pos, i); return; } let addItem = oCurChange.Items[0]; for (let i = 0; i < arrRemData.length; i++) { let oCurrentRemItem = arrRemData[i]; if (oCurChange.UseArray && oCurChange.PosArray[0] === oCurrentRemItem.pos && addItem.Value === oCurrentRemItem.item.Items[0].Value) { return false; } else if (!oCurChange.UseArray && oCurChange.Pos === oCurrentRemItem.pos && addItem.Value === oCurrentRemItem.item.Items[0].Value) { return false; } } } this.Add(oCurChange.Class, oCurChange, oCurChange.UseArray ? oCurChange.PosArray[0] : oCurChange.Pos, i); } this.DelDuplicate = function () { let arrKeys = Object.keys(this.data); for (let nKey = 0; nKey < arrKeys.length; nKey++) { let strCurrentKey = arrKeys[nKey]; let arrCurrentRunData = this.data[strCurrentKey]; for (let i = 0; i < arrCurrentRunData.length; i++) { for (let j = i + 1; j < arrCurrentRunData.length; j++) { if (arrCurrentRunData[i].item === arrCurrentRunData[j].item) { arrCurrentRunData.splice(j, 1); break } } } } } this.Add = function (oClass, oItem, Pos, nIndex) { if (!this.data[oClass.Id]) this.data[oClass.Id] = []; this.data[oClass.Id].push({item: oItem, pos: Pos, nIndex: nIndex}); } this.GetArrayChanges = function () { let arrOutput = [] let arrKeys = Object.keys(this.data); for (let nKey = 0; nKey < arrKeys.length; nKey++) { let strCurrentKey = arrKeys[nKey]; let arrCurrentRunData = this.data[strCurrentKey]; for (let i = 0; i < arrCurrentRunData.length; i++) { arrOutput.push(arrCurrentRunData[i]); } } return arrOutput; } this.ProceedChange = function (oChange) { if (oChange.Class && !this.oClasses[oChange.Class.Id]) { this.oClasses[oChange.Class.Id] = oChange.Class; if (-1 === this.arrClasses.indexOf(oChange.Class.Id)) { this.arrClasses.push(oChange.Class.Id); } } } this.FindInParent = function (oClass, oItem) { let arrParentContent = oClass.Content; for (let nPos = 0; nPos < arrParentContent.length; nPos++) { if (arrParentContent[nPos] === oItem) return nPos; } } this.ProceedPositions = function () { for (let nKey = 0; nKey < this.arrClasses.length; nKey++) { let strCurrentKey = this.arrClasses[nKey]; let arrCurrentRunData = this.data[strCurrentKey]; if (!arrCurrentRunData) continue; let oClass = this.oClasses[strCurrentKey]; let newArrCurrentRunData = []; for (let i = 0; i < arrCurrentRunData.length; i++) { let oItem = arrCurrentRunData[i]; let nPos = this.FindInParent(oClass, oItem.item.Items[0]); newArrCurrentRunData.push(nPos); } let nCurrentPos; let nPrevPos; let nTempPrevPos; for (let nPos = 1; nPos < newArrCurrentRunData.length; nPos++) { nPrevPos = newArrCurrentRunData[nPos - 1]; nCurrentPos = newArrCurrentRunData[nPos]; if (nTempPrevPos === nCurrentPos || nCurrentPos === nPrevPos) { if (nTempPrevPos) { nTempPrevPos = undefined; newArrCurrentRunData[nPos] = nPrevPos + 1; nTempPrevPos = nCurrentPos; } else { newArrCurrentRunData[nPos] = nCurrentPos + 1; nTempPrevPos = nCurrentPos; } } } this.data[strCurrentKey] = newArrCurrentRunData; } let transformedObject = CollapsePositions(this.data); return transformedObject } this.ResetData = function () { this.data = {}; } } function CollapsePositions (oInput) { let transformedObject = {}; for (let key in oInput) { if (oInput.hasOwnProperty(key)) { let values = oInput[key]; let pairs = []; let nStart = null; let nEnd = null; let decreasingSequence = false; for (let i = 0; i < values.length; i++) { let value = values[i]; if (nStart === null) { nStart = value; nEnd = value; } else if (value === nEnd + 1) { nEnd = value; decreasingSequence = false; } else if (value === nEnd - 1) { nStart = value; decreasingSequence = true; } else { pairs.push({ nStart : nStart, nEnd: nEnd }); nStart = value; nEnd = value; decreasingSequence = false; } } if (nStart !== null && nEnd !== null) pairs.push({ nStart : nStart, nEnd: nEnd }); transformedObject[key] = pairs; } } return transformedObject; } //--------------------------------------------------------export---------------------------------------------------- AscCommon.DeletedTextRecovery = DeletedTextRecovery; })();